@@ -11,6 +12,7 @@ * @author Binary Wang */ @Getter +@RequiredArgsConstructor public enum TicketType { /** * jsapi @@ -19,17 +21,15 @@ public enum TicketType { /** * sdk */ - SDK("2"), + SDK("sdk"), /** * 微信卡券 */ WX_CARD("wx_card"); + /** * type代码 */ - private String code; + private final String code; - TicketType(String code) { - this.code = code; - } } diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/error/WxCpErrorMsgEnum.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/error/WxCpErrorMsgEnum.java index 5529d69759..4c3dbb7cf1 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/error/WxCpErrorMsgEnum.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/error/WxCpErrorMsgEnum.java @@ -5,7 +5,7 @@ /** ** 企业微信全局错误码. - * 参考文档:企业微信全局错误码 + * 参考文档:企业微信全局错误码 * Created by Binary Wang on 2018/5/13. ** @@ -609,6 +609,322 @@ public enum WxCpErrorMsgEnum { * 不符合的state参数;必须是[a-zA-Z0-9]的参数值,长度不可超过128个字节. */ CODE_84025(84025, "不符合的state参数;必须是[a-zA-Z0-9]的参数值,长度不可超过128个字节"), + /** + * 缺少caller参数. + */ + CODE_84052(84052, "缺少caller参数"), + /** + * 缺少callee参数. + */ + CODE_84053(84053, "缺少callee参数"), + /** + * 缺少auth_corpid参数. + */ + CODE_84054(84054, "缺少auth_corpid参数"), + /** + * 超过拨打公费电话频率。排查方法:同一个客服5秒内只能调用api拨打一次公费电话 + */ + CODE_84055(84055, "超过拨打公费电话频率。排查方法:同一个客服5秒内只能调用api拨打一次公费电话"), + /** + * 被拨打用户安装应用时未授权拨打公费电话权限. + */ + CODE_84056(84056, "被拨打用户安装应用时未授权拨打公费电话权限"), + /** + * 公费电话余额不足. + */ + CODE_84057(84057, "公费电话余额不足"), + /** + * caller + */ + CODE_84058(84058, "caller 呼叫号码不支持"), + /** + * 号码非法. + */ + CODE_84059(84059, "号码非法"), + /** + * callee + */ + CODE_84060(84060, "callee 呼叫号码不支持"), + /** + * 不存在外部联系人的关系. + */ + CODE_84061(84061, "不存在外部联系人的关系"), + /** + * 未开启公费电话应用. + */ + CODE_84062(84062, "未开启公费电话应用"), + /** + * caller不存在. + */ + CODE_84063(84063, "caller不存在"), + /** + * callee不存在. + */ + CODE_84064(84064, "callee不存在"), + /** + * caller跟callee电话号码一致。排查方法:不允许自己拨打给自己 + */ + CODE_84065(84065, "caller跟callee电话号码一致。排查方法:不允许自己拨打给自己"), + /** + * 服务商拨打次数超过限制。排查方法:单个企业管理员,在一天(以上午10 + */ + CODE_84066(84066, "服务商拨打次数超过限制。排查方法:单个企业管理员,在一天(以上午10:00为起始时间)内,对应单个服务商,只能被呼叫【4】次。"), + /** + * 管理员收到的服务商公费电话个数超过限制。排查方法:单个企业管理员,在一天(以上午10 + */ + CODE_84067(84067, "管理员收到的服务商公费电话个数超过限制。排查方法:单个企业管理员,在一天(以上午10:00为起始时间)内,一共只能被【3】个服务商成功呼叫。"), + /** + * 拨打方被限制拨打公费电话. + */ + CODE_84069(84069, "拨打方被限制拨打公费电话"), + /** + * 不支持的电话号码。排查方法:拨打方或者被拨打方电话号码不支持 + */ + CODE_84070(84070, "不支持的电话号码。排查方法:拨打方或者被拨打方电话号码不支持"), + /** + * 不合法的外部联系人授权码。排查方法:非法或者已经消费过 + */ + CODE_84071(84071, "不合法的外部联系人授权码。排查方法:非法或者已经消费过"), + /** + * 应用未配置客服. + */ + CODE_84072(84072, "应用未配置客服"), + /** + * 客服userid不在应用配置的客服列表中. + */ + CODE_84073(84073, "客服userid不在应用配置的客服列表中"), + /** + * 没有外部联系人权限. + */ + CODE_84074(84074, "没有外部联系人权限"), + /** + * 不合法或过期的authcode. + */ + CODE_84075(84075, "不合法或过期的authcode"), + /** + * 缺失authcode. + */ + CODE_84076(84076, "缺失authcode"), + /** + * 订单价格过高,无法受理. + */ + CODE_84077(84077, "订单价格过高,无法受理"), + /** + * 购买人数不正确. + */ + CODE_84078(84078, "购买人数不正确"), + /** + * 价格策略不存在. + */ + CODE_84079(84079, "价格策略不存在"), + /** + * 订单不存在. + */ + CODE_84080(84080, "订单不存在"), + /** + * 存在未支付订单. + */ + CODE_84081(84081, "存在未支付订单"), + /** + * 存在申请退款中的订单. + */ + CODE_84082(84082, "存在申请退款中的订单"), + /** + * 非服务人员. + */ + CODE_84083(84083, "非服务人员"), + /** + * 非跟进用户. + */ + CODE_84084(84084, "非跟进用户"), + /** + * 应用已下架. + */ + CODE_84085(84085, "应用已下架"), + /** + * 订单人数超过可购买最大人数. + */ + CODE_84086(84086, "订单人数超过可购买最大人数"), + /** + * 打开订单支付前禁止关闭订单. + */ + CODE_84087(84087, "打开订单支付前禁止关闭订单"), + /** + * 禁止关闭已支付的订单. + */ + CODE_84088(84088, "禁止关闭已支付的订单"), + /** + * 订单已支付. + */ + CODE_84089(84089, "订单已支付"), + /** + * 缺失user_ticket. + */ + CODE_84090(84090, "缺失user_ticket"), + /** + * 订单价格不可低于下限. + */ + CODE_84091(84091, "订单价格不可低于下限"), + /** + * 无法发起代下单操作. + */ + CODE_84092(84092, "无法发起代下单操作"), + /** + * 代理关系已占用,无法代下单. + */ + CODE_84093(84093, "代理关系已占用,无法代下单"), + /** + * 该应用未配置代理分润规则,请先联系应用服务商处理. + */ + CODE_84094(84094, "该应用未配置代理分润规则,请先联系应用服务商处理"), + /** + * 免费试用版,无法扩容. + */ + CODE_84095(84095, "免费试用版,无法扩容"), + /** + * 免费试用版,无法续期. + */ + CODE_84096(84096, "免费试用版,无法续期"), + /** + * 当前企业有未处理订单. + */ + CODE_84097(84097, "当前企业有未处理订单"), + /** + * 固定总量,无法扩容. + */ + CODE_84098(84098, "固定总量,无法扩容"), + /** + * 非购买状态,无法扩容. + */ + CODE_84099(84099, "非购买状态,无法扩容"), + /** + * 未购买过此应用,无法续期. + */ + CODE_84100(84100, "未购买过此应用,无法续期"), + /** + * 企业已试用付费版本,无法全新购买. + */ + CODE_84101(84101, "企业已试用付费版本,无法全新购买"), + /** + * 企业当前应用状态已过期,无法扩容. + */ + CODE_84102(84102, "企业当前应用状态已过期,无法扩容"), + /** + * 仅可修改未支付订单. + */ + CODE_84103(84103, "仅可修改未支付订单"), + /** + * 订单已支付,无法修改. + */ + CODE_84104(84104, "订单已支付,无法修改"), + /** + * 订单已被取消,无法修改. + */ + CODE_84105(84105, "订单已被取消,无法修改"), + /** + * 企业含有该应用的待支付订单,无法代下单. + */ + CODE_84106(84106, "企业含有该应用的待支付订单,无法代下单"), + /** + * 企业含有该应用的退款中订单,无法代下单. + */ + CODE_84107(84107, "企业含有该应用的退款中订单,无法代下单"), + /** + * 企业含有该应用的待生效订单,无法代下单. + */ + CODE_84108(84108, "企业含有该应用的待生效订单,无法代下单"), + /** + * 订单定价不能未0. + */ + CODE_84109(84109, "订单定价不能未0"), + /** + * 新安装应用不在试用状态,无法升级为付费版. + */ + CODE_84110(84110, "新安装应用不在试用状态,无法升级为付费版"), + /** + * 无足够可用优惠券. + */ + CODE_84111(84111, "无足够可用优惠券"), + /** + * 无法关闭未支付订单. + */ + CODE_84112(84112, "无法关闭未支付订单"), + /** + * 无付费信息. + */ + CODE_84113(84113, "无付费信息"), + /** + * 虚拟版本不支持下单. + */ + CODE_84114(84114, "虚拟版本不支持下单"), + /** + * 虚拟版本不支持扩容. + */ + CODE_84115(84115, "虚拟版本不支持扩容"), + /** + * 虚拟版本不支持续期. + */ + CODE_84116(84116, "虚拟版本不支持续期"), + /** + * 在虚拟正式版期内不能扩容. + */ + CODE_84117(84117, "在虚拟正式版期内不能扩容"), + /** + * 虚拟正式版期内不能变更版本. + */ + CODE_84118(84118, "虚拟正式版期内不能变更版本"), + /** + * 当前企业未报备,无法进行代下单. + */ + CODE_84119(84119, "当前企业未报备,无法进行代下单"), + /** + * 当前应用版本已删除. + */ + CODE_84120(84120, "当前应用版本已删除"), + /** + * 应用版本已删除,无法扩容. + */ + CODE_84121(84121, "应用版本已删除,无法扩容"), + /** + * 应用版本已删除,无法续期. + */ + CODE_84122(84122, "应用版本已删除,无法续期"), + /** + * 非虚拟版本,无法升级. + */ + CODE_84123(84123, "非虚拟版本,无法升级"), + /** + * 非行业方案订单,不能添加部分应用版本的订单. + */ + CODE_84124(84124, "非行业方案订单,不能添加部分应用版本的订单"), + /** + * 购买人数不能少于最少购买人数. + */ + CODE_84125(84125, "购买人数不能少于最少购买人数"), + /** + * 购买人数不能多于最大购买人数. + */ + CODE_84126(84126, "购买人数不能多于最大购买人数"), + /** + * 无应用管理权限. + */ + CODE_84127(84127, "无应用管理权限"), + /** + * 无该行业方案下全部应用的管理权限. + */ + CODE_84128(84128, "无该行业方案下全部应用的管理权限"), + /** + * 付费策略已被删除,无法下单. + */ + CODE_84129(84129, "付费策略已被删除,无法下单"), + /** + * 订单生效时间不合法. + */ + CODE_84130(84130, "订单生效时间不合法"), + /** + * 文件转译解析错误。排查方法:只支持utf8文件转译,可能是不支持的文件类型或者格式 + */ + CODE_84200(84200, "文件转译解析错误。排查方法:只支持utf8文件转译,可能是不支持的文件类型或者格式"), /** * 包含不合法的词语. */ diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/error/WxMpErrorMsgEnum.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/error/WxMpErrorMsgEnum.java index c75f759660..486791986b 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/error/WxMpErrorMsgEnum.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/error/WxMpErrorMsgEnum.java @@ -86,9 +86,9 @@ public enum WxMpErrorMsgEnum { */ CODE_40016(40016, "不合法的按钮个数"), /** - * 不合法的按钮个数. + * 不合法的按钮类型. */ - CODE_40017(40017, "不合法的按钮个数"), + CODE_40017(40017, "不合法的按钮类型"), /** * 不合法的按钮名字长度. */ diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/redis/BaseWxRedisOps.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/redis/BaseWxRedisOps.java new file mode 100644 index 0000000000..17e992ab25 --- /dev/null +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/redis/BaseWxRedisOps.java @@ -0,0 +1,37 @@ +package me.chanjar.weixin.common.redis; + +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.Lock; + +/** + * 微信redis操作基本类 + *+ * 非内置实现redis相关操作, 请实现该类 + */ +public abstract class BaseWxRedisOps implements WxRedisOps { + + @Override + public String getValue(String key) { + throw new UnsupportedOperationException(); + } + + @Override + public void setValue(String key, String value, int expire, TimeUnit timeUnit) { + throw new UnsupportedOperationException(); + } + + @Override + public Long getExpire(String key) { + throw new UnsupportedOperationException(); + } + + @Override + public void expire(String key, int expire, TimeUnit timeUnit) { + throw new UnsupportedOperationException(); + } + + @Override + public Lock getLock(String key) { + throw new UnsupportedOperationException(); + } +} diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/redis/JedisWxRedisOps.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/redis/JedisWxRedisOps.java new file mode 100644 index 0000000000..b42142943f --- /dev/null +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/redis/JedisWxRedisOps.java @@ -0,0 +1,52 @@ +package me.chanjar.weixin.common.redis; + +import lombok.RequiredArgsConstructor; +import me.chanjar.weixin.common.util.locks.JedisDistributedLock; +import redis.clients.jedis.Jedis; +import redis.clients.util.Pool; + +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.Lock; + +@RequiredArgsConstructor +public class JedisWxRedisOps implements WxRedisOps { + + private final Pool
jedisPool; + + @Override + public String getValue(String key) { + try (Jedis jedis = this.jedisPool.getResource()) { + return jedis.get(key); + } + } + + @Override + public void setValue(String key, String value, int expire, TimeUnit timeUnit) { + try (Jedis jedis = this.jedisPool.getResource()) { + if (expire <= 0) { + jedis.set(key, value); + } else { + jedis.psetex(key, timeUnit.toMillis(expire), value); + } + } + } + + @Override + public Long getExpire(String key) { + try (Jedis jedis = this.jedisPool.getResource()) { + return jedis.ttl(key); + } + } + + @Override + public void expire(String key, int expire, TimeUnit timeUnit) { + try (Jedis jedis = this.jedisPool.getResource()) { + jedis.pexpire(key, timeUnit.toMillis(expire)); + } + } + + @Override + public Lock getLock(String key) { + return new JedisDistributedLock(jedisPool, key); + } +} diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/redis/RedisTemplateWxRedisOps.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/redis/RedisTemplateWxRedisOps.java new file mode 100644 index 0000000000..652cec84a1 --- /dev/null +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/redis/RedisTemplateWxRedisOps.java @@ -0,0 +1,43 @@ +package me.chanjar.weixin.common.redis; + +import lombok.RequiredArgsConstructor; +import org.springframework.data.redis.core.StringRedisTemplate; + +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +@RequiredArgsConstructor +public class RedisTemplateWxRedisOps implements WxRedisOps { + + private final StringRedisTemplate redisTemplate; + + @Override + public String getValue(String key) { + return redisTemplate.opsForValue().get(key); + } + + @Override + public void setValue(String key, String value, int expire, TimeUnit timeUnit) { + if (expire <= 0) { + redisTemplate.opsForValue().set(key, value); + } else { + redisTemplate.opsForValue().set(key, value, expire, timeUnit); + } + } + + @Override + public Long getExpire(String key) { + return redisTemplate.getExpire(key); + } + + @Override + public void expire(String key, int expire, TimeUnit timeUnit) { + redisTemplate.expire(key, expire, timeUnit); + } + + @Override + public Lock getLock(String key) { + return new ReentrantLock(); + } +} diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/redis/RedissonWxRedisOps.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/redis/RedissonWxRedisOps.java new file mode 100644 index 0000000000..d51cd3e1ad --- /dev/null +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/redis/RedissonWxRedisOps.java @@ -0,0 +1,47 @@ +package me.chanjar.weixin.common.redis; + +import lombok.RequiredArgsConstructor; +import org.redisson.api.RedissonClient; + +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.Lock; + +@RequiredArgsConstructor +public class RedissonWxRedisOps implements WxRedisOps { + + private final RedissonClient redissonClient; + + @Override + public String getValue(String key) { + Object value = redissonClient.getBucket(key).get(); + return value == null ? null : value.toString(); + } + + @Override + public void setValue(String key, String value, int expire, TimeUnit timeUnit) { + if (expire <= 0) { + redissonClient.getBucket(key).set(value); + } else { + redissonClient.getBucket(key).set(value, expire, timeUnit); + } + } + + @Override + public Long getExpire(String key) { + long expire = redissonClient.getBucket(key).remainTimeToLive(); + if (expire > 0) { + expire = expire / 1000; + } + return expire; + } + + @Override + public void expire(String key, int expire, TimeUnit timeUnit) { + redissonClient.getBucket(key).expire(expire, timeUnit); + } + + @Override + public Lock getLock(String key) { + return redissonClient.getLock(key); + } +} diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/redis/WxRedisOps.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/redis/WxRedisOps.java new file mode 100644 index 0000000000..5489165e74 --- /dev/null +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/redis/WxRedisOps.java @@ -0,0 +1,27 @@ +package me.chanjar.weixin.common.redis; + +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.Lock; + +/** + * 微信Redis相关操作 + * + * 该接口不承诺稳定, 外部实现请继承{@link BaseWxRedisOps} + * + * @see BaseWxRedisOps 实现需要继承该类 + * @see JedisWxRedisOps jedis实现 + * @see RedissonWxRedisOps redisson实现 + * @see RedisTemplateWxRedisOps redisTemplate实现 + */ +public interface WxRedisOps { + + String getValue(String key); + + void setValue(String key, String value, int expire, TimeUnit timeUnit); + + Long getExpire(String key); + + void expire(String key, int expire, TimeUnit timeUnit); + + Lock getLock(String key); +} diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/service/WxService.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/service/WxService.java new file mode 100644 index 0000000000..fc49bfd9ce --- /dev/null +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/service/WxService.java @@ -0,0 +1,41 @@ +package me.chanjar.weixin.common.service; + +import me.chanjar.weixin.common.error.WxErrorException; + +/** + * 微信服务接口. + * + * @author Binary Wang + * @date 2020-04-25 + */ +public interface WxService { + /** + * 当本Service没有实现某个API的时候,可以用这个,针对所有微信API中的GET请求. + * + * @param queryParam 参数 + * @param url 请求接口地址 + * @return 接口响应字符串 + * @throws WxErrorException 异常 + */ + String get(String url, String queryParam) throws WxErrorException; + + /** + * 当本Service没有实现某个API的时候,可以用这个,针对所有微信API中的POST请求. + * + * @param postData 请求参数json值 + * @param url 请求接口地址 + * @return 接口响应字符串 + * @throws WxErrorException 异常 + */ + String post(String url, String postData) throws WxErrorException; + + /** + * 当本Service没有实现某个API的时候,可以用这个,针对所有微信API中的POST请求. + * + * @param url 请求接口地址 + * @param obj 请求对象 + * @return 接口响应字符串 + * @throws WxErrorException 异常 + */ + String post(String url, Object obj) throws WxErrorException; +} diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/session/StandardSessionManager.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/session/StandardSessionManager.java index 2472cb44b8..591b7025dd 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/session/StandardSessionManager.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/session/StandardSessionManager.java @@ -1,13 +1,12 @@ package me.chanjar.weixin.common.session; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.atomic.AtomicBoolean; - +import me.chanjar.weixin.common.util.res.StringManager; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import me.chanjar.weixin.common.util.res.StringManager; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicBoolean; /** * 基于内存的session manager. @@ -15,7 +14,6 @@ * @author Daniel Qian */ public class StandardSessionManager implements WxSessionManager, InternalSessionManager { - protected static final StringManager SM = StringManager.getManager(Constants.PACKAGE); /** * The descriptive name of this Manager implementation (for logging). @@ -51,7 +49,9 @@ public class StandardSessionManager implements WxSessionManager, InternalSession */ protected int maxInactiveInterval = 30 * 60; - // Number of sessions created by this manager + /** + * Number of sessions created by this manager + */ protected long sessionCounter = 0; protected volatile int maxActive = 0; @@ -154,12 +154,10 @@ public InternalSession createSession(String sessionId) { session.setValid(true); session.setCreationTime(System.currentTimeMillis()); session.setMaxInactiveInterval(this.maxInactiveInterval); - String id = sessionId; - session.setId(id); + session.setId(sessionId); this.sessionCounter++; - return (session); - + return session; } @@ -181,10 +179,8 @@ protected InternalSession getNewSession() { return new StandardSession(this); } - @Override public void add(InternalSession session) { - // 当第一次有session创建的时候,开启session清理线程 if (!this.backgroundProcessStarted.getAndSet(true)) { Thread t = new Thread(new Runnable() { diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/BeanUtils.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/BeanUtils.java index 4b7f9be6a7..768f2e5324 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/BeanUtils.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/BeanUtils.java @@ -50,14 +50,13 @@ public static void checkRequiredFields(Object bean) throws WxErrorException { } } field.setAccessible(isAccessible); - } catch (SecurityException | IllegalArgumentException - | IllegalAccessException e) { + } catch (SecurityException | IllegalArgumentException | IllegalAccessException e) { log.error(e.getMessage(), e); } } if (!requiredFields.isEmpty()) { - String msg = "必填字段 " + requiredFields + " 必须提供值"; + String msg = String.format("必填字段【%s】必须提供值!", requiredFields); log.debug(msg); throw new WxErrorException(WxError.builder().errorMsg(msg).build()); } diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/XmlUtils.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/XmlUtils.java index cd3a7a984c..c2ffdb001b 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/XmlUtils.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/XmlUtils.java @@ -1,21 +1,21 @@ package me.chanjar.weixin.common.util; -import java.io.StringReader; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; - +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import com.google.common.collect.Sets; import org.dom4j.Document; import org.dom4j.DocumentException; import org.dom4j.Element; import org.dom4j.Node; import org.dom4j.io.SAXReader; import org.dom4j.tree.DefaultText; +import org.xml.sax.SAXException; -import com.google.common.collect.Lists; -import com.google.common.collect.Maps; -import com.google.common.collect.Sets; +import java.io.StringReader; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; /** *
@@ -31,13 +31,18 @@ public static Map*/ @XStreamAlias("attach") + @XStreamConverter(value = XStreamCDataConverter.class) private String attach; /** diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/result/WxPayFaceAuthInfoResult.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/result/WxPayFaceAuthInfoResult.java index 5c357c560f..53adb40d7a 100644 --- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/result/WxPayFaceAuthInfoResult.java +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/result/WxPayFaceAuthInfoResult.java @@ -21,7 +21,6 @@ @NoArgsConstructor @XStreamAlias("xml") public class WxPayFaceAuthInfoResult extends BaseWxPayResult implements Serializable { - private static final long serialVersionUID = -65138145275211272L; /** diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/result/WxPayOrderQueryResult.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/result/WxPayOrderQueryResult.java index 906a8cf12f..2737dc2a6b 100644 --- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/result/WxPayOrderQueryResult.java +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/result/WxPayOrderQueryResult.java @@ -1,17 +1,13 @@ package com.github.binarywang.wxpay.bean.result; -import java.io.Serializable; -import java.util.List; - import com.google.common.collect.Lists; import com.thoughtworks.xstream.annotations.XStreamAlias; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Data; -import lombok.EqualsAndHashCode; -import lombok.NoArgsConstructor; +import lombok.*; import org.w3c.dom.Document; +import java.io.Serializable; +import java.util.List; + /** *xml2Map(String xmlString) { Map map = new HashMap<>(16); try { SAXReader saxReader = new SAXReader(); + saxReader.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); + saxReader.setFeature("http://javax.xml.XMLConstants/feature/secure-processing", true); + saxReader.setFeature("http://xml.org/sax/features/external-parameter-entities", false); + saxReader.setFeature("http://xml.org/sax/features/external-general-entities", false); + saxReader.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false); Document doc = saxReader.read(new StringReader(xmlString)); Element root = doc.getRootElement(); List elements = root.elements(); for (Element element : elements) { - map.put(element.getName(), element2MapOrString(element)); + map.put(element.getName(), element2MapOrString(element)); } - } catch (DocumentException e) { + } catch (DocumentException | SAXException e) { throw new RuntimeException(e); } diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/SimpleGetRequestExecutor.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/SimpleGetRequestExecutor.java index 05c396e029..f221cdcbbe 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/SimpleGetRequestExecutor.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/SimpleGetRequestExecutor.java @@ -3,8 +3,9 @@ import java.io.IOException; import me.chanjar.weixin.common.WxType; +import me.chanjar.weixin.common.error.WxError; import me.chanjar.weixin.common.error.WxErrorException; -import me.chanjar.weixin.common.util.http.apache.ApacheHttpClientSimpleGetRequestExecutor; +import me.chanjar.weixin.common.util.http.apache.ApacheSimpleGetRequestExecutor; import me.chanjar.weixin.common.util.http.jodd.JoddHttpSimpleGetRequestExecutor; import me.chanjar.weixin.common.util.http.okhttp.OkHttpSimpleGetRequestExecutor; @@ -29,7 +30,7 @@ public void execute(String uri, String data, ResponseHandler handler, Wx public static RequestExecutor create(RequestHttp requestHttp) { switch (requestHttp.getRequestType()) { case APACHE_HTTP: - return new ApacheHttpClientSimpleGetRequestExecutor(requestHttp); + return new ApacheSimpleGetRequestExecutor(requestHttp); case JODD_HTTP: return new JoddHttpSimpleGetRequestExecutor(requestHttp); case OK_HTTP: @@ -39,4 +40,12 @@ public static RequestExecutor create(RequestHttp requestHttp) { } } + protected String handleResponse(WxType wxType, String responseContent) throws WxErrorException { + WxError error = WxError.fromJson(responseContent, wxType); + if (error.getErrorCode() != 0) { + throw new WxErrorException(error); + } + + return responseContent; + } } diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/SimplePostRequestExecutor.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/SimplePostRequestExecutor.java index 1209790b65..804f13fe54 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/SimplePostRequestExecutor.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/SimplePostRequestExecutor.java @@ -1,10 +1,12 @@ package me.chanjar.weixin.common.util.http; import me.chanjar.weixin.common.WxType; +import me.chanjar.weixin.common.error.WxError; import me.chanjar.weixin.common.error.WxErrorException; import me.chanjar.weixin.common.util.http.apache.ApacheSimplePostRequestExecutor; import me.chanjar.weixin.common.util.http.jodd.JoddHttpSimplePostRequestExecutor; import me.chanjar.weixin.common.util.http.okhttp.OkHttpSimplePostRequestExecutor; +import org.jetbrains.annotations.NotNull; import java.io.IOException; @@ -39,4 +41,21 @@ public static RequestExecutor create(RequestHttp requestHttp) { } } + @NotNull + public String handleResponse(WxType wxType, String responseContent) throws WxErrorException { + if (responseContent.isEmpty()) { + throw new WxErrorException(WxError.builder().errorCode(9999).errorMsg("无响应内容").build()); + } + + if (responseContent.startsWith(" ")) { + //xml格式输出直接返回 + return responseContent; + } + + WxError error = WxError.fromJson(responseContent, wxType); + if (error.getErrorCode() != 0) { + throw new WxErrorException(error); + } + return responseContent; + } } diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/ApacheMediaDownloadRequestExecutor.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/ApacheMediaDownloadRequestExecutor.java index 51c37f59a5..aeb3bb301e 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/ApacheMediaDownloadRequestExecutor.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/ApacheMediaDownloadRequestExecutor.java @@ -63,7 +63,12 @@ public File execute(String uri, String queryParam, WxType wxType) throws WxError fileName = String.valueOf(System.currentTimeMillis()); } - return FileUtils.createTmpFile(inputStream, FilenameUtils.getBaseName(fileName), FilenameUtils.getExtension(fileName), + String baseName = FilenameUtils.getBaseName(fileName); + if (StringUtils.isBlank(fileName) || baseName.length() < 3) { + baseName = String.valueOf(System.currentTimeMillis()); + } + + return FileUtils.createTmpFile(inputStream, baseName, FilenameUtils.getExtension(fileName), super.tmpDirFile); } finally { diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/ApacheHttpClientSimpleGetRequestExecutor.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/ApacheSimpleGetRequestExecutor.java similarity index 79% rename from weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/ApacheHttpClientSimpleGetRequestExecutor.java rename to weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/ApacheSimpleGetRequestExecutor.java index cc830013d2..32299f56fe 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/ApacheHttpClientSimpleGetRequestExecutor.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/ApacheSimpleGetRequestExecutor.java @@ -19,8 +19,8 @@ * @author ecoolper * @date 2017/5/4 */ -public class ApacheHttpClientSimpleGetRequestExecutor extends SimpleGetRequestExecutor { - public ApacheHttpClientSimpleGetRequestExecutor(RequestHttp requestHttp) { +public class ApacheSimpleGetRequestExecutor extends SimpleGetRequestExecutor { + public ApacheSimpleGetRequestExecutor(RequestHttp requestHttp) { super(requestHttp); } @@ -40,11 +40,7 @@ public String execute(String uri, String queryParam, WxType wxType) throws WxErr try (CloseableHttpResponse response = requestHttp.getRequestHttpClient().execute(httpGet)) { String responseContent = Utf8ResponseHandler.INSTANCE.handleResponse(response); - WxError error = WxError.fromJson(responseContent, wxType); - if (error.getErrorCode() != 0) { - throw new WxErrorException(error); - } - return responseContent; + return handleResponse(wxType, responseContent); } finally { httpGet.releaseConnection(); } diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/ApacheSimplePostRequestExecutor.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/ApacheSimplePostRequestExecutor.java index 960ea865af..598278c78c 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/ApacheSimplePostRequestExecutor.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/ApacheSimplePostRequestExecutor.java @@ -12,6 +12,7 @@ import org.apache.http.client.methods.HttpPost; import org.apache.http.entity.StringEntity; import org.apache.http.impl.client.CloseableHttpClient; +import org.jetbrains.annotations.NotNull; import java.io.IOException; @@ -42,22 +43,10 @@ public String execute(String uri, String postEntity, WxType wxType) throws WxErr try (CloseableHttpResponse response = requestHttp.getRequestHttpClient().execute(httpPost)) { String responseContent = Utf8ResponseHandler.INSTANCE.handleResponse(response); - if (responseContent.isEmpty()) { - throw new WxErrorException(WxError.builder().errorCode(9999).errorMsg("无响应内容").build()); - } - - if (responseContent.startsWith(" ")) { - //xml格式输出直接返回 - return responseContent; - } - - WxError error = WxError.fromJson(responseContent, wxType); - if (error.getErrorCode() != 0) { - throw new WxErrorException(error); - } - return responseContent; + return this.handleResponse(wxType, responseContent); } finally { httpPost.releaseConnection(); } } + } diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/DefaultApacheHttpClientBuilder.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/DefaultApacheHttpClientBuilder.java index dfca21a7b2..3fb08ab2c6 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/DefaultApacheHttpClientBuilder.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/DefaultApacheHttpClientBuilder.java @@ -1,5 +1,7 @@ package me.chanjar.weixin.common.util.http.apache; +import lombok.Data; +import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.apache.http.HttpHost; import org.apache.http.annotation.NotThreadSafe; @@ -23,8 +25,6 @@ import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; import org.apache.http.protocol.HttpContext; import org.apache.http.ssl.SSLContexts; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import javax.net.ssl.SSLContext; import java.io.IOException; @@ -41,26 +41,65 @@ * * @author kakotor */ +@Slf4j +@Data @NotThreadSafe public class DefaultApacheHttpClientBuilder implements ApacheHttpClientBuilder { - protected final Logger log = LoggerFactory.getLogger(DefaultApacheHttpClientBuilder.class); private final AtomicBoolean prepared = new AtomicBoolean(false); - private int connectionRequestTimeout = 3000; + + /** + * 获取链接的超时时间设置 + * + * 设置为零时不超时,一直等待. + * 设置为负数是使用系统默认设置(非3000ms的默认值,而是httpClient的默认设置). + *
+ */ + private int connectionRequestTimeout = -1; + + /** + * 建立链接的超时时间,默认为5000ms.由于是在链接池获取链接,此设置应该并不起什么作用 + *+ * 设置为零时不超时,一直等待. + * 设置为负数是使用系统默认设置(非上述的5000ms的默认值,而是httpclient的默认设置). + *
+ */ private int connectionTimeout = 5000; + /** + * 默认NIO的socket超时设置,默认5000ms. + */ private int soTimeout = 5000; + /** + * 空闲链接的超时时间,默认60000ms. + *+ * 超时的链接将在下一次空闲链接检查是被销毁 + *
+ */ private int idleConnTimeout = 60000; + /** + * 检查空间链接的间隔周期,默认60000ms. + */ private int checkWaitTime = 60000; + /** + * 每路的最大链接数,默认10 + */ private int maxConnPerHost = 10; + /** + * 最大总连接数,默认50 + */ private int maxTotalConn = 50; + /** + * 自定义httpclient的User Agent + */ private String userAgent; - private HttpRequestRetryHandler httpRequestRetryHandler = new HttpRequestRetryHandler() { + + private final HttpRequestRetryHandler httpRequestRetryHandler = new HttpRequestRetryHandler() { @Override public boolean retryRequest(IOException exception, int executionCount, HttpContext context) { return false; } }; private SSLConnectionSocketFactory sslConnectionSocketFactory = SSLConnectionSocketFactory.getSocketFactory(); - private PlainConnectionSocketFactory plainConnectionSocketFactory = PlainConnectionSocketFactory.getSocketFactory(); + private final PlainConnectionSocketFactory plainConnectionSocketFactory = PlainConnectionSocketFactory.getSocketFactory(); private String httpProxyHost; private int httpProxyPort; private String httpProxyUsername; @@ -111,90 +150,6 @@ public ApacheHttpClientBuilder sslConnectionSocketFactory(SSLConnectionSocketFac return this; } - /** - * 获取链接的超时时间设置,默认3000ms - *- * 设置为零时不超时,一直等待. - * 设置为负数是使用系统默认设置(非上述的3000ms的默认值,而是httpclient的默认设置). - *
- * - * @param connectionRequestTimeout 获取链接的超时时间设置(单位毫秒),默认3000ms - */ - public void setConnectionRequestTimeout(int connectionRequestTimeout) { - this.connectionRequestTimeout = connectionRequestTimeout; - } - - /** - * 建立链接的超时时间,默认为5000ms.由于是在链接池获取链接,此设置应该并不起什么作用 - *- * 设置为零时不超时,一直等待. - * 设置为负数是使用系统默认设置(非上述的5000ms的默认值,而是httpclient的默认设置). - *
- * - * @param connectionTimeout 建立链接的超时时间设置(单位毫秒),默认5000ms - */ - public void setConnectionTimeout(int connectionTimeout) { - this.connectionTimeout = connectionTimeout; - } - - /** - * 默认NIO的socket超时设置,默认5000ms. - * - * @param soTimeout 默认NIO的socket超时设置,默认5000ms. - * @see java.net.SocketOptions#SO_TIMEOUT - */ - public void setSoTimeout(int soTimeout) { - this.soTimeout = soTimeout; - } - - /** - * 空闲链接的超时时间,默认60000ms. - *- * 超时的链接将在下一次空闲链接检查是被销毁 - *
- * - * @param idleConnTimeout 空闲链接的超时时间,默认60000ms. - */ - public void setIdleConnTimeout(int idleConnTimeout) { - this.idleConnTimeout = idleConnTimeout; - } - - /** - * 检查空间链接的间隔周期,默认60000ms. - * - * @param checkWaitTime 检查空间链接的间隔周期,默认60000ms. - */ - public void setCheckWaitTime(int checkWaitTime) { - this.checkWaitTime = checkWaitTime; - } - - /** - * 每路的最大链接数,默认10 - * - * @param maxConnPerHost 每路的最大链接数,默认10 - */ - public void setMaxConnPerHost(int maxConnPerHost) { - this.maxConnPerHost = maxConnPerHost; - } - - /** - * 最大总连接数,默认50 - * - * @param maxTotalConn 最大总连接数,默认50 - */ - public void setMaxTotalConn(int maxTotalConn) { - this.maxTotalConn = maxTotalConn; - } - - /** - * 自定义httpclient的User Agent - * - * @param userAgent User Agent - */ - public void setUserAgent(String userAgent) { - this.userAgent = userAgent; - } - public IdleConnectionMonitorThread getIdleConnectionMonitorThread() { return this.idleConnectionMonitorThread; } @@ -268,7 +223,7 @@ public boolean isTrusted(X509Certificate[] chain, String authType) throws Certif null, SSLConnectionSocketFactory.getDefaultHostnameVerifier()); } catch (NoSuchAlgorithmException | KeyManagementException | KeyStoreException e) { - this.log.error(e.getMessage(), e); + log.error("构建SSL连接工厂时发生异常!", e); } return null; diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/jodd/JoddHttpMediaDownloadRequestExecutor.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/jodd/JoddHttpMediaDownloadRequestExecutor.java index 4f310274df..df508869f0 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/jodd/JoddHttpMediaDownloadRequestExecutor.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/jodd/JoddHttpMediaDownloadRequestExecutor.java @@ -60,9 +60,14 @@ public File execute(String uri, String queryParam, WxType wxType) throws WxError return null; } + String baseName = FilenameUtils.getBaseName(fileName); + if (StringUtils.isBlank(fileName) || baseName.length() < 3) { + baseName = String.valueOf(System.currentTimeMillis()); + } + try (InputStream inputStream = new ByteArrayInputStream(response.bodyBytes())) { return FileUtils.createTmpFile(inputStream, - FilenameUtils.getBaseName(fileName), + baseName, FilenameUtils.getExtension(fileName), super.tmpDirFile); } diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/jodd/JoddHttpSimpleGetRequestExecutor.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/jodd/JoddHttpSimpleGetRequestExecutor.java index c93bd4b180..193787a6bc 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/jodd/JoddHttpSimpleGetRequestExecutor.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/jodd/JoddHttpSimpleGetRequestExecutor.java @@ -41,13 +41,7 @@ public String execute(String uri, String queryParam, WxType wxType) throws WxErr HttpResponse response = request.send(); response.charset(StringPool.UTF_8); - String responseContent = response.bodyText(); - - WxError error = WxError.fromJson(responseContent, wxType); - if (error.getErrorCode() != 0) { - throw new WxErrorException(error); - } - return responseContent; + return handleResponse(wxType, response.bodyText()); } } diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/jodd/JoddHttpSimplePostRequestExecutor.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/jodd/JoddHttpSimplePostRequestExecutor.java index 75114d6a7d..08dd9792ed 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/jodd/JoddHttpSimplePostRequestExecutor.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/jodd/JoddHttpSimplePostRequestExecutor.java @@ -6,7 +6,6 @@ import jodd.http.ProxyInfo; import jodd.util.StringPool; import me.chanjar.weixin.common.WxType; -import me.chanjar.weixin.common.error.WxError; import me.chanjar.weixin.common.error.WxErrorException; import me.chanjar.weixin.common.util.http.RequestHttp; import me.chanjar.weixin.common.util.http.SimplePostRequestExecutor; @@ -40,21 +39,7 @@ public String execute(String uri, String postEntity, WxType wxType) throws WxErr HttpResponse response = request.send(); response.charset(StringPool.UTF_8); - String responseContent = response.bodyText(); - if (responseContent.isEmpty()) { - throw new WxErrorException(WxError.builder().errorCode(9999).errorMsg("无响应内容").build()); - } - - if (responseContent.startsWith("")) { - //xml格式输出直接返回 - return responseContent; - } - - WxError error = WxError.fromJson(responseContent, wxType); - if (error.getErrorCode() != 0) { - throw new WxErrorException(error); - } - return responseContent; + return this.handleResponse(wxType, response.bodyText()); } } diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/okhttp/OkHttpMediaDownloadRequestExecutor.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/okhttp/OkHttpMediaDownloadRequestExecutor.java index 729f1e186f..86710ccced 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/okhttp/OkHttpMediaDownloadRequestExecutor.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/okhttp/OkHttpMediaDownloadRequestExecutor.java @@ -14,8 +14,6 @@ import okio.Okio; import org.apache.commons.io.FilenameUtils; import org.apache.commons.lang3.StringUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import java.io.File; import java.io.IOException; @@ -58,8 +56,13 @@ public File execute(String uri, String queryParam, WxType wxType) throws WxError return null; } + String baseName = FilenameUtils.getBaseName(fileName); + if (StringUtils.isBlank(fileName) || baseName.length() < 3) { + baseName = String.valueOf(System.currentTimeMillis()); + } + File file = File.createTempFile( - FilenameUtils.getBaseName(fileName), "." + FilenameUtils.getExtension(fileName), super.tmpDirFile + baseName, "." + FilenameUtils.getExtension(fileName), super.tmpDirFile ); try (BufferedSink sink = Okio.buffer(Okio.sink(file))) { diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/okhttp/OkHttpSimpleGetRequestExecutor.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/okhttp/OkHttpSimpleGetRequestExecutor.java index dbaa27c544..09012692f5 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/okhttp/OkHttpSimpleGetRequestExecutor.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/okhttp/OkHttpSimpleGetRequestExecutor.java @@ -35,12 +35,7 @@ public String execute(String uri, String queryParam, WxType wxType) throws WxErr OkHttpClient client = requestHttp.getRequestHttpClient(); Request request = new Request.Builder().url(uri).build(); Response response = client.newCall(request).execute(); - String responseContent = response.body().string(); - WxError error = WxError.fromJson(responseContent, wxType); - if (error.getErrorCode() != 0) { - throw new WxErrorException(error); - } - return responseContent; + return this.handleResponse(wxType, response.body().string()); } } diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/okhttp/OkHttpSimplePostRequestExecutor.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/okhttp/OkHttpSimplePostRequestExecutor.java index 3be6152055..d4351b87ac 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/okhttp/OkHttpSimplePostRequestExecutor.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/okhttp/OkHttpSimplePostRequestExecutor.java @@ -2,13 +2,13 @@ import lombok.extern.slf4j.Slf4j; import me.chanjar.weixin.common.WxType; -import me.chanjar.weixin.common.error.WxError; import me.chanjar.weixin.common.error.WxErrorException; import me.chanjar.weixin.common.util.http.RequestHttp; import me.chanjar.weixin.common.util.http.SimplePostRequestExecutor; import okhttp3.*; import java.io.IOException; +import java.util.Objects; /** * . @@ -24,16 +24,10 @@ public OkHttpSimplePostRequestExecutor(RequestHttp requestHttp) { @Override public String execute(String uri, String postEntity, WxType wxType) throws WxErrorException, IOException { - RequestBody body = RequestBody.create(MediaType.parse("text/plain; charset=utf-8"), postEntity); + RequestBody body = RequestBody.Companion.create(postEntity, MediaType.parse("text/plain; charset=utf-8")); Request request = new Request.Builder().url(uri).post(body).build(); Response response = requestHttp.getRequestHttpClient().newCall(request).execute(); - String responseContent = response.body().string(); - WxError error = WxError.fromJson(responseContent, wxType); - if (error.getErrorCode() != 0) { - throw new WxErrorException(error); - } - - return responseContent; + return this.handleResponse(wxType, Objects.requireNonNull(response.body()).string()); } } diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/locks/JedisDistributedLock.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/locks/JedisDistributedLock.java new file mode 100644 index 0000000000..b136a4c25a --- /dev/null +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/locks/JedisDistributedLock.java @@ -0,0 +1,73 @@ +package me.chanjar.weixin.common.util.locks; + +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.Lock; + +import com.github.jedis.lock.JedisLock; +import redis.clients.jedis.Jedis; +import redis.clients.util.Pool; + +/** + * JedisPool 分布式锁 + * + * @author 007 + */ +public class JedisDistributedLock implements Lock { + private final Pool jedisPool; + private final JedisLock lock; + + public JedisDistributedLock(Pool jedisPool, String key){ + this.jedisPool = jedisPool; + this.lock = new JedisLock(key); + } + + @Override + public void lock() { + try (Jedis jedis = jedisPool.getResource()) { + if (!lock.acquire(jedis)) { + throw new RuntimeException("acquire timeouted"); + } + } catch (InterruptedException e) { + throw new RuntimeException("lock failed", e); + } + } + + @Override + public void lockInterruptibly() throws InterruptedException { + try (Jedis jedis = jedisPool.getResource()) { + if (!lock.acquire(jedis)) { + throw new RuntimeException("acquire timeouted"); + } + } + } + + @Override + public boolean tryLock() { + try (Jedis jedis = jedisPool.getResource()) { + return lock.acquire(jedis); + } catch (InterruptedException e) { + throw new RuntimeException("lock failed", e); + } + } + + @Override + public boolean tryLock(long time, TimeUnit unit) throws InterruptedException { + try (Jedis jedis = jedisPool.getResource()) { + return lock.acquire(jedis); + } + } + + @Override + public void unlock() { + try (Jedis jedis = jedisPool.getResource()) { + lock.release(jedis); + } + } + + @Override + public Condition newCondition() { + throw new RuntimeException("unsupported method"); + } + +} diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/xml/XStreamInitializer.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/xml/XStreamInitializer.java index 639fcf08d1..5fd7ceb2cb 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/xml/XStreamInitializer.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/xml/XStreamInitializer.java @@ -1,13 +1,18 @@ package me.chanjar.weixin.common.util.xml; -import java.io.Writer; - import com.thoughtworks.xstream.XStream; +import com.thoughtworks.xstream.converters.basic.*; +import com.thoughtworks.xstream.converters.collections.CollectionConverter; import com.thoughtworks.xstream.converters.reflection.PureJavaReflectionProvider; +import com.thoughtworks.xstream.converters.reflection.ReflectionConverter; import com.thoughtworks.xstream.core.util.QuickWriter; import com.thoughtworks.xstream.io.HierarchicalStreamWriter; import com.thoughtworks.xstream.io.xml.PrettyPrintWriter; import com.thoughtworks.xstream.io.xml.XppDriver; +import com.thoughtworks.xstream.security.NoTypePermission; +import com.thoughtworks.xstream.security.WildcardTypePermission; + +import java.io.Writer; public class XStreamInitializer { private static final XppDriver XPP_DRIVER = new XppDriver() { @@ -41,14 +46,34 @@ public String encodeNode(String name) { }; public static XStream getInstance() { - XStream xstream = new XStream(new PureJavaReflectionProvider(), XPP_DRIVER); + XStream xstream = new XStream(new PureJavaReflectionProvider(), XPP_DRIVER) { + // only register the converters we need; other converters generate a private access warning in the console on Java9+... + @Override + protected void setupConverters() { + registerConverter(new NullConverter(), PRIORITY_VERY_HIGH); + registerConverter(new IntConverter(), PRIORITY_NORMAL); + registerConverter(new FloatConverter(), PRIORITY_NORMAL); + registerConverter(new DoubleConverter(), PRIORITY_NORMAL); + registerConverter(new LongConverter(), PRIORITY_NORMAL); + registerConverter(new ShortConverter(), PRIORITY_NORMAL); + registerConverter(new BooleanConverter(), PRIORITY_NORMAL); + registerConverter(new ByteConverter(), PRIORITY_NORMAL); + registerConverter(new StringConverter(), PRIORITY_NORMAL); + registerConverter(new DateConverter(), PRIORITY_NORMAL); + registerConverter(new CollectionConverter(getMapper()), PRIORITY_NORMAL); + registerConverter(new ReflectionConverter(getMapper(), getReflectionProvider()), PRIORITY_VERY_LOW); + } + }; xstream.ignoreUnknownElements(); xstream.setMode(XStream.NO_REFERENCES); XStream.setupDefaultSecurity(xstream); - xstream.allowTypesByWildcard(new String[]{ - "me.chanjar.weixin.**", "cn.binarywang.wx.**", "com.github.binarywang.**" - }); + xstream.autodetectAnnotations(true); + // setup proper security by limiting which classes can be loaded by XStream + xstream.addPermission(NoTypePermission.NONE); + xstream.addPermission(new WildcardTypePermission(new String[]{ + "me.chanjar.weixin.**", "cn.binarywang.wx.**", "com.github.binarywang.**" + })); xstream.setClassLoader(Thread.currentThread().getContextClassLoader()); return xstream; } diff --git a/weixin-java-common/src/test/java/me/chanjar/weixin/common/redis/CommonWxRedisOpsTest.java b/weixin-java-common/src/test/java/me/chanjar/weixin/common/redis/CommonWxRedisOpsTest.java new file mode 100644 index 0000000000..96ba20ba2b --- /dev/null +++ b/weixin-java-common/src/test/java/me/chanjar/weixin/common/redis/CommonWxRedisOpsTest.java @@ -0,0 +1,51 @@ +package me.chanjar.weixin.common.redis; + +import org.testng.Assert; +import org.testng.annotations.Test; + +import java.util.concurrent.TimeUnit; + +public class CommonWxRedisOpsTest { + + protected WxRedisOps wxRedisOps; + private String key = "access_token"; + private String value = String.valueOf(System.currentTimeMillis()); + + @Test + public void testGetValue() { + wxRedisOps.setValue(key, value, 3, TimeUnit.SECONDS); + Assert.assertEquals(wxRedisOps.getValue(key), value); + } + + @Test + public void testSetValue() { + String key = "access_token", value = String.valueOf(System.currentTimeMillis()); + wxRedisOps.setValue(key, value, -1, TimeUnit.SECONDS); + wxRedisOps.setValue(key, value, 0, TimeUnit.SECONDS); + wxRedisOps.setValue(key, value, 1, TimeUnit.SECONDS); + } + + @Test + public void testGetExpire() { + String key = "access_token", value = String.valueOf(System.currentTimeMillis()); + wxRedisOps.setValue(key, value, -1, TimeUnit.SECONDS); + Assert.assertTrue(wxRedisOps.getExpire(key) < 0); + wxRedisOps.setValue(key, value, 4, TimeUnit.SECONDS); + Long expireSeconds = wxRedisOps.getExpire(key); + Assert.assertTrue(expireSeconds <= 4 && expireSeconds >= 0); + } + + @Test + public void testExpire() { + String key = "access_token", value = String.valueOf(System.currentTimeMillis()); + wxRedisOps.setValue(key, value, -1, TimeUnit.SECONDS); + wxRedisOps.expire(key, 4, TimeUnit.SECONDS); + Long expireSeconds = wxRedisOps.getExpire(key); + Assert.assertTrue(expireSeconds <= 4 && expireSeconds >= 0); + } + + @Test + public void testGetLock() { + Assert.assertNotNull(wxRedisOps.getLock("access_token_lock")); + } +} diff --git a/weixin-java-common/src/test/java/me/chanjar/weixin/common/redis/JedisWxRedisOpsTest.java b/weixin-java-common/src/test/java/me/chanjar/weixin/common/redis/JedisWxRedisOpsTest.java new file mode 100644 index 0000000000..2ff2c37b81 --- /dev/null +++ b/weixin-java-common/src/test/java/me/chanjar/weixin/common/redis/JedisWxRedisOpsTest.java @@ -0,0 +1,21 @@ +package me.chanjar.weixin.common.redis; + +import org.testng.annotations.AfterTest; +import org.testng.annotations.BeforeTest; +import redis.clients.jedis.JedisPool; + +public class JedisWxRedisOpsTest extends CommonWxRedisOpsTest { + + JedisPool jedisPool; + + @BeforeTest + public void init() { + this.jedisPool = new JedisPool("127.0.0.1", 6379); + this.wxRedisOps = new JedisWxRedisOps(jedisPool); + } + + @AfterTest + public void destroy() { + this.jedisPool.close(); + } +} diff --git a/weixin-java-common/src/test/java/me/chanjar/weixin/common/redis/RedisTemplateWxRedisOpsTest.java b/weixin-java-common/src/test/java/me/chanjar/weixin/common/redis/RedisTemplateWxRedisOpsTest.java new file mode 100644 index 0000000000..bf3b35a7cc --- /dev/null +++ b/weixin-java-common/src/test/java/me/chanjar/weixin/common/redis/RedisTemplateWxRedisOpsTest.java @@ -0,0 +1,26 @@ +package me.chanjar.weixin.common.redis; + +import org.springframework.data.redis.connection.jedis.JedisConnectionFactory; +import org.springframework.data.redis.core.StringRedisTemplate; +import org.testng.annotations.AfterTest; +import org.testng.annotations.BeforeTest; + +public class RedisTemplateWxRedisOpsTest extends CommonWxRedisOpsTest { + + StringRedisTemplate redisTemplate; + + @BeforeTest + public void init() { + JedisConnectionFactory connectionFactory = new JedisConnectionFactory(); + connectionFactory.setHostName("127.0.0.1"); + connectionFactory.setPort(6379); + connectionFactory.afterPropertiesSet(); + StringRedisTemplate redisTemplate = new StringRedisTemplate(connectionFactory); + this.redisTemplate = redisTemplate; + this.wxRedisOps = new RedisTemplateWxRedisOps(this.redisTemplate); + } + + @AfterTest + public void destroy() { + } +} diff --git a/weixin-java-common/src/test/java/me/chanjar/weixin/common/redis/RedissonWxRedisOpsTest.java b/weixin-java-common/src/test/java/me/chanjar/weixin/common/redis/RedissonWxRedisOpsTest.java new file mode 100644 index 0000000000..48cf7b29be --- /dev/null +++ b/weixin-java-common/src/test/java/me/chanjar/weixin/common/redis/RedissonWxRedisOpsTest.java @@ -0,0 +1,27 @@ +package me.chanjar.weixin.common.redis; + +import org.redisson.Redisson; +import org.redisson.api.RedissonClient; +import org.redisson.config.Config; +import org.redisson.config.TransportMode; +import org.testng.annotations.AfterTest; +import org.testng.annotations.BeforeTest; + +public class RedissonWxRedisOpsTest extends CommonWxRedisOpsTest { + + RedissonClient redissonClient; + + @BeforeTest + public void init() { + Config config = new Config(); + config.useSingleServer().setAddress("redis://127.0.0.1:6379"); + config.setTransportMode(TransportMode.NIO); + this.redissonClient = Redisson.create(config); + this.wxRedisOps = new RedissonWxRedisOps(this.redissonClient); + } + + @AfterTest + public void destroy() { + this.redissonClient.shutdown(); + } +} diff --git a/weixin-java-common/src/test/java/me/chanjar/weixin/common/util/XmlUtilsTest.java b/weixin-java-common/src/test/java/me/chanjar/weixin/common/util/XmlUtilsTest.java index 1afd1c1d9c..7b6bb536f4 100644 --- a/weixin-java-common/src/test/java/me/chanjar/weixin/common/util/XmlUtilsTest.java +++ b/weixin-java-common/src/test/java/me/chanjar/weixin/common/util/XmlUtilsTest.java @@ -1,10 +1,10 @@ package me.chanjar.weixin.common.util; +import org.testng.annotations.Test; + import java.util.List; import java.util.Map; -import org.testng.annotations.*; - import static org.assertj.core.api.Assertions.assertThat; /** @@ -17,6 +17,17 @@ */ public class XmlUtilsTest { + @Test(expectedExceptions = {RuntimeException.class}) + public void testXml2Map_xxe() { + String xml = "\n" + + "\n" + + "\n" + + "]>\n" + + " "; + XmlUtils.xml2Map(xml); + } + @Test public void testXml2Map() { String xml = " \n" + diff --git a/weixin-java-cp/pom.xml b/weixin-java-cp/pom.xml index 5892d6a3d6..cbd9be4d40 100644 --- a/weixin-java-cp/pom.xml +++ b/weixin-java-cp/pom.xml @@ -7,7 +7,7 @@ com.github.binarywang wx-java -3.7.0 +3.8.0 weixin-java-cp @@ -38,7 +38,11 @@org.slf4j slf4j-api - + ++ org.redisson +redisson +org.testng testng diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpExternalContactService.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpExternalContactService.java index df5326f249..5aa2336a71 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpExternalContactService.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpExternalContactService.java @@ -1,8 +1,10 @@ package me.chanjar.weixin.cp.api; import me.chanjar.weixin.common.error.WxErrorException; -import me.chanjar.weixin.cp.bean.WxCpUserExternalContactInfo; +import me.chanjar.weixin.cp.bean.*; +import java.util.Calendar; +import java.util.Date; import java.util.List; /** @@ -83,6 +85,81 @@ public interface WxCpExternalContactService { * @return List of CpUser id * @throws WxErrorException . */ - ListlistFollowUser() throws WxErrorException; + List listFollowers() throws WxErrorException; + /** + * 企业和第三方可通过此接口,获取所有离职成员的客户列表,并可进一步调用离职成员的外部联系人再分配接口将这些客户重新分配给其他企业成员。 + * @param page + * @param pageSize + * @return + * @throws WxErrorException + */ + WxCpUserExternalUnassignList listUnassignedList(Integer page, Integer pageSize) throws WxErrorException; + + /** + * 企业可通过此接口,将已离职成员的外部联系人分配给另一个成员接替联系。 + * @param externalUserid + * @param handOverUserid + * @param takeOverUserid + * @return + * @throws WxErrorException + */ + WxCpBaseResp transferExternalContact(String externalUserid,String handOverUserid,String takeOverUserid)throws WxErrorException; + + /** + * 该接口用于获取配置过客户群管理的客户群列表。 + * 企业需要使用“客户联系”secret或配置到“可调用应用”列表中的自建应用secret所获取的accesstoken来调用(accesstoken如何获取?)。 + * 暂不支持第三方调用。 + * 微信文档:https://work.weixin.qq.com/api/doc/90000/90135/92119 + *+ */ + WxCpUserExternalGroupChatList listGroupChat(Integer pageIndex,Integer pageSize,int status,String[] userIds,String[] partyIds) throws WxErrorException; + + /** + *+ * 通过客户群ID,获取详情。包括群名、群成员列表、群成员入群时间、入群方式。(客户群是由具有客户群使用权限的成员创建的外部群) + * 企业需要使用“客户联系”secret或配置到“可调用应用”列表中的自建应用secret所获取的accesstoken来调用(accesstoken如何获取?)。 + * 暂不支持第三方调用。 + * 微信文档:https://work.weixin.qq.com/api/doc/90000/90135/92122 + *+ * + * @param chatId + * @return + * @throws WxErrorException + */ + WxCpUserExternalGroupChatInfo getGroupChat(String chatId) throws WxErrorException; + + /** + *+ * 企业可通过此接口获取成员联系客户的数据,包括发起申请数、新增客户数、聊天数、发送消息数和删除/拉黑成员的客户数等指标。 + * 企业需要使用“客户联系”secret或配置到“可调用应用”列表中的自建应用secret所获取的accesstoken来调用(accesstoken如何获取?)。 + * 第三方应用需拥有“企业客户”权限。 + * 第三方/自建应用调用时传入的userid和partyid要在应用的可见范围内; + *+ * @param startTime + * @param endTime + * @param userIds + * @param partyIds + * @return + * @throws WxErrorException + */ + WxCpUserExternalUserBehaviorStatistic getUserBehaviorStatistic(Date startTime, Date endTime, String[] userIds, String[] partyIds) throws WxErrorException; + + /** + *+ * 获取指定日期全天的统计数据。注意,企业微信仅存储60天的数据。 + * 企业需要使用“客户联系”secret或配置到“可调用应用”列表中的自建应用secret所获取的accesstoken来调用(accesstoken如何获取?)。 + * 暂不支持第三方调用。 + *+ * @param startTime + * @param orderBy + * @param orderAsc + * @param pageIndex + * @param pageSize + * @param userIds + * @param partyIds + * @return + * @throws WxErrorException + */ + WxCpUserExternalGroupChatStatistic getGroupChatStatistic(Date startTime,Integer orderBy,Integer orderAsc,Integer pageIndex,Integer pageSize, String[] userIds, String[] partyIds) throws WxErrorException; } diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpTpService.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpTpService.java index edc2f552ce..ad2f403af6 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpTpService.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpTpService.java @@ -6,7 +6,9 @@ import me.chanjar.weixin.common.util.http.RequestExecutor; import me.chanjar.weixin.common.util.http.RequestHttp; import me.chanjar.weixin.cp.bean.WxCpMaJsCode2SessionResult; +import me.chanjar.weixin.cp.bean.WxCpTpAuthInfo; import me.chanjar.weixin.cp.bean.WxCpTpCorp; +import me.chanjar.weixin.cp.bean.WxCpTpPermanentCodeInfo; import me.chanjar.weixin.cp.config.WxCpTpConfigStorage; /** @@ -88,8 +90,46 @@ public interface WxCpTpService { * @param authCode . * @return . */ + @Deprecated WxCpTpCorp getPermanentCode(String authCode) throws WxErrorException; + /** + * 获取企业永久授权码信息 + *+ * 原来的方法实现不全 + *+ * + * @param authCode + * @return + * + * @author yuan + * @since 2020-03-18 + * + * @throws WxErrorException + */ + WxCpTpPermanentCodeInfo getPermanentCodeInfo(String authCode) throws WxErrorException; + + /** + *+ * 获取预授权链接 + *+ * @param redirectUri 授权完成后的回调网址 + * @param state a-zA-Z0-9的参数值(不超过128个字节),用于第三方自行校验session,防止跨域攻击 + * @return + * @throws WxErrorException + */ + String getPreAuthUrl(String redirectUri,String state) throws WxErrorException; + + /** + * 获取企业的授权信息 + * + * @param authCorpId 授权企业的corpId + * @param permanentCode 授权企业的永久授权码 + * @return + * @throws WxErrorException + */ + WxCpTpAuthInfo getAuthInfo(String authCorpId,String permanentCode) throws WxErrorException; + /** * 当本Service没有实现某个API的时候,可以用这个,针对所有微信API中的GET请求. * diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/BaseWxCpTpServiceImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/BaseWxCpTpServiceImpl.java index dea5428c1b..dea647f3f9 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/BaseWxCpTpServiceImpl.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/BaseWxCpTpServiceImpl.java @@ -3,6 +3,7 @@ import com.google.common.base.Joiner; import com.google.gson.JsonObject; import com.google.gson.JsonParser; +import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; import me.chanjar.weixin.common.WxType; import me.chanjar.weixin.common.bean.WxAccessToken; @@ -15,12 +16,13 @@ import me.chanjar.weixin.common.util.http.SimpleGetRequestExecutor; import me.chanjar.weixin.common.util.http.SimplePostRequestExecutor; import me.chanjar.weixin.cp.api.WxCpTpService; -import me.chanjar.weixin.cp.bean.WxCpMaJsCode2SessionResult; -import me.chanjar.weixin.cp.bean.WxCpTpCorp; +import me.chanjar.weixin.cp.bean.*; import me.chanjar.weixin.cp.config.WxCpTpConfigStorage; +import org.apache.commons.lang3.StringUtils; import java.io.File; import java.io.IOException; +import java.net.URLEncoder; import java.util.HashMap; import java.util.Map; @@ -123,6 +125,35 @@ public WxCpTpCorp getPermanentCode(String authCode) throws WxErrorException { return wxCpTpCorp; } + @Override + public WxCpTpPermanentCodeInfo getPermanentCodeInfo(String authCode) throws WxErrorException{ + JsonObject jsonObject = new JsonObject(); + jsonObject.addProperty("auth_code", authCode); + String result = post(configStorage.getApiUrl(GET_PERMANENT_CODE), jsonObject.toString()); + return WxCpTpPermanentCodeInfo.fromJson(result); + } + + @Override + @SneakyThrows + public String getPreAuthUrl(String redirectUri,String state) throws WxErrorException{ + String result = get(configStorage.getApiUrl(GET_PREAUTH_CODE),null); + WxCpTpPreauthCode preauthCode = WxCpTpPreauthCode.fromJson(result); + String preAuthUrl = "https://open.work.weixin.qq.com/3rdapp/install?suite_id="+configStorage.getSuiteId()+ + "&pre_auth_code="+preauthCode.getPreAuthCode()+"&redirect_uri="+ URLEncoder.encode(redirectUri,"utf-8"); + if(StringUtils.isNotBlank(state)) + preAuthUrl += "&state="+state; + return preAuthUrl; + } + + @Override + public WxCpTpAuthInfo getAuthInfo(String authCorpId, String permanentCode) throws WxErrorException{ + JsonObject jsonObject = new JsonObject(); + jsonObject.addProperty("auth_corpid", authCorpId); + jsonObject.addProperty("permanent_code", permanentCode); + String result = post(configStorage.getApiUrl(GET_AUTH_INFO), jsonObject.toString()); + return WxCpTpAuthInfo.fromJson(result); + } + @Override public String get(String url, String queryParam) throws WxErrorException { return execute(SimpleGetRequestExecutor.create(this), url, queryParam); diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpExternalContactServiceImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpExternalContactServiceImpl.java index 044b1e5d49..044155847e 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpExternalContactServiceImpl.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpExternalContactServiceImpl.java @@ -1,22 +1,27 @@ package me.chanjar.weixin.cp.api.impl; +import com.google.gson.Gson; +import com.google.gson.JsonObject; +import lombok.RequiredArgsConstructor; +import me.chanjar.weixin.common.error.WxCpErrorMsgEnum; import me.chanjar.weixin.common.error.WxErrorException; import me.chanjar.weixin.cp.api.WxCpExternalContactService; import me.chanjar.weixin.cp.api.WxCpService; -import me.chanjar.weixin.cp.bean.WxCpUserExternalContactInfo; -import me.chanjar.weixin.cp.bean.WxCpUserExternalContactList; -import me.chanjar.weixin.cp.bean.WxCpUserWithExternalPermission; +import me.chanjar.weixin.cp.bean.*; +import org.apache.commons.lang3.ArrayUtils; +import java.util.Collections; +import java.util.Date; import java.util.List; import static me.chanjar.weixin.cp.constant.WxCpApiPathConsts.ExternalContact.*; +/** + * @author 曹祖鹏 & yuanqixun + */ +@RequiredArgsConstructor public class WxCpExternalContactServiceImpl implements WxCpExternalContactService { - private WxCpService mainService; - - public WxCpExternalContactServiceImpl(WxCpService mainService) { - this.mainService = mainService; - } + private final WxCpService mainService; @Override public WxCpUserExternalContactInfo getExternalContact(String userId) throws WxErrorException { @@ -35,14 +40,114 @@ public WxCpUserExternalContactInfo getContactDetail(String userId) throws WxErro @Override public ListlistExternalContacts(String userId) throws WxErrorException { final String url = this.mainService.getWxCpConfigStorage().getApiUrl(LIST_EXTERNAL_CONTACT + userId); - String responseContent = this.mainService.get(url, null); - return WxCpUserExternalContactList.fromJson(responseContent).getExternalUserId(); + try { + String responseContent = this.mainService.get(url, null); + return WxCpUserExternalContactList.fromJson(responseContent).getExternalUserId(); + } catch (WxErrorException e) { + // not external contact,无客户则返回空列表 + if (e.getError().getErrorCode() == WxCpErrorMsgEnum.CODE_84061.getCode()) { + return Collections.emptyList(); + } + throw e; + } } @Override - public List listFollowUser() throws WxErrorException { + public List listFollowers() throws WxErrorException { final String url = this.mainService.getWxCpConfigStorage().getApiUrl(GET_FOLLOW_USER_LIST); String responseContent = this.mainService.get(url, null); - return WxCpUserWithExternalPermission.fromJson(responseContent).getFollowUser(); + return WxCpUserWithExternalPermission.fromJson(responseContent).getFollowers(); + } + + @Override + public WxCpUserExternalUnassignList listUnassignedList(Integer pageIndex, Integer pageSize) throws WxErrorException { + JsonObject json = new JsonObject(); + json.addProperty("page_id", pageIndex == null ? 0 : pageIndex); + json.addProperty("page_size", pageSize == null ? 100 : pageSize); + final String url = this.mainService.getWxCpConfigStorage().getApiUrl(LIST_UNASSIGNED_CONTACT); + final String result = this.mainService.post(url, json.toString()); + return WxCpUserExternalUnassignList.fromJson(result); + } + + @Override + public WxCpBaseResp transferExternalContact(String externalUserid, String handOverUserid, String takeOverUserid) throws WxErrorException { + JsonObject json = new JsonObject(); + json.addProperty("external_userid", externalUserid); + json.addProperty("handover_userid", handOverUserid); + json.addProperty("takeover_userid", takeOverUserid); + final String url = this.mainService.getWxCpConfigStorage().getApiUrl(TRANSFER_UNASSIGNED_CONTACT); + final String result = this.mainService.post(url, json.toString()); + return WxCpBaseResp.fromJson(result); + } + + @Override + public WxCpUserExternalGroupChatList listGroupChat(Integer pageIndex, Integer pageSize, int status, String[] userIds, String[] partyIds) throws WxErrorException { + JsonObject json = new JsonObject(); + json.addProperty("offset", pageIndex == null ? 0 : pageIndex); + json.addProperty("limit", pageSize == null ? 100 : pageSize); + json.addProperty("status_filter", status); + if (ArrayUtils.isNotEmpty(userIds) || ArrayUtils.isNotEmpty(partyIds)) { + JsonObject ownerFilter = new JsonObject(); + if (ArrayUtils.isNotEmpty(userIds)) { + json.add("userid", new Gson().toJsonTree(userIds).getAsJsonArray()); + } + if (ArrayUtils.isNotEmpty(partyIds)) { + json.add("partyid", new Gson().toJsonTree(partyIds).getAsJsonArray()); + } + json.add("owner_filter", ownerFilter); + } + final String url = this.mainService.getWxCpConfigStorage().getApiUrl(GROUP_CHAT_LIST); + final String result = this.mainService.post(url, json.toString()); + return WxCpUserExternalGroupChatList.fromJson(result); + } + + @Override + public WxCpUserExternalGroupChatInfo getGroupChat(String chatId) throws WxErrorException { + JsonObject json = new JsonObject(); + json.addProperty("chat_id", chatId); + final String url = this.mainService.getWxCpConfigStorage().getApiUrl(GROUP_CHAT_INFO); + final String result = this.mainService.post(url, json.toString()); + return WxCpUserExternalGroupChatInfo.fromJson(result); + } + + @Override + public WxCpUserExternalUserBehaviorStatistic getUserBehaviorStatistic(Date startTime, Date endTime, String[] userIds, String[] partyIds) throws WxErrorException { + JsonObject json = new JsonObject(); + json.addProperty("start_time", startTime.getTime() / 1000); + json.addProperty("end_time", endTime.getTime() / 1000); + if (ArrayUtils.isNotEmpty(userIds) || ArrayUtils.isNotEmpty(partyIds)) { + if (ArrayUtils.isNotEmpty(userIds)) { + json.add("userid", new Gson().toJsonTree(userIds).getAsJsonArray()); + } + if (ArrayUtils.isNotEmpty(partyIds)) { + json.add("partyid", new Gson().toJsonTree(partyIds).getAsJsonArray()); + } + } + final String url = this.mainService.getWxCpConfigStorage().getApiUrl(LIST_USER_BEHAVIOR_DATA); + final String result = this.mainService.post(url, json.toString()); + return WxCpUserExternalUserBehaviorStatistic.fromJson(result); + } + + @Override + public WxCpUserExternalGroupChatStatistic getGroupChatStatistic(Date startTime, Integer orderBy, Integer orderAsc, Integer pageIndex, Integer pageSize, String[] userIds, String[] partyIds) throws WxErrorException { + JsonObject json = new JsonObject(); + json.addProperty("day_begin_time", startTime.getTime() / 1000); + json.addProperty("order_by", orderBy == null ? 1 : orderBy); + json.addProperty("order_asc", orderAsc == null ? 0 : orderAsc); + json.addProperty("offset", pageIndex == null ? 0 : pageIndex); + json.addProperty("limit", pageSize == null ? 500 : pageSize); + if (ArrayUtils.isNotEmpty(userIds) || ArrayUtils.isNotEmpty(partyIds)) { + JsonObject ownerFilter = new JsonObject(); + if (ArrayUtils.isNotEmpty(userIds)) { + json.add("userid_list", new Gson().toJsonTree(userIds).getAsJsonArray()); + } + if (ArrayUtils.isNotEmpty(partyIds)) { + json.add("userid_list", new Gson().toJsonTree(partyIds).getAsJsonArray()); + } + json.add("owner_filter", ownerFilter); + } + final String url = this.mainService.getWxCpConfigStorage().getApiUrl(LIST_GROUP_CHAT_DATA); + final String result = this.mainService.post(url, json.toString()); + return WxCpUserExternalGroupChatStatistic.fromJson(result); } } diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpServiceImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpServiceImpl.java index a7538c6ead..016c880fd1 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpServiceImpl.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpServiceImpl.java @@ -1,12 +1,125 @@ package me.chanjar.weixin.cp.api.impl; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import me.chanjar.weixin.common.WxType; +import me.chanjar.weixin.common.bean.WxAccessToken; +import me.chanjar.weixin.common.error.WxError; +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.cp.constant.WxCpApiPathConsts; +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 java.io.IOException; +import java.util.concurrent.locks.Lock; + +import static me.chanjar.weixin.cp.constant.WxCpApiPathConsts.GET_AGENT_CONFIG_TICKET; +import static me.chanjar.weixin.cp.constant.WxCpApiPathConsts.GET_JSAPI_TICKET; + /** * * 默认接口实现类,使用apache httpclient实现 * Created by Binary Wang on 2017-5-27. *+ *+ * 增加分布式锁(基于WxCpConfigStorage实现)的支持 + * Updated by yuanqixun on 2020-05-13 + *+ * * * @author Binary Wang */ public class WxCpServiceImpl extends WxCpServiceApacheHttpClientImpl { + @Override + public String getAccessToken(boolean forceRefresh) throws WxErrorException { + if (!getWxCpConfigStorage().isAccessTokenExpired() && !forceRefresh) { + return getWxCpConfigStorage().getAccessToken(); + } + Lock lock = getWxCpConfigStorage().getAccessTokenLock(); + lock.lock(); + try { + // 拿到锁之后,再次判断一下最新的token是否过期,避免重刷 + if (!getWxCpConfigStorage().isAccessTokenExpired() && !forceRefresh) { + return getWxCpConfigStorage().getAccessToken(); + } + String url = String.format(getWxCpConfigStorage().getApiUrl(WxCpApiPathConsts.GET_TOKEN), this.configStorage.getCorpId(), this.configStorage.getCorpSecret()); + try { + HttpGet httpGet = new HttpGet(url); + if (getRequestHttpProxy() != null) { + RequestConfig config = RequestConfig.custom() + .setProxy(getRequestHttpProxy()).build(); + httpGet.setConfig(config); + } + String resultContent; + try (CloseableHttpClient httpClient = getRequestHttpClient(); + CloseableHttpResponse response = httpClient.execute(httpGet)) { + resultContent = new BasicResponseHandler().handleResponse(response); + } finally { + httpGet.releaseConnection(); + } + WxError error = WxError.fromJson(resultContent, WxType.CP); + if (error.getErrorCode() != 0) { + throw new WxErrorException(error); + } + + WxAccessToken accessToken = WxAccessToken.fromJson(resultContent); + getWxCpConfigStorage().updateAccessToken(accessToken.getAccessToken(), accessToken.getExpiresIn()); + } catch (IOException e) { + throw new RuntimeException(e); + } + } finally { + lock.unlock(); + } + return getWxCpConfigStorage().getAccessToken(); + } + + @Override + public String getAgentJsapiTicket(boolean forceRefresh) throws WxErrorException { + if (forceRefresh) { + getWxCpConfigStorage().expireAgentJsapiTicket(); + } + if (getWxCpConfigStorage().isAgentJsapiTicketExpired()) { + Lock lock = getWxCpConfigStorage().getAgentJsapiTicketLock(); + lock.lock(); + try { + // 拿到锁之后,再次判断一下最新的token是否过期,避免重刷 + if (getWxCpConfigStorage().isAgentJsapiTicketExpired()) { + String responseContent = this.get(getWxCpConfigStorage().getApiUrl(GET_AGENT_CONFIG_TICKET), null); + JsonObject jsonObject = new JsonParser().parse(responseContent).getAsJsonObject(); + getWxCpConfigStorage().updateAgentJsapiTicket(jsonObject.get("ticket").getAsString(), + jsonObject.get("expires_in").getAsInt()); + } + } finally { + lock.unlock(); + } + } + return getWxCpConfigStorage().getAgentJsapiTicket(); + } + + @Override + public String getJsapiTicket(boolean forceRefresh) throws WxErrorException { + if (forceRefresh) { + getWxCpConfigStorage().expireJsapiTicket(); + } + + if (getWxCpConfigStorage().isJsapiTicketExpired()) { + Lock lock = getWxCpConfigStorage().getJsapiTicketLock(); + lock.lock(); + try { + // 拿到锁之后,再次判断一下最新的token是否过期,避免重刷 + if (getWxCpConfigStorage().isJsapiTicketExpired()) { + String responseContent = this.get(getWxCpConfigStorage().getApiUrl(GET_JSAPI_TICKET), null); + JsonObject tmpJsonObject = new JsonParser().parse(responseContent).getAsJsonObject(); + getWxCpConfigStorage().updateJsapiTicket(tmpJsonObject.get("ticket").getAsString(), + tmpJsonObject.get("expires_in").getAsInt()); + } + } finally { + lock.unlock(); + } + } + return getWxCpConfigStorage().getJsapiTicket(); + } } diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpServiceJoddHttpImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpServiceJoddHttpImpl.java index cc76e9cf4c..6aff7ed530 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpServiceJoddHttpImpl.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpServiceJoddHttpImpl.java @@ -3,8 +3,8 @@ import jodd.http.HttpConnectionProvider; import jodd.http.HttpRequest; import jodd.http.HttpResponse; -import jodd.http.JoddHttp; import jodd.http.ProxyInfo; +import jodd.http.net.SocketHttpConnectionProvider; import me.chanjar.weixin.common.WxType; import me.chanjar.weixin.common.bean.WxAccessToken; import me.chanjar.weixin.common.error.WxError; @@ -68,7 +68,7 @@ public void initHttp() { configStorage.getHttpProxyPort(), configStorage.getHttpProxyUsername(), configStorage.getHttpProxyPassword()); } - httpClient = JoddHttp.httpConnectionProvider; + httpClient = new SocketHttpConnectionProvider(); } @Override diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpBaseResp.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpBaseResp.java new file mode 100644 index 0000000000..e94110a055 --- /dev/null +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpBaseResp.java @@ -0,0 +1,28 @@ +package me.chanjar.weixin.cp.bean; + +import com.google.gson.annotations.SerializedName; +import lombok.Getter; +import lombok.Setter; +import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder; + +/** + * @author yqx + * @date 2020/3/16 + */ +@Getter +@Setter +public class WxCpBaseResp { + @SerializedName("errcode") + protected Long errcode; + + @SerializedName("errmsg") + protected String errmsg; + + public boolean success() { + return getErrcode() == 0; + } + + public static WxCpBaseResp fromJson(String json) { + return WxCpGsonBuilder.create().fromJson(json, WxCpBaseResp.class); + } +} diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpDepart.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpDepart.java index dc71e027e3..f5b9b32592 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpDepart.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpDepart.java @@ -6,16 +6,17 @@ import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder; /** - * 微信部门. + * 企业微信的部门. * * @author Daniel Qian */ @Data public class WxCpDepart implements Serializable { - private static final long serialVersionUID = -5028321625140879571L; + private Long id; private String name; + private String enName; private Long parentId; private Long order; diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpAuthInfo.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpAuthInfo.java new file mode 100644 index 0000000000..4354865a28 --- /dev/null +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpAuthInfo.java @@ -0,0 +1,202 @@ +package me.chanjar.weixin.cp.bean; + +import com.google.gson.annotations.SerializedName; +import lombok.Getter; +import lombok.Setter; +import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder; + +import java.util.List; + +/** + * 服务商模式获取授权信息 + * + * @author yuanqixun + */ +@Getter +@Setter +public class WxCpTpAuthInfo extends WxCpBaseResp { + private static final long serialVersionUID = -5028321625140879571L; + + /** + * 服务商信息 + */ + @SerializedName("dealer_corp_info") + private DealerCorpInfo dealerCorpInfo; + + /** + * 授权企业信息 + */ + @SerializedName("auth_corp_info") + private AuthCorpInfo authCorpInfo; + + /** + * 授权信息。如果是通讯录应用,且没开启实体应用,是没有该项的。通讯录应用拥有企业通讯录的全部信息读写权限 + */ + @SerializedName("auth_info") + private AuthInfo authInfo; + + @Getter + @Setter + public static class DealerCorpInfo { + @SerializedName("corpid") + private String corpId; + + @SerializedName("corp_name") + private String corpName; + } + + @Getter + @Setter + public static class AuthCorpInfo { + @SerializedName("corpid") + private String corpId; + + @SerializedName("corp_name") + private String corpName; + + @SerializedName("corp_type") + private String corpType; + + @SerializedName("corp_square_logo_url") + private String corpSquareLogoUrl; + + @SerializedName("corp_round_logo_url") + private String corpRoundLogoUrl; + + @SerializedName("corp_user_max") + private String corpUserMax; + + @SerializedName("corp_agent_max") + private String corpAgentMax; + + /** + * 所绑定的企业微信主体名称(仅认证过的企业有) + */ + @SerializedName("corp_full_name") + private String corpFullName; + + /** + * 认证到期时间 + */ + @SerializedName("verified_end_time") + private Long verifiedEndTime; + + /** + * 企业类型,1. 企业; 2. 政府以及事业单位; 3. 其他组织, 4.团队号 + */ + @SerializedName("subject_type") + private Integer subjectType; + + /** + * 授权企业在微工作台(原企业号)的二维码,可用于关注微工作台 + */ + @SerializedName("corp_wxqrcode") + private String corpWxQrcode; + + @SerializedName("corp_scale") + private String corpScale; + + @SerializedName("corp_industry") + private String corpIndustry; + + @SerializedName("corp_sub_industry") + private String corpSubIndustry; + + @SerializedName("location") + private String location; + + } + + /** + * 授权信息 + */ + @Getter + @Setter + public static class AuthInfo { + + /** + * 授权的应用信息,注意是一个数组,但仅旧的多应用套件授权时会返回多个agent,对新的单应用授权,永远只返回一个agent + */ + @SerializedName("agent") + private Listagents; + + } + + @Getter + @Setter + public static class Agent { + @SerializedName("agentid") + private Integer agentId; + + @SerializedName("name") + private String name; + + @SerializedName("round_logo_url") + private String roundLogoUrl; + + @SerializedName("square_logo_url") + private String squareLogoUrl; + + /** + * 旧的多应用套件中的对应应用id,新开发者请忽略 + */ + @SerializedName("appid") + @Deprecated + private String appid; + + /** + * 应用权限 + */ + @SerializedName("privilege") + private Privilege privilege; + + } + + /** + * 应用对应的权限 + */ + @Getter + @Setter + public static class Privilege { + + /** + * 权限等级。 + * 1:通讯录基本信息只读 + * 2:通讯录全部信息只读 + * 3:通讯录全部信息读写 + * 4:单个基本信息只读 + * 5:通讯录全部信息只写 + */ + @SerializedName("level") + private Integer level; + + @SerializedName("allow_party") + private List allowParties; + + @SerializedName("allow_user") + private List allowUsers; + + @SerializedName("allow_tag") + private List allowTags; + + @SerializedName("extra_party") + private List extraParties; + + @SerializedName("extra_user") + private List extraUsers; + + @SerializedName("extra_tag") + private List extraTags; + + } + + + public static WxCpTpAuthInfo fromJson(String json) { + return WxCpGsonBuilder.create().fromJson(json, WxCpTpAuthInfo.class); + } + + public String toJson() { + return WxCpGsonBuilder.create().toJson(this); + } + +} diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpCorp.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpCorp.java index 9ca59b843d..f77fdb78df 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpCorp.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpCorp.java @@ -1,42 +1,44 @@ -package me.chanjar.weixin.cp.bean; - -import java.io.Serializable; - -import com.google.gson.annotations.SerializedName; - -import lombok.Data; -import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder; - -/** - * 微信部门. - * - * @author Daniel Qian - */ -@Data -public class WxCpTpCorp implements Serializable { - - private static final long serialVersionUID = -5028321625140879571L; - @SerializedName("corpid") - private String corpId; - @SerializedName("corp_name") - private String corpName; - @SerializedName("corp_full_name") - private String corpFullName; - @SerializedName("corp_type") - private String corpType; - @SerializedName("corp_square_logo_url") - private String corpSquareLogoUrl; - @SerializedName("corp_user_max") - private String corpUserMax; - @SerializedName("permanent_code") - private String permanentCode; - - public static WxCpTpCorp fromJson(String json) { - return WxCpGsonBuilder.create().fromJson(json, WxCpTpCorp.class); - } - - public String toJson() { - return WxCpGsonBuilder.create().toJson(this); - } - -} +package me.chanjar.weixin.cp.bean; + +import java.io.Serializable; + +import com.google.gson.annotations.SerializedName; + +import lombok.Data; +import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder; + +/** + * 微信部门. + * + * @author Daniel Qian + */ +@Data +public class WxCpTpCorp implements Serializable { + + private static final long serialVersionUID = -5028321625140879571L; + @SerializedName("corpid") + private String corpId; + @SerializedName("corp_name") + private String corpName; + @SerializedName("corp_full_name") + private String corpFullName; + @SerializedName("corp_type") + private String corpType; + @SerializedName("corp_square_logo_url") + private String corpSquareLogoUrl; + @SerializedName("corp_user_max") + private String corpUserMax; + @SerializedName("permanent_code") + private String permanentCode; + @SerializedName("auth_info") + private String authInfo; + + public static WxCpTpCorp fromJson(String json) { + return WxCpGsonBuilder.create().fromJson(json, WxCpTpCorp.class); + } + + public String toJson() { + return WxCpGsonBuilder.create().toJson(this); + } + +} diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpPermanentCodeInfo.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpPermanentCodeInfo.java new file mode 100644 index 0000000000..cd57119d1e --- /dev/null +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpPermanentCodeInfo.java @@ -0,0 +1,219 @@ +package me.chanjar.weixin.cp.bean; + +import com.google.gson.annotations.SerializedName; +import lombok.Getter; +import lombok.Setter; +import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder; + +import java.util.List; + +/** + * 服务商模式获取永久授权码信息 + * + * @author yunaqixun + */ +@Getter +@Setter +public class WxCpTpPermanentCodeInfo extends WxCpBaseResp { + + private static final long serialVersionUID = -5028321625140879571L; + + @SerializedName("access_token") + private String accessToken; + + @SerializedName("expires_in") + private Long expiresIn; + + @SerializedName("permanent_code") + private String permanentCode; + + /** + * 授权企业信息 + */ + @SerializedName("auth_corp_info") + private AuthCorpInfo authCorpInfo; + + /** + * 授权信息。如果是通讯录应用,且没开启实体应用,是没有该项的。通讯录应用拥有企业通讯录的全部信息读写权限 + */ + @SerializedName("auth_info") + private AuthInfo authInfo; + + /** + * 授权用户信息 + */ + @SerializedName("auth_user_info") + private AuthUserInfo authUserInfo; + + + @Getter + @Setter + public static class AuthCorpInfo { + @SerializedName("corpid") + private String corpId; + + @SerializedName("corp_name") + private String corpName; + + @SerializedName("corp_type") + private String corpType; + + @SerializedName("corp_square_logo_url") + private String corpSquareLogoUrl; + + @SerializedName("corp_round_logo_url") + private String corpRoundLogoUrl; + + @SerializedName("corp_user_max") + private String corpUserMax; + + @SerializedName("corp_agent_max") + private String corpAgentMax; + + /** + * 所绑定的企业微信主体名称(仅认证过的企业有) + */ + @SerializedName("corp_full_name") + private String corpFullName; + + /** + * 认证到期时间 + */ + @SerializedName("verified_end_time") + private Long verifiedEndTime; + + /** + * 企业类型,1. 企业; 2. 政府以及事业单位; 3. 其他组织, 4.团队号 + */ + @SerializedName("subject_type") + private Integer subjectType; + + /** + * 授权企业在微工作台(原企业号)的二维码,可用于关注微工作台 + */ + @SerializedName("corp_wxqrcode") + private String corpWxQrcode; + + @SerializedName("corp_scale") + private String corpScale; + + @SerializedName("corp_industry") + private String corpIndustry; + + @SerializedName("corp_sub_industry") + private String corpSubIndustry; + + @SerializedName("location") + private String location; + + } + + /** + * 授权信息 + */ + @Getter + @Setter + public static class AuthInfo { + + /** + * 授权的应用信息,注意是一个数组,但仅旧的多应用套件授权时会返回多个agent,对新的单应用授权,永远只返回一个agent + */ + @SerializedName("agent") + private List agents; + + } + + @Getter + @Setter + public static class Agent { + @SerializedName("agentid") + private Integer agentId; + + @SerializedName("name") + private String name; + + @SerializedName("round_logo_url") + private String roundLogoUrl; + + @SerializedName("square_logo_url") + private String squareLogoUrl; + + /** + * 旧的多应用套件中的对应应用id,新开发者请忽略 + */ + @SerializedName("appid") + @Deprecated + private String appid; + + /** + * 应用权限 + */ + @SerializedName("privilege") + private Privilege privilege; + + } + + /** + * 授权人员信息 + */ + @Getter + @Setter + public static class AuthUserInfo { + @SerializedName("userid") + private String userId; + + @SerializedName("name") + private String name; + + @SerializedName("avatar") + private String avatar; + } + + /** + * 应用对应的权限 + */ + @Getter + @Setter + public static class Privilege { + + /** + * 权限等级。 + * 1:通讯录基本信息只读 + * 2:通讯录全部信息只读 + * 3:通讯录全部信息读写 + * 4:单个基本信息只读 + * 5:通讯录全部信息只写 + */ + @SerializedName("level") + private Integer level; + + @SerializedName("allow_party") + private List allowParties; + + @SerializedName("allow_user") + private List allowUsers; + + @SerializedName("allow_tag") + private List allowTags; + + @SerializedName("extra_party") + private List extraParties; + + @SerializedName("extra_user") + private List extraUsers; + + @SerializedName("extra_tag") + private List extraTags; + + + } + + public static WxCpTpPermanentCodeInfo fromJson(String json) { + return WxCpGsonBuilder.create().fromJson(json, WxCpTpPermanentCodeInfo.class); + } + + public String toJson() { + return WxCpGsonBuilder.create().toJson(this); + } + +} diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpPreauthCode.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpPreauthCode.java new file mode 100644 index 0000000000..8c102ae4a2 --- /dev/null +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpPreauthCode.java @@ -0,0 +1,26 @@ +package me.chanjar.weixin.cp.bean; + +import com.google.gson.annotations.SerializedName; +import lombok.Getter; +import lombok.Setter; +import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder; + +/** + * 预授权码返回 + * @author yqx + * @date 2020/3/19 + */ +@Getter +@Setter +public class WxCpTpPreauthCode extends WxCpBaseResp { + + @SerializedName("pre_auth_code") + String preAuthCode; + + @SerializedName("expires_in") + Long expiresIn; + + public static WxCpTpPreauthCode fromJson(String json) { + return WxCpGsonBuilder.create().fromJson(json, WxCpTpPreauthCode.class); + } +} diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpXmlMessage.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpXmlMessage.java index 53e068dbfe..f20f74b757 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpXmlMessage.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpXmlMessage.java @@ -1,68 +1,72 @@ -package me.chanjar.weixin.cp.bean; - -import java.io.Serializable; -import java.util.Map; - -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.util.XmlUtils; -import me.chanjar.weixin.common.util.crypto.WxCryptUtil; -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.config.WxCpTpConfigStorage; -import me.chanjar.weixin.cp.util.crypto.WxCpCryptUtil; -import me.chanjar.weixin.cp.util.xml.XStreamTransformer; - -/** - * 回调推送的message - * https://work.weixin.qq.com/api/doc#90001/90143/90612 - * - * @author zhenjun cai - */ -@XStreamAlias("xml") -@Slf4j -@Data -public class WxCpTpXmlMessage implements Serializable { - - private static final long serialVersionUID = 6031833682211475786L; - /** - * 使用dom4j解析的存放所有xml属性和值的map. - */ - private Map allFieldsMap; - - @XStreamAlias("SuiteId") - @XStreamConverter(value = XStreamCDataConverter.class) - protected String suiteId; - - @XStreamAlias("InfoType") - @XStreamConverter(value = XStreamCDataConverter.class) - protected String infoType; - - @XStreamAlias("TimeStamp") - @XStreamConverter(value = XStreamCDataConverter.class) - protected String timeStamp; - - @XStreamAlias("SuiteTicket") - @XStreamConverter(value = XStreamCDataConverter.class) - protected String suiteTicket; - - @XStreamAlias("AuthCode") - @XStreamConverter(value = XStreamCDataConverter.class) - protected String authCode; - - public static WxCpTpXmlMessage fromXml(String xml) { - //修改微信变态的消息内容格式,方便解析 - //xml = xml.replace(" ", ""); - final WxCpTpXmlMessage xmlPackage = XStreamTransformer.fromXml(WxCpTpXmlMessage.class, xml); - xmlPackage.setAllFieldsMap(XmlUtils.xml2Map(xml)); - return xmlPackage; - } - -} +package me.chanjar.weixin.cp.bean; + +import java.io.Serializable; +import java.util.Map; + +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.util.XmlUtils; +import me.chanjar.weixin.common.util.crypto.WxCryptUtil; +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.config.WxCpTpConfigStorage; +import me.chanjar.weixin.cp.util.crypto.WxCpCryptUtil; +import me.chanjar.weixin.cp.util.xml.XStreamTransformer; + +/** + * 回调推送的message + * https://work.weixin.qq.com/api/doc#90001/90143/90612 + * + * @author zhenjun cai + */ +@XStreamAlias("xml") +@Slf4j +@Data +public class WxCpTpXmlMessage implements Serializable { + + private static final long serialVersionUID = 6031833682211475786L; + /** + * 使用dom4j解析的存放所有xml属性和值的map. + */ + private Map allFieldsMap; + + @XStreamAlias("SuiteId") + @XStreamConverter(value = XStreamCDataConverter.class) + protected String suiteId; + + @XStreamAlias("InfoType") + @XStreamConverter(value = XStreamCDataConverter.class) + protected String infoType; + + @XStreamAlias("TimeStamp") + @XStreamConverter(value = XStreamCDataConverter.class) + protected String timeStamp; + + @XStreamAlias("SuiteTicket") + @XStreamConverter(value = XStreamCDataConverter.class) + protected String suiteTicket; + + @XStreamAlias("AuthCode") + @XStreamConverter(value = XStreamCDataConverter.class) + protected String authCode; + + @XStreamAlias("AuthCorpId") + @XStreamConverter(value = XStreamCDataConverter.class) + protected String authCorpId; + + public static WxCpTpXmlMessage fromXml(String xml) { + //修改微信变态的消息内容格式,方便解析 + //xml = xml.replace(" ", ""); + final WxCpTpXmlMessage xmlPackage = XStreamTransformer.fromXml(WxCpTpXmlMessage.class, xml); + xmlPackage.setAllFieldsMap(XmlUtils.xml2Map(xml)); + return xmlPackage; + } + +} diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpUser.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpUser.java index 90354154a6..07ecb4aa85 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpUser.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpUser.java @@ -4,6 +4,7 @@ import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; +import lombok.experimental.Accessors; import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder; import java.io.Serializable; @@ -16,8 +17,10 @@ * @author Daniel Qian */ @Data +@Accessors(chain = true) public class WxCpUser implements Serializable { private static final long serialVersionUID = -5696099236344075582L; + private String userId; private String name; private Long[] departIds; @@ -56,13 +59,19 @@ public class WxCpUser implements Serializable { * 成员对外信息. */ private List externalAttrs = new ArrayList<>(); + private String externalPosition; + private String externalCorpName; public void addExternalAttr(ExternalAttribute externalAttr) { this.externalAttrs.add(externalAttr); } public void addExtAttr(String name, String value) { - this.extAttrs.add(new Attr(name, value)); + this.extAttrs.add(new Attr().setType(0).setName(name).setTextValue(value)); + } + + public void addExtAttr(Attr attr) { + this.extAttrs.add(attr); } public static WxCpUser fromJson(String json) { @@ -74,10 +83,16 @@ public String toJson() { } @Data - @AllArgsConstructor + @Accessors(chain = true) public static class Attr { + /** + * 属性类型: 0-文本 1-网页 + */ + private int type; private String name; - private String value; + private String textValue; + private String webUrl; + private String webTitle; } @Data diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpUserExternalGroupChatInfo.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpUserExternalGroupChatInfo.java new file mode 100644 index 0000000000..e5d8db7262 --- /dev/null +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpUserExternalGroupChatInfo.java @@ -0,0 +1,75 @@ +package me.chanjar.weixin.cp.bean; + +import com.google.gson.annotations.SerializedName; +import lombok.Getter; +import lombok.Setter; +import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder; + +import java.util.List; + +/** + * @author yqx + * @date 2020/3/116 + */ +@Getter +@Setter +public class WxCpUserExternalGroupChatInfo extends WxCpBaseResp{ + + @SerializedName("group_chat") + private GroupChat groupChat; + + @Getter + @Setter + public static class GroupChat { + @SerializedName("chat_id") + private String chatId; + + @SerializedName("name") + private String name; + + @SerializedName("owner") + private String owner; + + @SerializedName("create_time") + private Long createTime; + + @SerializedName("notice") + private String notice; + + @SerializedName("member_list") + private List memberList; + + } + + @Getter + @Setter + public static class GroupMember { + @SerializedName("userid") + private String userId; + + /** + * 成员类型。 + * 1 - 企业成员 + * 2 - 外部联系人 + */ + @SerializedName("type") + private int type; + + @SerializedName("join_time") + private Long joinTime; + + /** + * 入群方式。 + * 1 - 由成员邀请入群(直接邀请入群) + * 2 - 由成员邀请入群(通过邀请链接入群) + * 3 - 通过扫描群二维码入群 + */ + @SerializedName("join_scene") + private int joinScene; + + } + + public static WxCpUserExternalGroupChatInfo fromJson(String json) { + return WxCpGsonBuilder.create().fromJson(json, WxCpUserExternalGroupChatInfo.class); + } +} diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpUserExternalGroupChatList.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpUserExternalGroupChatList.java new file mode 100644 index 0000000000..0a0e970efc --- /dev/null +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpUserExternalGroupChatList.java @@ -0,0 +1,46 @@ +package me.chanjar.weixin.cp.bean; + +import com.google.gson.annotations.SerializedName; +import lombok.Getter; +import lombok.Setter; +import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder; + +import java.util.List; + +/** + * @author yqx + * @date 2020/3/116 + */ +@Getter +@Setter +public class WxCpUserExternalGroupChatList extends WxCpBaseResp { + + @SerializedName("group_chat_list") + private List groupChatList; + + @Getter + @Setter + public static class ChatStatus { + + /** + * 客户群ID + */ + @SerializedName("chat_id") + private String chatId; + + /** + * 客户群状态 + * 0 - 正常 + * 1 - 跟进人离职 + * 2 - 离职继承中 + * 3 - 离职继承完成 + */ + @SerializedName("status") + private int status; + + } + + public static WxCpUserExternalGroupChatList fromJson(String json) { + return WxCpGsonBuilder.create().fromJson(json, WxCpUserExternalGroupChatList.class); + } +} diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpUserExternalGroupChatStatistic.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpUserExternalGroupChatStatistic.java new file mode 100644 index 0000000000..242ceb4f8b --- /dev/null +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpUserExternalGroupChatStatistic.java @@ -0,0 +1,90 @@ +package me.chanjar.weixin.cp.bean; + +import com.google.gson.annotations.SerializedName; +import lombok.Getter; +import lombok.Setter; +import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder; + +import java.util.List; + +/** + * 联系客户群统计数据 + * + * @author yqx + * @date 2020/3/16 + */ +@Getter +@Setter +public class WxCpUserExternalGroupChatStatistic extends WxCpBaseResp{ + + @SerializedName("total") + int total; + + @SerializedName("next_offset") + int nextOffset; + + @SerializedName("items") + List itemList; + + @Getter + @Setter + public static class StatisticItem { + + @SerializedName("owner") + String owner; + + @SerializedName("data") + ItemData itemData; + } + + @Getter + @Setter + public static class ItemData { + + /** + * 新增客户群数量 + */ + @SerializedName("new_chat_cnt") + int newChatCnt; + + /** + * 截至当天客户群总数量 + */ + @SerializedName("chat_total") + int chatTotal; + + /** + * 截至当天有发过消息的客户群数量 + */ + @SerializedName("chat_has_msg") + int chatHasMsg; + + /** + * 客户群新增群人数。 + */ + @SerializedName("new_member_cnt") + int newMemberCnt; + + /** + * 截至当天客户群总人数 + */ + @SerializedName("member_total") + int memberTotal; + + /** + * 截至当天有发过消息的群成员数 + */ + @SerializedName("member_has_msg") + int memberHasMsg; + + /** + * 截至当天客户群消息总数 + */ + @SerializedName("msg_total") + int msgTotal; + } + + public static WxCpUserExternalGroupChatStatistic fromJson(String json) { + return WxCpGsonBuilder.create().fromJson(json, WxCpUserExternalGroupChatStatistic.class); + } +} diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpUserExternalUnassignList.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpUserExternalUnassignList.java new file mode 100644 index 0000000000..ec86dea767 --- /dev/null +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpUserExternalUnassignList.java @@ -0,0 +1,52 @@ +package me.chanjar.weixin.cp.bean; + +import com.google.gson.annotations.Expose; +import com.google.gson.annotations.SerializedName; +import lombok.Getter; +import lombok.Setter; +import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder; + +import java.util.List; + +/** + * 离职员工外部联系人列表 + * @author yqx + * @date 2020/3/15 + */ +@Getter +@Setter +public class WxCpUserExternalUnassignList extends WxCpBaseResp{ + + @SerializedName("info") + private List unassignInfos; + + @SerializedName("is_last") + private boolean isLast; + + @Getter + @Setter + public static class UnassignInfo { + + /** + * 离职成员userid + */ + @SerializedName("handover_userid") + private String handoverUserid; + + /** + * 外部联系人userid + */ + @SerializedName("external_userid") + private String externalUserid; + + /** + * 成员离职时间 + */ + @SerializedName("dimission_time") + private Long dimissionTime; + } + + public static WxCpUserExternalUnassignList fromJson(String json) { + return WxCpGsonBuilder.create().fromJson(json, WxCpUserExternalUnassignList.class); + } +} diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpUserExternalUserBehaviorStatistic.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpUserExternalUserBehaviorStatistic.java new file mode 100644 index 0000000000..bc1aa0ca53 --- /dev/null +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpUserExternalUserBehaviorStatistic.java @@ -0,0 +1,78 @@ +package me.chanjar.weixin.cp.bean; + +import com.google.gson.annotations.SerializedName; +import lombok.Getter; +import lombok.Setter; +import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder; + +import java.util.List; + +/** + * 联系客户统计数据 + * @author yqx + * @date 2020/3/16 + */ +@Getter +@Setter +public class WxCpUserExternalUserBehaviorStatistic extends WxCpBaseResp { + + @SerializedName("behavior_data") + private List behaviorList; + + @Getter + @Setter + public static class Behavior { + + /** + * 数据日期,为当日0点的时间戳 + */ + @SerializedName("stat_time") + private Long statTime; + + /** + * 聊天总数, 成员有主动发送过消息的聊天数,包括单聊和群聊。 + */ + @SerializedName("chat_cnt") + private int chatCnt; + + /** + * 发送消息数,成员在单聊和群聊中发送的消息总数。 + */ + @SerializedName("message_cnt") + private int messageCnt; + + /** + * 已回复聊天占比,客户主动发起聊天后,成员在一个自然日内有回复过消息的聊天数/客户主动发起的聊天数比例,不包括群聊,仅在确有回复时返回。 + */ + @SerializedName("reply_percentage") + private double replyPercentage; + + /** + * 平均首次回复时长,单位为分钟,即客户主动发起聊天后,成员在一个自然日内首次回复的时长间隔为首次回复时长,所有聊天的首次回复总时长/已回复的聊天总数即为平均首次回复时长,不包括群聊,仅在确有回复时返回。 + */ + @SerializedName("avg_reply_time") + private int avgReplyTime; + + /** + * 删除/拉黑成员的客户数,即将成员删除或加入黑名单的客户数。 + */ + @SerializedName("negative_fee_back_cnt") + private int negativeFeeBackCnt; + + /** + * 发起申请数,成员通过「搜索手机号」、「扫一扫」、「从微信好友中添加」、「从群聊中添加」、「添加共享、分配给我的客户」、「添加单向、双向删除好友关系的好友」、「从新的联系人推荐中添加」等渠道主动向客户发起的好友申请数量。 + */ + @SerializedName("new_apply_cnt") + private int newApplyCnt; + + /** + * 新增客户数,成员新添加的客户数量。 + */ + @SerializedName("new_contact_cnt") + private int newContactCnt; + } + + public static WxCpUserExternalUserBehaviorStatistic fromJson(String json) { + return WxCpGsonBuilder.create().fromJson(json, WxCpUserExternalUserBehaviorStatistic.class); + } +} diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpUserWithExternalPermission.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpUserWithExternalPermission.java index 2530c79dc8..e64a273d74 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpUserWithExternalPermission.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpUserWithExternalPermission.java @@ -2,46 +2,26 @@ import com.google.gson.annotations.Expose; import com.google.gson.annotations.SerializedName; +import lombok.Data; import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder; import java.util.List; +/** + * @author 曹祖鹏 + */ +@Data public class WxCpUserWithExternalPermission { @SerializedName("errcode") @Expose - private Long errcode; + private Long errCode; @SerializedName("errmsg") @Expose - private String errmsg; + private String errMsg; @SerializedName("follow_user") @Expose - private List followUser = null; - - public Long getErrcode() { - return errcode; - } - - public void setErrcode(Long errcode) { - this.errcode = errcode; - } - - public String getErrmsg() { - return errmsg; - } - - public void setErrmsg(String errmsg) { - this.errmsg = errmsg; - } - - public List getFollowUser() { - return followUser; - } - - public void setFollowUser(List followUser) { - this.followUser = followUser; - } - + private List followers = null; public static WxCpUserWithExternalPermission fromJson(String json) { return WxCpGsonBuilder.create().fromJson(json, WxCpUserWithExternalPermission.class); diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpXmlMessage.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpXmlMessage.java index 712eed83f0..ccae5a4644 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpXmlMessage.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpXmlMessage.java @@ -233,6 +233,13 @@ public class WxCpXmlMessage implements Serializable { @XStreamConverter(value = XStreamCDataConverter.class) private String position; + /** + * 群ID. + */ + @XStreamAlias("ChatId") + @XStreamConverter(value = XStreamCDataConverter.class) + private String chatId; + /** * 性别,1表示男性,2表示女性. */ @@ -396,6 +403,12 @@ public class WxCpXmlMessage implements Serializable { @XStreamAlias("SendLocationInfo") private SendLocationInfo sendLocationInfo = new SendLocationInfo(); + + @XStreamAlias("ApprovalInfo") + private ApprovalInfo approvalInfo=new ApprovalInfo(); + + + protected static WxCpXmlMessage fromXml(String xml) { //修改微信变态的消息内容格式,方便解析 xml = xml.replace(" ", ""); @@ -515,4 +528,57 @@ public static class SendLocationInfo { } + @XStreamAlias("ApprovalInfo") + @Data + public static class ApprovalInfo { + + /** + * 审批编号 + */ + @XStreamAlias("SpNo") + private String spNo; + /** + * 审批申请类型名称(审批模板名称) + */ + @XStreamAlias("SpName") + private String spName; + /** + * 申请单状态:1-审批中;2-已通过;3-已驳回;4-已撤销;6-通过后撤销;7-已删除;10-已支付 + */ + @XStreamAlias("SpStatus") + private Integer spStatus; + + /** + * 审批模板id。 + */ + @XStreamAlias("templateId") + private String templateId; + /** + * 审批申请提交时间,Unix时间戳 + */ + @XStreamAlias("ApplyTime") + private Integer applyTime; + + /** + * 申请人信息 + */ + @XStreamAlias("Applyer") + private Applyer applyer; + /** + * 审批申请单变化类型 + */ + @XStreamAlias("StatuChangeEvent") + private Integer statuChangeEvent; + + @XStreamAlias("Applyer") + @Data + public static class Applyer { + @XStreamAlias("Applyer") + private String UserId; + @XStreamAlias("Party") + private String party; + } + + } + } diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/article/MpnewsArticle.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/article/MpnewsArticle.java index 595d12c5bc..2b2a9fe051 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/article/MpnewsArticle.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/article/MpnewsArticle.java @@ -1,7 +1,9 @@ package me.chanjar.weixin.cp.bean.article; +import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; +import lombok.NoArgsConstructor; import java.io.Serializable; @@ -13,6 +15,8 @@ * @author Binary Wang */ @Data +@NoArgsConstructor +@AllArgsConstructor @Builder(builderMethodName = "newBuilder") public class MpnewsArticle implements Serializable { private static final long serialVersionUID = 6985871812170756481L; diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/WxCpApprovalComment.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/WxCpApprovalComment.java index 8a70e3e6e9..1bf06ec51b 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/WxCpApprovalComment.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/WxCpApprovalComment.java @@ -13,7 +13,6 @@ */ @Data public class WxCpApprovalComment implements Serializable { - private static final long serialVersionUID = -5430367411926856292L; /** diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/WxCpApprovalDetail.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/WxCpApprovalDetail.java index 5021a57ee6..a22a435844 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/WxCpApprovalDetail.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/WxCpApprovalDetail.java @@ -13,7 +13,6 @@ */ @Data public class WxCpApprovalDetail implements Serializable { - private static final long serialVersionUID = 1353393306564207170L; /** @@ -49,19 +48,20 @@ public class WxCpApprovalDetail implements Serializable { /** * 申请人信息 */ - private WxCpApprovalApplyer applyer; + @SerializedName("applyer") + private WxCpApprovalApplyer applier; /** * 审批流程信息,可能有多个审批节点 */ @SerializedName("sp_record") - private WxCpApprovalRecord spRecord; + private WxCpApprovalRecord[] spRecords; /** * 抄送信息,可能有多个抄送节点 */ @SerializedName("notifyer") - private WxCpOperator notifyer; + private WxCpOperator[] notifiers; /** * 审批申请数据 diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/applydata/ContentValue.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/applydata/ContentValue.java index 7798338aa1..92b47f56da 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/applydata/ContentValue.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/applydata/ContentValue.java @@ -11,16 +11,15 @@ */ @Data public class ContentValue implements Serializable { - private static final long serialVersionUID = -5607678965965065261L; private String text; @SerializedName("new_number") - private Integer newNumber; + private Double newNumber; @SerializedName("new_money") - private Integer newMoney; + private Double newMoney; private ContentValue.Date date; @@ -34,26 +33,25 @@ public class ContentValue implements Serializable { private List children; + private Attendance attendance; + @Data public static class Date implements Serializable { - private static final long serialVersionUID = -6181554080062231138L; private String type; @SerializedName("s_timestamp") - private Long timestamp; + private Double timestamp; } @Data public static class Selector implements Serializable { - private static final long serialVersionUID = 7305458759126951773L; private String type; private List ", ""); diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/result/WxMpCurrentAutoReplyInfo.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/result/WxMpCurrentAutoReplyInfo.java index d702eeaf85..7473bb1323 100644 --- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/result/WxMpCurrentAutoReplyInfo.java +++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/result/WxMpCurrentAutoReplyInfo.java @@ -14,6 +14,7 @@ /** * * 公众号的自动回复规则. + * 参考文档地址:https://developers.weixin.qq.com/doc/offiaccount/Message_Management/Getting_Rules_for_Auto_Replies.html * Created by Binary Wang on 2017-7-8. ** diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/template/WxMpTemplateIndustryEnum.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/template/WxMpTemplateIndustryEnum.java index 8a0a91371b..022551d64e 100644 --- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/template/WxMpTemplateIndustryEnum.java +++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/template/WxMpTemplateIndustryEnum.java @@ -41,9 +41,9 @@ public enum WxMpTemplateIndustryEnum { */ BANK("金融业", "银行", 7), /** - * 金融业 - 基金|理财|信托 + * 金融业 - 证券|基金|理财|信托(实际是这个) */ - FUND("金融业", "基金|理财|信托", 8), + FUND("金融业", "证券基金理财信托", 8), /** * 金融业 - 保险 */ @@ -93,9 +93,9 @@ public enum WxMpTemplateIndustryEnum { */ MUSEUM("政府与公共事业", "博物馆", 20), /** - * 政府与公共事业 - 公共事业|非盈利机构 + * 政府与公共事业 - 公共事业非盈利机构 */ - PUBLIC_WORKS_NONPROFIT("政府与公共事业", "公共事业|非盈利机构", 21), + PUBLIC_WORKS_NONPROFIT("政府与公共事业", "公共事业非盈利机构", 21), /** * 医药护理 - 医药医疗 */ @@ -125,7 +125,7 @@ public enum WxMpTemplateIndustryEnum { */ THE_PLANE_RELATED("交通工具", "飞机相关", 28), /** - * 房地产 - 建筑 + * 房地产 - 房地产|建筑(实际上是这个) */ ARCHITECTURE("房地产", "建筑", 29), /** @@ -153,9 +153,9 @@ public enum WxMpTemplateIndustryEnum { */ AUTHENTICATION("商业服务", "认证", 35), /** - * 商业服务 - 会计|审计 + * 商业服务 - 审计 */ - AUDIT("商业服务", "会计|审计", 36), + AUDIT("商业服务", "审计", 36), /** * 文体娱乐 - 传媒 */ @@ -198,7 +198,7 @@ public enum WxMpTemplateIndustryEnum { */ public static WxMpTemplateIndustryEnum findBySecondary(String industry) { for (WxMpTemplateIndustryEnum industryEnum : WxMpTemplateIndustryEnum.values()) { - if (industryEnum.secondClass.equals(industry)) { + if (industryEnum.secondClass.contains(industry)) { return industryEnum; } } diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/config/WxMpConfigStorage.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/config/WxMpConfigStorage.java index 7c3c46292f..d0d7003a25 100644 --- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/config/WxMpConfigStorage.java +++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/config/WxMpConfigStorage.java @@ -3,7 +3,7 @@ import me.chanjar.weixin.common.bean.WxAccessToken; import me.chanjar.weixin.common.util.http.apache.ApacheHttpClientBuilder; import me.chanjar.weixin.mp.bean.WxMpHostConfig; -import me.chanjar.weixin.mp.enums.TicketType; +import me.chanjar.weixin.common.enums.TicketType; import java.io.File; import java.util.concurrent.locks.Lock; diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/config/impl/WxMpDefaultConfigImpl.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/config/impl/WxMpDefaultConfigImpl.java index 08e7d0830c..0cb350f72d 100644 --- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/config/impl/WxMpDefaultConfigImpl.java +++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/config/impl/WxMpDefaultConfigImpl.java @@ -10,7 +10,7 @@ import me.chanjar.weixin.common.util.http.apache.ApacheHttpClientBuilder; import me.chanjar.weixin.mp.config.WxMpConfigStorage; import me.chanjar.weixin.mp.bean.WxMpHostConfig; -import me.chanjar.weixin.mp.enums.TicketType; +import me.chanjar.weixin.common.enums.TicketType; import me.chanjar.weixin.mp.util.json.WxMpGsonBuilder; /** diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/config/impl/WxMpRedisConfigImpl.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/config/impl/WxMpRedisConfigImpl.java index 4d2d1783ed..dbb3878218 100644 --- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/config/impl/WxMpRedisConfigImpl.java +++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/config/impl/WxMpRedisConfigImpl.java @@ -1,9 +1,13 @@ package me.chanjar.weixin.mp.config.impl; -import me.chanjar.weixin.mp.enums.TicketType; -import redis.clients.jedis.Jedis; +import lombok.Data; +import me.chanjar.weixin.common.redis.JedisWxRedisOps; +import me.chanjar.weixin.common.redis.WxRedisOps; +import me.chanjar.weixin.common.enums.TicketType; import redis.clients.jedis.JedisPool; +import java.util.concurrent.TimeUnit; + /** * 基于Redis的微信配置provider. * @@ -14,19 +18,28 @@ * * @author nickwong */ +@Data @SuppressWarnings("hiding") public class WxMpRedisConfigImpl extends WxMpDefaultConfigImpl { - private static final String ACCESS_TOKEN_KEY = "wx:access_token:"; + private static final long serialVersionUID = -988502871997239733L; - /** - * 使用连接池保证线程安全. - */ - private final JedisPool jedisPool; + private static final String ACCESS_TOKEN_KEY_TPL = "%s:access_token:%s"; + private static final String TICKET_KEY_TPL = "%s:ticket:key:%s:%s"; + private static final String LOCK_KEY_TPL = "%s:lock:%s:"; + + private final WxRedisOps redisOps; + private final String keyPrefix; private String accessTokenKey; + private String lockKey; public WxMpRedisConfigImpl(JedisPool jedisPool) { - this.jedisPool = jedisPool; + this(new JedisWxRedisOps(jedisPool), "wx"); + } + + public WxMpRedisConfigImpl(WxRedisOps redisOps, String keyPrefix) { + this.redisOps = redisOps; + this.keyPrefix = keyPrefix; } /** @@ -35,67 +48,57 @@ public WxMpRedisConfigImpl(JedisPool jedisPool) { @Override public void setAppId(String appId) { super.setAppId(appId); - this.accessTokenKey = ACCESS_TOKEN_KEY.concat(appId); + this.accessTokenKey = String.format(ACCESS_TOKEN_KEY_TPL, this.keyPrefix, appId); + this.lockKey = String.format(LOCK_KEY_TPL, this.keyPrefix, appId); + accessTokenLock = this.redisOps.getLock(lockKey.concat("accessTokenLock")); + jsapiTicketLock = this.redisOps.getLock(lockKey.concat("jsapiTicketLock")); + sdkTicketLock = this.redisOps.getLock(lockKey.concat("sdkTicketLock")); + cardApiTicketLock = this.redisOps.getLock(lockKey.concat("cardApiTicketLock")); } private String getTicketRedisKey(TicketType type) { - return String.format("wx:ticket:key:%s:%s", this.appId, type.getCode()); + return String.format(TICKET_KEY_TPL, this.keyPrefix, appId, type.getCode()); } @Override public String getAccessToken() { - try (Jedis jedis = this.jedisPool.getResource()) { - return jedis.get(this.accessTokenKey); - } + return redisOps.getValue(this.accessTokenKey); } @Override public boolean isAccessTokenExpired() { - try (Jedis jedis = this.jedisPool.getResource()) { - return jedis.ttl(accessTokenKey) < 2; - } + Long expire = redisOps.getExpire(this.accessTokenKey); + return expire == null || expire < 2; } @Override public synchronized void updateAccessToken(String accessToken, int expiresInSeconds) { - try (Jedis jedis = this.jedisPool.getResource()) { - jedis.setex(this.accessTokenKey, expiresInSeconds - 200, accessToken); - } + redisOps.setValue(this.accessTokenKey, accessToken, expiresInSeconds - 200, TimeUnit.SECONDS); } @Override public void expireAccessToken() { - try (Jedis jedis = this.jedisPool.getResource()) { - jedis.expire(this.accessTokenKey, 0); - } + redisOps.expire(this.accessTokenKey, 0, TimeUnit.SECONDS); } @Override public String getTicket(TicketType type) { - try (Jedis jedis = this.jedisPool.getResource()) { - return jedis.get(this.getTicketRedisKey(type)); - } + return redisOps.getValue(this.getTicketRedisKey(type)); } @Override public boolean isTicketExpired(TicketType type) { - try (Jedis jedis = this.jedisPool.getResource()) { - return jedis.ttl(this.getTicketRedisKey(type)) < 2; - } + return redisOps.getExpire(this.getTicketRedisKey(type)) < 2; } @Override public synchronized void updateTicket(TicketType type, String jsapiTicket, int expiresInSeconds) { - try (Jedis jedis = this.jedisPool.getResource()) { - jedis.setex(this.getTicketRedisKey(type), expiresInSeconds - 200, jsapiTicket); - } + redisOps.setValue(this.getTicketRedisKey(type), jsapiTicket, expiresInSeconds - 200, TimeUnit.SECONDS); } @Override public void expireTicket(TicketType type) { - try (Jedis jedis = this.jedisPool.getResource()) { - jedis.expire(this.getTicketRedisKey(type), 0); - } + redisOps.expire(this.getTicketRedisKey(type), 0, TimeUnit.SECONDS); } } diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/constant/WxMpEventConstants.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/constant/WxMpEventConstants.java index 0019816a20..4d7ef4beb5 100644 --- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/constant/WxMpEventConstants.java +++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/constant/WxMpEventConstants.java @@ -126,4 +126,19 @@ public static class Qualification { public static final String VERIFY_EXPIRED = "verify_expired"; } + /** + * 电子发票. + */ + public static class Invoice { + /** + * 用户授权事件. + */ + public static final String USER_AUTHORIZE_INVOICE = "user_authorize_invoice"; + + /** + * 统一开票接口-异步通知开票结果. + */ + public static final String CLOUD_INVOICE_INVOICERESULT_EVENT = "cloud_invoice_invoiceresult_event"; + } + } diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/enums/WxMpApiUrl.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/enums/WxMpApiUrl.java index cbe3d4df92..9bc0e1de03 100644 --- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/enums/WxMpApiUrl.java +++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/enums/WxMpApiUrl.java @@ -1082,4 +1082,78 @@ public String getUrl(WxMpConfigStorage config) { } } + @AllArgsConstructor + enum Invoice implements WxMpApiUrl { + + /** + * 获取用户开票授权地址 + */ + GET_AUTH_URL(API_DEFAULT_HOST_URL, "/card/invoice/getauthurl"), + + /** + * 获取用户开票授权信息 + */ + GET_AUTH_DATA(API_DEFAULT_HOST_URL, "/card/invoice/getauthdata"), + + /** + * 拒绝为用户开票 + */ + REJECT_INSERT(API_DEFAULT_HOST_URL, "/card/invoice/rejectinsert"), + + /** + * 开票 + */ + MAKE_OUT_INVOICE(API_DEFAULT_HOST_URL, "/card/invoice/makeoutinvoice"), + + /** + * 发票冲红 + */ + CLEAR_OUT_INVOICE(API_DEFAULT_HOST_URL, "/card/invoice/clearoutinvoice"), + + /** + * 查询发票信息 + */ + QUERY_INVOICE_INFO(API_DEFAULT_HOST_URL, "/card/invoice/queryinvoceinfo"), + + /** + * 设置商户信息联系 + */ + SET_CONTACT_SET_BIZ_ATTR(API_DEFAULT_HOST_URL, "/card/invoice/setbizattr?action=set_contact"), + + /** + * 获取商户联系信息 + */ + GET_CONTACT_SET_BIZ_ATTR(API_DEFAULT_HOST_URL, "/card/invoice/setbizattr?action=get_contact"), + + /** + * 设置授权页面字段 + */ + SET_AUTH_FIELD_SET_BIZ_ATTR(API_DEFAULT_HOST_URL, "/card/invoice/setbizattr?action=set_auth_field"), + + /** + * 获取授权页面字段 + */ + GET_AUTH_FIELD_SET_BIZ_ATTR(API_DEFAULT_HOST_URL, "/card/invoice/setbizattr?action=get_auth_field"), + + /** + * 设置关联商户 + */ + SET_PAY_MCH_SET_BIZ_ATTR(API_DEFAULT_HOST_URL, "/card/invoice/setbizattr?action=set_pay_mch"), + + /** + * 获取关联商户 + */ + GET_PAY_MCH_SET_BIZ_ATTR(API_DEFAULT_HOST_URL, "/card/invoice/setbizattr?action=get_pay_mch"), + ; + private String prefix; + private String path; + + @Override + public String getUrl(WxMpConfigStorage config) { + if (null == config) { + return buildUrl(null, prefix, path); + } + return buildUrl(config.getHostConfig(), prefix, path); + } + } } diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/WxMpConfigStorageHolder.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/WxMpConfigStorageHolder.java index af2f9226db..e844c4866b 100644 --- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/WxMpConfigStorageHolder.java +++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/WxMpConfigStorageHolder.java @@ -1,13 +1,11 @@ package me.chanjar.weixin.mp.util; - /** - * @Author: yd - * @Date: 2019-03-20 22:06 + * @author yd + * @date 2019-03-20 22:06 */ public class WxMpConfigStorageHolder { - - private final static ThreadLocalWX_MP_CONFIG_STORAGE_CHOSE = new ThreadLocal () { + private final static ThreadLocal THREAD_LOCAL = new ThreadLocal () { @Override protected String initialValue() { return "default"; @@ -15,11 +13,17 @@ protected String initialValue() { }; public static String get() { - return WX_MP_CONFIG_STORAGE_CHOSE.get(); + return THREAD_LOCAL.get(); } public static void set(String label) { - WX_MP_CONFIG_STORAGE_CHOSE.set(label); + THREAD_LOCAL.set(label); } + /** + * 此方法需要用户根据自己程序代码,在适当位置手动触发调用,本SDK里无法判断调用时机 + */ + public static void remove() { + THREAD_LOCAL.remove(); + } } diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/json/WxMpGsonBuilder.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/json/WxMpGsonBuilder.java index 1d1ae9095f..b7a4df93f5 100644 --- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/json/WxMpGsonBuilder.java +++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/json/WxMpGsonBuilder.java @@ -8,10 +8,10 @@ import me.chanjar.weixin.mp.bean.datacube.WxDataCubeUserCumulate; import me.chanjar.weixin.mp.bean.datacube.WxDataCubeUserSummary; import me.chanjar.weixin.mp.bean.kefu.WxMpKefuMessage; +import me.chanjar.weixin.mp.bean.material.*; import me.chanjar.weixin.mp.bean.membercard.WxMpMemberCardActivateTempInfoResult; import me.chanjar.weixin.mp.bean.membercard.WxMpMemberCardUpdateResult; import me.chanjar.weixin.mp.bean.membercard.WxMpMemberCardUserInfoResult; -import me.chanjar.weixin.mp.bean.material.*; import me.chanjar.weixin.mp.bean.result.*; import me.chanjar.weixin.mp.bean.subscribe.WxMpSubscribeMessage; import me.chanjar.weixin.mp.bean.template.WxMpTemplateIndustry; @@ -45,11 +45,10 @@ public class WxMpGsonBuilder { INSTANCE.registerTypeAdapter(WxDataCubeUserCumulate.class, new WxMpUserCumulateGsonAdapter()); INSTANCE.registerTypeAdapter(WxMpMaterialUploadResult.class, new WxMpMaterialUploadResultAdapter()); INSTANCE.registerTypeAdapter(WxMpMaterialVideoInfoResult.class, new WxMpMaterialVideoInfoResultAdapter()); - INSTANCE.registerTypeAdapter(WxMpMassNews.WxMpMassNewsArticle.class, new WxMpMassNewsArticleGsonAdapter()); INSTANCE.registerTypeAdapter(WxMpMaterialArticleUpdate.class, new WxMpMaterialArticleUpdateGsonAdapter()); INSTANCE.registerTypeAdapter(WxMpMaterialCountResult.class, new WxMpMaterialCountResultAdapter()); INSTANCE.registerTypeAdapter(WxMpMaterialNews.class, new WxMpMaterialNewsGsonAdapter()); - INSTANCE.registerTypeAdapter(WxMpMaterialNews.WxMpMaterialNewsArticle.class, new WxMpMaterialNewsArticleGsonAdapter()); + INSTANCE.registerTypeAdapter(WxMpNewsArticle.class, new WxMpNewsArticleGsonAdapter()); INSTANCE.registerTypeAdapter(WxMpMaterialNewsBatchGetResult.class, new WxMpMaterialNewsBatchGetGsonAdapter()); INSTANCE.registerTypeAdapter(WxMpMaterialNewsBatchGetResult.WxMaterialNewsBatchGetNewsItem.class, new WxMpMaterialNewsBatchGetGsonItemAdapter()); INSTANCE.registerTypeAdapter(WxMpMaterialFileBatchGetResult.class, new WxMpMaterialFileBatchGetGsonAdapter()); diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/json/WxMpIndustryGsonAdapter.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/json/WxMpIndustryGsonAdapter.java index 04d9d7dadd..9c418c2a68 100644 --- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/json/WxMpIndustryGsonAdapter.java +++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/json/WxMpIndustryGsonAdapter.java @@ -29,6 +29,11 @@ public WxMpTemplateIndustry deserialize(JsonElement jsonElement, Type type, Json private WxMpTemplateIndustryEnum convertFromJson(JsonObject json) { String secondClass = GsonHelper.getString(json, "second_class"); + final WxMpTemplateIndustryEnum industryEnum = WxMpTemplateIndustryEnum.findBySecondary(secondClass); + if (industryEnum != null) { + return industryEnum; + } + if (secondClass.contains("|")) { secondClass = secondClass.split("\\|")[1]; } diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/json/WxMpMassNewsArticleGsonAdapter.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/json/WxMpMassNewsArticleGsonAdapter.java deleted file mode 100644 index 70343de94d..0000000000 --- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/json/WxMpMassNewsArticleGsonAdapter.java +++ /dev/null @@ -1,70 +0,0 @@ -package me.chanjar.weixin.mp.util.json; - -import com.google.gson.*; -import me.chanjar.weixin.common.util.json.GsonHelper; -import me.chanjar.weixin.mp.bean.WxMpMassNews; -import org.apache.commons.lang3.BooleanUtils; - -import java.lang.reflect.Type; - -/** - * @author codepiano - */ -public class WxMpMassNewsArticleGsonAdapter implements JsonSerializer , JsonDeserializer { - - @Override - public JsonElement serialize(WxMpMassNews.WxMpMassNewsArticle article, Type typeOfSrc, JsonSerializationContext context) { - JsonObject articleJson = new JsonObject(); - - articleJson.addProperty("thumb_media_id", article.getThumbMediaId()); - articleJson.addProperty("title", article.getTitle()); - articleJson.addProperty("content", article.getContent()); - if (null != article.getAuthor()) { - articleJson.addProperty("author", article.getAuthor()); - } - if (null != article.getContentSourceUrl()) { - articleJson.addProperty("content_source_url", article.getContentSourceUrl()); - } - if (null != article.getDigest()) { - articleJson.addProperty("digest", article.getDigest()); - } - articleJson.addProperty("show_cover_pic", article.isShowCoverPic() ? "1" : "0"); - return articleJson; - } - - @Override - public WxMpMassNews.WxMpMassNewsArticle deserialize(JsonElement jsonElement, Type type, JsonDeserializationContext jsonDeserializationContext) throws JsonParseException { - JsonObject articleInfo = jsonElement.getAsJsonObject(); - WxMpMassNews.WxMpMassNewsArticle article = new WxMpMassNews.WxMpMassNewsArticle(); - - JsonElement title = articleInfo.get("title"); - if (title != null && !title.isJsonNull()) { - article.setTitle(GsonHelper.getAsString(title)); - } - JsonElement content = articleInfo.get("content"); - if (content != null && !content.isJsonNull()) { - article.setContent(GsonHelper.getAsString(content)); - } - JsonElement contentSourceUrl = articleInfo.get("content_source_url"); - if (contentSourceUrl != null && !contentSourceUrl.isJsonNull()) { - article.setContentSourceUrl(GsonHelper.getAsString(contentSourceUrl)); - } - JsonElement author = articleInfo.get("author"); - if (author != null && !author.isJsonNull()) { - article.setAuthor(GsonHelper.getAsString(author)); - } - JsonElement digest = articleInfo.get("digest"); - if (digest != null && !digest.isJsonNull()) { - article.setDigest(GsonHelper.getAsString(digest)); - } - JsonElement thumbMediaId = articleInfo.get("thumb_media_id"); - if (thumbMediaId != null && !thumbMediaId.isJsonNull()) { - article.setThumbMediaId(GsonHelper.getAsString(thumbMediaId)); - } - JsonElement showCoverPic = articleInfo.get("show_cover_pic"); - if (showCoverPic != null && !showCoverPic.isJsonNull()) { - article.setShowCoverPic(BooleanUtils.toBoolean(showCoverPic.getAsInt())); - } - return article; - } -} diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/json/WxMpMassNewsGsonAdapter.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/json/WxMpMassNewsGsonAdapter.java index b780300e4e..015ad9262a 100644 --- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/json/WxMpMassNewsGsonAdapter.java +++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/json/WxMpMassNewsGsonAdapter.java @@ -2,6 +2,7 @@ import com.google.gson.*; import me.chanjar.weixin.mp.bean.WxMpMassNews; +import me.chanjar.weixin.mp.bean.material.WxMpNewsArticle; import java.lang.reflect.Type; @@ -12,8 +13,8 @@ public JsonElement serialize(WxMpMassNews message, Type typeOfSrc, JsonSerializa JsonObject newsJson = new JsonObject(); JsonArray articleJsonArray = new JsonArray(); - for (WxMpMassNews.WxMpMassNewsArticle article : message.getArticles()) { - JsonObject articleJson = WxMpGsonBuilder.create().toJsonTree(article, WxMpMassNews.WxMpMassNewsArticle.class).getAsJsonObject(); + for (WxMpNewsArticle article : message.getArticles()) { + JsonObject articleJson = WxMpGsonBuilder.create().toJsonTree(article, WxMpNewsArticle.class).getAsJsonObject(); articleJsonArray.add(articleJson); } newsJson.add("articles", articleJsonArray); @@ -29,7 +30,7 @@ public WxMpMassNews deserialize(JsonElement jsonElement, Type type, JsonDeserial JsonArray articles = json.getAsJsonArray("articles"); for (JsonElement article1 : articles) { JsonObject articleInfo = article1.getAsJsonObject(); - WxMpMassNews.WxMpMassNewsArticle article = WxMpGsonBuilder.create().fromJson(articleInfo, WxMpMassNews.WxMpMassNewsArticle.class); + WxMpNewsArticle article = WxMpGsonBuilder.create().fromJson(articleInfo, WxMpNewsArticle.class); wxMpMassNews.addArticle(article); } } diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/json/WxMpMaterialArticleUpdateGsonAdapter.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/json/WxMpMaterialArticleUpdateGsonAdapter.java index bdbbde561d..cb7784e9f5 100644 --- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/json/WxMpMaterialArticleUpdateGsonAdapter.java +++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/json/WxMpMaterialArticleUpdateGsonAdapter.java @@ -5,7 +5,7 @@ import com.google.gson.JsonSerializationContext; import com.google.gson.JsonSerializer; import me.chanjar.weixin.mp.bean.material.WxMpMaterialArticleUpdate; -import me.chanjar.weixin.mp.bean.material.WxMpMaterialNews; +import me.chanjar.weixin.mp.bean.material.WxMpNewsArticle; import java.lang.reflect.Type; @@ -16,7 +16,7 @@ public JsonElement serialize(WxMpMaterialArticleUpdate wxMpMaterialArticleUpdate JsonObject articleUpdateJson = new JsonObject(); articleUpdateJson.addProperty("media_id", wxMpMaterialArticleUpdate.getMediaId()); articleUpdateJson.addProperty("index", wxMpMaterialArticleUpdate.getIndex()); - articleUpdateJson.add("articles", WxMpGsonBuilder.create().toJsonTree(wxMpMaterialArticleUpdate.getArticles(), WxMpMaterialNews.WxMpMaterialNewsArticle.class)); + articleUpdateJson.add("articles", WxMpGsonBuilder.create().toJsonTree(wxMpMaterialArticleUpdate.getArticles(), WxMpNewsArticle.class)); return articleUpdateJson; } diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/json/WxMpMaterialNewsGsonAdapter.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/json/WxMpMaterialNewsGsonAdapter.java index c406360f6f..3204090b75 100644 --- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/json/WxMpMaterialNewsGsonAdapter.java +++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/json/WxMpMaterialNewsGsonAdapter.java @@ -3,6 +3,7 @@ import com.google.gson.*; import me.chanjar.weixin.common.util.json.GsonHelper; import me.chanjar.weixin.mp.bean.material.WxMpMaterialNews; +import me.chanjar.weixin.mp.bean.material.WxMpNewsArticle; import java.lang.reflect.Type; import java.text.SimpleDateFormat; @@ -15,8 +16,8 @@ public JsonElement serialize(WxMpMaterialNews wxMpMaterialNews, Type typeOfSrc, JsonObject newsJson = new JsonObject(); JsonArray articleJsonArray = new JsonArray(); - for (WxMpMaterialNews.WxMpMaterialNewsArticle article : wxMpMaterialNews.getArticles()) { - JsonObject articleJson = WxMpGsonBuilder.create().toJsonTree(article, WxMpMaterialNews.WxMpMaterialNewsArticle.class).getAsJsonObject(); + for (WxMpNewsArticle article : wxMpMaterialNews.getArticles()) { + JsonObject articleJson = WxMpGsonBuilder.create().toJsonTree(article, WxMpNewsArticle.class).getAsJsonObject(); articleJsonArray.add(articleJson); } newsJson.add("articles", articleJsonArray); @@ -42,7 +43,7 @@ public WxMpMaterialNews deserialize(JsonElement jsonElement, Type type, JsonDese JsonArray articles = json.getAsJsonArray("news_item"); for (JsonElement article1 : articles) { JsonObject articleInfo = article1.getAsJsonObject(); - WxMpMaterialNews.WxMpMaterialNewsArticle article = WxMpGsonBuilder.create().fromJson(articleInfo, WxMpMaterialNews.WxMpMaterialNewsArticle.class); + WxMpNewsArticle article = WxMpGsonBuilder.create().fromJson(articleInfo, WxMpNewsArticle.class); wxMpMaterialNews.addArticle(article); } } diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/json/WxMpMaterialNewsArticleGsonAdapter.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/json/WxMpNewsArticleGsonAdapter.java similarity index 84% rename from weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/json/WxMpMaterialNewsArticleGsonAdapter.java rename to weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/json/WxMpNewsArticleGsonAdapter.java index faad5ec528..ff88cb9cbd 100644 --- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/json/WxMpMaterialNewsArticleGsonAdapter.java +++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/json/WxMpNewsArticleGsonAdapter.java @@ -2,7 +2,7 @@ import com.google.gson.*; import me.chanjar.weixin.common.util.json.GsonHelper; -import me.chanjar.weixin.mp.bean.material.WxMpMaterialNews; +import me.chanjar.weixin.mp.bean.material.WxMpNewsArticle; import org.apache.commons.lang3.BooleanUtils; import java.lang.reflect.Type; @@ -10,10 +10,9 @@ /** * @author codepiano */ -public class WxMpMaterialNewsArticleGsonAdapter implements JsonSerializer , JsonDeserializer { - +public class WxMpNewsArticleGsonAdapter implements JsonSerializer , JsonDeserializer { @Override - public JsonElement serialize(WxMpMaterialNews.WxMpMaterialNewsArticle article, Type typeOfSrc, JsonSerializationContext context) { + public JsonElement serialize(WxMpNewsArticle article, Type typeOfSrc, JsonSerializationContext context) { JsonObject articleJson = new JsonObject(); articleJson.addProperty("thumb_media_id", article.getThumbMediaId()); @@ -47,9 +46,9 @@ public JsonElement serialize(WxMpMaterialNews.WxMpMaterialNewsArticle article, T } @Override - public WxMpMaterialNews.WxMpMaterialNewsArticle deserialize(JsonElement jsonElement, Type type, JsonDeserializationContext jsonDeserializationContext) throws JsonParseException { + public WxMpNewsArticle deserialize(JsonElement jsonElement, Type type, JsonDeserializationContext jsonDeserializationContext) throws JsonParseException { JsonObject articleInfo = jsonElement.getAsJsonObject(); - WxMpMaterialNews.WxMpMaterialNewsArticle article = new WxMpMaterialNews.WxMpMaterialNewsArticle(); + WxMpNewsArticle article = new WxMpNewsArticle(); JsonElement title = articleInfo.get("title"); if (title != null && !title.isJsonNull()) { diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/qrcode/QrCodeJoddHttpRequestExecutor.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/qrcode/QrCodeJoddHttpRequestExecutor.java index 6945575021..2cbbc20e24 100644 --- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/qrcode/QrCodeJoddHttpRequestExecutor.java +++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/qrcode/QrCodeJoddHttpRequestExecutor.java @@ -4,7 +4,7 @@ import jodd.http.HttpRequest; import jodd.http.HttpResponse; import jodd.http.ProxyInfo; -import jodd.util.MimeTypes; +import jodd.net.MimeTypes; import jodd.util.StringPool; import me.chanjar.weixin.common.WxType; diff --git a/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/impl/BaseWxMpServiceImplTest.java b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/impl/BaseWxMpServiceImplTest.java index e0b2471bd0..f35a7d4658 100644 --- a/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/impl/BaseWxMpServiceImplTest.java +++ b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/impl/BaseWxMpServiceImplTest.java @@ -151,4 +151,300 @@ public void run() { assertEquals(set.size(), 1); } + + @Test + public void testCheckSignature() { + } + + @Test + public void testGetTicket() { + } + + @Test + public void testTestGetTicket() { + } + + @Test + public void testGetJsapiTicket() { + } + + @Test + public void testTestGetJsapiTicket() { + } + + @Test + public void testCreateJsapiSignature() { + } + + @Test + public void testGetAccessToken() { + } + + @Test + public void testSemanticQuery() { + } + + @Test + public void testOauth2buildAuthorizationUrl() { + } + + @Test + public void testBuildQrConnectUrl() { + } + + @Test + public void testOauth2getAccessToken() { + } + + @Test + public void testOauth2refreshAccessToken() { + } + + @Test + public void testOauth2getUserInfo() { + } + + @Test + public void testOauth2validateAccessToken() { + } + + @Test + public void testGetCurrentAutoReplyInfo() { + } + + @Test + public void testClearQuota() { + } + + @Test + public void testGet() { + } + + @Test + public void testTestGet() { + } + + @Test + public void testPost() { + } + + @Test + public void testTestPost() { + } + + @Test + public void testExecute() { + } + + @Test + public void testTestExecute() { + } + + @Test + public void testExecuteInternal() { + } + + @Test + public void testGetWxMpConfigStorage() { + } + + @Test + public void testSetWxMpConfigStorage() { + } + + @Test + public void testSetMultiConfigStorages() { + } + + @Test + public void testTestSetMultiConfigStorages() { + } + + @Test + public void testAddConfigStorage() { + } + + @Test + public void testRemoveConfigStorage() { + } + + @Test + public void testSetRetrySleepMillis() { + } + + @Test + public void testSetMaxRetryTimes() { + } + + @Test + public void testGetKefuService() { + } + + @Test + public void testGetMaterialService() { + } + + @Test + public void testGetMenuService() { + } + + @Test + public void testGetUserService() { + } + + @Test + public void testGetUserTagService() { + } + + @Test + public void testGetQrcodeService() { + } + + @Test + public void testGetCardService() { + } + + @Test + public void testGetDataCubeService() { + } + + @Test + public void testGetBlackListService() { + } + + @Test + public void testGetStoreService() { + } + + @Test + public void testGetTemplateMsgService() { + } + + @Test + public void testGetSubscribeMsgService() { + } + + @Test + public void testGetDeviceService() { + } + + @Test + public void testGetShakeService() { + } + + @Test + public void testGetMemberCardService() { + } + + @Test + public void testGetRequestHttp() { + } + + @Test + public void testGetMassMessageService() { + } + + @Test + public void testSetKefuService() { + } + + @Test + public void testSetMaterialService() { + } + + @Test + public void testSetMenuService() { + } + + @Test + public void testSetUserService() { + } + + @Test + public void testSetTagService() { + } + + @Test + public void testSetQrCodeService() { + } + + @Test + public void testSetCardService() { + } + + @Test + public void testSetStoreService() { + } + + @Test + public void testSetDataCubeService() { + } + + @Test + public void testSetBlackListService() { + } + + @Test + public void testSetTemplateMsgService() { + } + + @Test + public void testSetDeviceService() { + } + + @Test + public void testSetShakeService() { + } + + @Test + public void testSetMemberCardService() { + } + + @Test + public void testSetMassMessageService() { + } + + @Test + public void testGetAiOpenService() { + } + + @Test + public void testSetAiOpenService() { + } + + @Test + public void testGetWifiService() { + } + + @Test + public void testGetOcrService() { + } + + @Test + public void testGetMarketingService() { + } + + @Test + public void testSetMarketingService() { + } + + @Test + public void testSetOcrService() { + } + + @Test + public void testGetCommentService() { + } + + @Test + public void testSetCommentService() { + } + + @Test + public void testGetImgProcService() { + } + + @Test + public void testSetImgProcService() { + } } diff --git a/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/impl/WxMpCardServiceImplTest.java b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/impl/WxMpCardServiceImplTest.java index 4fa75b7f98..ecacc36de5 100644 --- a/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/impl/WxMpCardServiceImplTest.java +++ b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/impl/WxMpCardServiceImplTest.java @@ -1,5 +1,6 @@ package me.chanjar.weixin.mp.api.impl; +import com.google.common.collect.Lists; import com.google.inject.Inject; import me.chanjar.weixin.common.bean.WxCardApiSignature; import me.chanjar.weixin.common.error.WxErrorException; @@ -134,7 +135,7 @@ public void testCreateGrouponCard() throws WxErrorException { base.setCustomUrlSubTitle("副标题tip"); base.setPromotionUrlName("更多优惠"); base.setPromotionUrl("http://www.qq.com"); - base.setLocationIdList("1234"); + base.setLocationIdList(Lists.newArrayList("1234")); //团购券 WxMpCardCreateRequest grouponMessage = new WxMpCardCreateRequest(); diff --git a/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/impl/WxMpMassMessageServiceImplTest.java b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/impl/WxMpMassMessageServiceImplTest.java index f2d7683021..b31cc748ea 100644 --- a/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/impl/WxMpMassMessageServiceImplTest.java +++ b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/impl/WxMpMassMessageServiceImplTest.java @@ -12,14 +12,17 @@ import me.chanjar.weixin.mp.bean.WxMpMassOpenIdsMessage; import me.chanjar.weixin.mp.bean.WxMpMassTagMessage; import me.chanjar.weixin.mp.bean.WxMpMassVideo; +import me.chanjar.weixin.mp.bean.material.WxMpNewsArticle; import me.chanjar.weixin.mp.bean.result.WxMpMassSendResult; import me.chanjar.weixin.mp.bean.result.WxMpMassUploadResult; -import org.testng.annotations.*; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Guice; +import org.testng.annotations.Test; import java.io.IOException; import java.io.InputStream; -import static org.testng.Assert.*; +import static org.testng.Assert.assertNotNull; /** * 测试群发消息 @@ -35,15 +38,13 @@ public class WxMpMassMessageServiceImplTest { @Test public void testTextMassOpenIdsMessageSend() throws WxErrorException { // 发送群发消息 - TestConfigStorage configProvider = (TestConfigStorage) this.wxService - .getWxMpConfigStorage(); + TestConfigStorage configProvider = (TestConfigStorage) this.wxService .getWxMpConfigStorage(); WxMpMassOpenIdsMessage massMessage = new WxMpMassOpenIdsMessage(); massMessage.setMsgType(WxConsts.MassMsgType.TEXT); - massMessage.setContent("测试群发消息\n欢迎欢迎,热烈欢迎\n换行测试\n超链接:Hello World"); + massMessage.setContent("测试群发消息\n欢迎欢迎\n换行测试\n超链接:Hello World"); massMessage.getToUsers().add(configProvider.getOpenid()); - WxMpMassSendResult massResult = this.wxService.getMassMessageService() - .massOpenIdsMessageSend(massMessage); + WxMpMassSendResult massResult = this.wxService.getMassMessageService().massOpenIdsMessageSend(massMessage); assertNotNull(massResult); assertNotNull(massResult.getMsgId()); } @@ -51,15 +52,13 @@ public void testTextMassOpenIdsMessageSend() throws WxErrorException { @Test(dataProvider = "massMessages") public void testMediaMassOpenIdsMessageSend(String massMsgType, String mediaId) throws WxErrorException { // 发送群发消息 - TestConfigStorage configProvider = (TestConfigStorage) this.wxService - .getWxMpConfigStorage(); + TestConfigStorage configProvider = (TestConfigStorage) this.wxService.getWxMpConfigStorage(); WxMpMassOpenIdsMessage massMessage = new WxMpMassOpenIdsMessage(); massMessage.setMsgType(massMsgType); massMessage.setMediaId(mediaId); massMessage.getToUsers().add(configProvider.getOpenid()); - WxMpMassSendResult massResult = this.wxService.getMassMessageService() - .massOpenIdsMessageSend(massMessage); + WxMpMassSendResult massResult = this.wxService.getMassMessageService().massOpenIdsMessageSend(massMessage); assertNotNull(massResult); assertNotNull(massResult.getMsgId()); } @@ -68,26 +67,23 @@ public void testMediaMassOpenIdsMessageSend(String massMsgType, String mediaId) public void testTextMassGroupMessageSend() throws WxErrorException { WxMpMassTagMessage massMessage = new WxMpMassTagMessage(); massMessage.setMsgType(WxConsts.MassMsgType.TEXT); - massMessage.setContent("测试群发消息\n欢迎欢迎,热烈欢迎\n换行测试\n超链接:Hello World"); + massMessage.setContent("测试群发消息\n欢迎欢迎\n换行测试\n超链接:Hello World"); massMessage .setTagId(this.wxService.getUserTagService().tagGet().get(0).getId()); - WxMpMassSendResult massResult = this.wxService.getMassMessageService() - .massGroupMessageSend(massMessage); + WxMpMassSendResult massResult = this.wxService.getMassMessageService().massGroupMessageSend(massMessage); assertNotNull(massResult); assertNotNull(massResult.getMsgId()); } @Test(dataProvider = "massMessages") - public void testMediaMassGroupMessageSend(String massMsgType, String mediaId) - throws WxErrorException { + public void testMediaMassGroupMessageSend(String massMsgType, String mediaId) throws WxErrorException { WxMpMassTagMessage massMessage = new WxMpMassTagMessage(); massMessage.setMsgType(massMsgType); massMessage.setMediaId(mediaId); massMessage.setTagId(this.wxService.getUserTagService().tagGet().get(0).getId()); - WxMpMassSendResult massResult = this.wxService.getMassMessageService() - .massGroupMessageSend(massMessage); + WxMpMassSendResult massResult = this.wxService.getMassMessageService().massGroupMessageSend(massMessage); assertNotNull(massResult); assertNotNull(massResult.getMsgId()); } @@ -155,15 +151,15 @@ public Object[][] massMessages() throws WxErrorException, IOException { // 上传图文消息 WxMpMassNews news = new WxMpMassNews(); - WxMpMassNews.WxMpMassNewsArticle article1 = new WxMpMassNews.WxMpMassNewsArticle(); + WxMpNewsArticle article1 = new WxMpNewsArticle(); article1.setTitle("标题1"); - article1.setContent("内容1内容1内容1内容1内容1内容1内容1内容1内容1内容1内容1内容1内容1内容1内容1内容1内容1内容1内容1内容1内容1"); + article1.setContent("内容1内容1内容1内容1内容1内容1内容1内容1内容1内容1内容1内容1内容1内容1内容1内容1内容1"); article1.setThumbMediaId(uploadMediaRes.getMediaId()); news.addArticle(article1); - WxMpMassNews.WxMpMassNewsArticle article2 = new WxMpMassNews.WxMpMassNewsArticle(); + WxMpNewsArticle article2 = new WxMpNewsArticle(); article2.setTitle("标题2"); - article2.setContent("内容2内容2内容2内容2内容2内容2内容2内容2内容2内容2内容2内容2内容2内容2内容2内容2内容2内容2内容2内容2内容2"); + article2.setContent("内容2内容2内容2内容2内容2内容2内容2内容2内2内容2内容2内容2内容2内容2内容2内容2内容2内容2"); article2.setThumbMediaId(uploadMediaRes.getMediaId()); article2.setShowCoverPic(true); article2.setAuthor("作者2"); @@ -183,7 +179,7 @@ public Object[][] massMessages() throws WxErrorException, IOException { @Test public void testMassDelete() throws Exception { - this.wxService.getMassMessageService().delete(1L,2); + this.wxService.getMassMessageService().delete(1L, 2); } } diff --git a/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/impl/WxMpMaterialServiceImplTest.java b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/impl/WxMpMaterialServiceImplTest.java index 6d14b336f5..707f1df311 100644 --- a/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/impl/WxMpMaterialServiceImplTest.java +++ b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/impl/WxMpMaterialServiceImplTest.java @@ -103,7 +103,7 @@ public void testUploadMaterial(String mediaType, String fileType, String fileNam public void testAddNews() throws WxErrorException { // 单图文消息 WxMpMaterialNews wxMpMaterialNewsSingle = new WxMpMaterialNews(); - WxMpMaterialNews.WxMpMaterialNewsArticle article = new WxMpMaterialNews.WxMpMaterialNewsArticle(); + WxMpNewsArticle article = new WxMpNewsArticle(); article.setAuthor("author"); article.setThumbMediaId(this.thumbMediaId); article.setTitle("single title"); @@ -115,7 +115,7 @@ public void testAddNews() throws WxErrorException { // 多图文消息 WxMpMaterialNews wxMpMaterialNewsMultiple = new WxMpMaterialNews(); - WxMpMaterialNews.WxMpMaterialNewsArticle article1 = new WxMpMaterialNews.WxMpMaterialNewsArticle(); + WxMpNewsArticle article1 = new WxMpNewsArticle(); article1.setAuthor("author1"); article1.setThumbMediaId(this.thumbMediaId); article1.setTitle("multi title1"); @@ -124,7 +124,7 @@ public void testAddNews() throws WxErrorException { article1.setShowCoverPic(true); article1.setDigest(""); - WxMpMaterialNews.WxMpMaterialNewsArticle article2 = new WxMpMaterialNews.WxMpMaterialNewsArticle(); + WxMpNewsArticle article2 = new WxMpNewsArticle(); article2.setAuthor("author2"); article2.setThumbMediaId(this.thumbMediaId); article2.setTitle("multi title2"); @@ -196,7 +196,7 @@ public void testUpdateNewsInfo() throws WxErrorException { .getMaterialService().materialNewsInfo(this.singleNewsMediaId); assertNotNull(wxMpMaterialNewsSingle); WxMpMaterialArticleUpdate wxMpMaterialArticleUpdateSingle = new WxMpMaterialArticleUpdate(); - WxMpMaterialNews.WxMpMaterialNewsArticle articleSingle = wxMpMaterialNewsSingle.getArticles().get(0); + WxMpNewsArticle articleSingle = wxMpMaterialNewsSingle.getArticles().get(0); articleSingle.setContent("content single update"); wxMpMaterialArticleUpdateSingle.setMediaId(this.singleNewsMediaId); wxMpMaterialArticleUpdateSingle.setArticles(articleSingle); @@ -213,7 +213,7 @@ public void testUpdateNewsInfo() throws WxErrorException { .getMaterialService().materialNewsInfo(this.multiNewsMediaId); assertNotNull(wxMpMaterialNewsMultiple); WxMpMaterialArticleUpdate wxMpMaterialArticleUpdateMulti = new WxMpMaterialArticleUpdate(); - WxMpMaterialNews.WxMpMaterialNewsArticle articleMulti = wxMpMaterialNewsMultiple.getArticles().get(1); + WxMpNewsArticle articleMulti = wxMpMaterialNewsMultiple.getArticles().get(1); articleMulti.setContent("content 2 update"); wxMpMaterialArticleUpdateMulti.setMediaId(this.multiNewsMediaId); wxMpMaterialArticleUpdateMulti.setArticles(articleMulti); diff --git a/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/impl/WxMpMemberCardServiceImplTest.java b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/impl/WxMpMemberCardServiceImplTest.java index 96eb671370..7f611ab8a9 100644 --- a/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/impl/WxMpMemberCardServiceImplTest.java +++ b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/impl/WxMpMemberCardServiceImplTest.java @@ -95,7 +95,6 @@ public void testGetUserInfo() throws Exception { @Test public void testUpdateUserMemberCard() throws Exception { WxMpMemberCardUpdateMessage updateMessage = new WxMpMemberCardUpdateMessage(); - updateMessage.setAddBounus(100); updateMessage.setBonus(1000); updateMessage.setCardId(cardId); updateMessage.setCode(code); diff --git a/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/impl/WxMpServiceImplTest.java b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/impl/WxMpServiceImplTest.java index a434144cc1..c450775d25 100644 --- a/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/impl/WxMpServiceImplTest.java +++ b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/impl/WxMpServiceImplTest.java @@ -12,7 +12,7 @@ import me.chanjar.weixin.mp.api.test.ApiTestModule; import me.chanjar.weixin.mp.api.test.TestConfigStorage; import me.chanjar.weixin.mp.bean.result.WxMpCurrentAutoReplyInfo; -import me.chanjar.weixin.mp.enums.TicketType; +import me.chanjar.weixin.common.enums.TicketType; import static org.testng.Assert.*; diff --git a/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/impl/WxMpTemplateMsgServiceImplTest.java b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/impl/WxMpTemplateMsgServiceImplTest.java index 181411df41..884e41b59d 100644 --- a/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/impl/WxMpTemplateMsgServiceImplTest.java +++ b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/impl/WxMpTemplateMsgServiceImplTest.java @@ -53,8 +53,8 @@ public void testGetIndustry() throws Exception { @Test public void testSetIndustry() throws Exception { - WxMpTemplateIndustry industry = new WxMpTemplateIndustry(WxMpTemplateIndustryEnum.findByCode(1), - WxMpTemplateIndustryEnum.findByCode(4)); + WxMpTemplateIndustry industry = new WxMpTemplateIndustry(WxMpTemplateIndustryEnum.findByCode(29), + WxMpTemplateIndustryEnum.findByCode(8)); boolean result = this.wxService.getTemplateMsgService().setIndustry(industry); Assert.assertTrue(result); } diff --git a/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/bean/message/WxMpXmlMessageTest.java b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/bean/message/WxMpXmlMessageTest.java index 50bb0ac09d..b8d8843624 100644 --- a/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/bean/message/WxMpXmlMessageTest.java +++ b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/bean/message/WxMpXmlMessageTest.java @@ -79,9 +79,9 @@ public void testFromXml() { assertEquals(wxMessage.getMediaId(), "media_id"); assertEquals(wxMessage.getFormat(), "Format"); assertEquals(wxMessage.getThumbMediaId(), "thumb_media_id"); - assertEquals(wxMessage.getLocationX(), 23.134521d); - assertEquals(wxMessage.getLocationY(), 113.358803d); - assertEquals(wxMessage.getScale(), 20d); + assertEquals(wxMessage.getLocationX().doubleValue(), 23.134521d); + assertEquals(wxMessage.getLocationY().doubleValue(), 113.358803d); + assertEquals(wxMessage.getScale().doubleValue(), 20d); assertEquals(wxMessage.getLabel(), "位置信息"); assertEquals(wxMessage.getDescription(), "公众平台官网链接"); assertEquals(wxMessage.getUrl(), "url"); @@ -89,9 +89,9 @@ public void testFromXml() { assertEquals(wxMessage.getEvent(), "subscribe"); assertEquals(wxMessage.getEventKey(), "qrscene_123123"); assertEquals(wxMessage.getTicket(), "TICKET"); - assertEquals(wxMessage.getLatitude(), 23.137466); - assertEquals(wxMessage.getLongitude(), 113.352425); - assertEquals(wxMessage.getPrecision(), 119.385040); + assertEquals(wxMessage.getLatitude().doubleValue(), 23.137466); + assertEquals(wxMessage.getLongitude().doubleValue(), 113.352425); + assertEquals(wxMessage.getPrecision().doubleValue(), 119.385040); assertEquals(wxMessage.getScanCodeInfo().getScanType(), "qrcode"); assertEquals(wxMessage.getScanCodeInfo().getScanResult(), "1"); assertEquals(wxMessage.getSendPicsInfo().getCount(), new Long(1L)); @@ -170,9 +170,9 @@ public void testFromXml2() { assertEquals(wxMessage.getMediaId(), "media_id"); assertEquals(wxMessage.getFormat(), "Format"); assertEquals(wxMessage.getThumbMediaId(), "thumb_media_id"); - assertEquals(wxMessage.getLocationX(), 23.134521d); - assertEquals(wxMessage.getLocationY(), 113.358803d); - assertEquals(wxMessage.getScale(), 20d); + assertEquals(wxMessage.getLocationX().doubleValue(), 23.134521d); + assertEquals(wxMessage.getLocationY().doubleValue(), 113.358803d); + assertEquals(wxMessage.getScale().doubleValue(), 20d); assertEquals(wxMessage.getLabel(), "位置信息"); assertEquals(wxMessage.getDescription(), "公众平台官网链接"); assertEquals(wxMessage.getUrl(), "url"); @@ -180,9 +180,9 @@ public void testFromXml2() { assertEquals(wxMessage.getEvent(), "subscribe"); assertEquals(wxMessage.getEventKey(), "qrscene_123123"); assertEquals(wxMessage.getTicket(), "TICKET"); - assertEquals(wxMessage.getLatitude(), 23.137466); - assertEquals(wxMessage.getLongitude(), 113.352425); - assertEquals(wxMessage.getPrecision(), 119.385040); + assertEquals(wxMessage.getLatitude().doubleValue(), 23.137466); + assertEquals(wxMessage.getLongitude().doubleValue(), 113.352425); + assertEquals(wxMessage.getPrecision().doubleValue(), 119.385040); assertEquals(wxMessage.getScanCodeInfo().getScanType(), "qrcode"); assertEquals(wxMessage.getScanCodeInfo().getScanResult(), "1"); assertEquals(wxMessage.getSendPicsInfo().getCount(), new Long(1L)); diff --git a/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/bean/template/WxMpTemplateIndustryTest.java b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/bean/template/WxMpTemplateIndustryTest.java index d5affa288b..c2ae722977 100644 --- a/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/bean/template/WxMpTemplateIndustryTest.java +++ b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/bean/template/WxMpTemplateIndustryTest.java @@ -3,7 +3,6 @@ import org.testng.annotations.Test; import static org.assertj.core.api.Assertions.assertThat; -import static org.testng.Assert.*; /** * 测试类. @@ -13,11 +12,21 @@ */ public class WxMpTemplateIndustryTest { - @Test - public void testFromJson() { - String json="{\"primary_industry\":{\"first_class\":\"IT科技\",\"second_class\":\"IT软件与服务\"},\"secondary_industry\":{\"first_class\":\"房地产\",\"second_class\":\"房地产|建筑\"}}"; - final WxMpTemplateIndustry industry = WxMpTemplateIndustry.fromJson(json); - assertThat(industry).isNotNull(); - System.out.println(industry); - } + @Test + public void testFromJson() { + String json = "{\"primary_industry\":{\"first_class\":\"IT科技\",\"second_class\":\"互联网|电子商务\"}," + + "\"secondary_industry\":{\"first_class\":\"房地产\",\"second_class\":\"房地产|建筑\"}}"; + final WxMpTemplateIndustry industry = WxMpTemplateIndustry.fromJson(json); + assertThat(industry).isNotNull(); + System.out.println(industry); + } + + @Test + public void testFromJson_another_example() { + String json = "{\"primary_industry\":{\"first_class\":\"金融业\",\"second_class\":\"基金理财信托\"}," + + "\"secondary_industry\":{\"first_class\":\"房地产\",\"second_class\":\"建筑\"}}"; + final WxMpTemplateIndustry industry = WxMpTemplateIndustry.fromJson(json); + assertThat(industry).isNotNull(); + System.out.println(industry); + } } diff --git a/weixin-java-open/pom.xml b/weixin-java-open/pom.xml index 825a1e1a89..b03d8b3307 100644 --- a/weixin-java-open/pom.xml +++ b/weixin-java-open/pom.xml @@ -7,7 +7,7 @@ com.github.binarywang wx-java -3.7.0 +3.8.0 weixin-java-open diff --git a/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/WxOpenConfigStorage.java b/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/WxOpenConfigStorage.java index 79969228ad..5a51c3bfa1 100644 --- a/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/WxOpenConfigStorage.java +++ b/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/WxOpenConfigStorage.java @@ -6,6 +6,8 @@ import me.chanjar.weixin.open.bean.WxOpenAuthorizerAccessToken; import me.chanjar.weixin.open.bean.WxOpenComponentAccessToken; +import java.util.concurrent.locks.Lock; + /** * @author 007 */ @@ -53,6 +55,10 @@ public interface WxOpenConfigStorage { WxMaConfig getWxMaConfig(String appId); + Lock getComponentAccessTokenLock(); + + Lock getLockByKey(String key); + /** * 应该是线程安全的 * diff --git a/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/WxOpenMaService.java b/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/WxOpenMaService.java index 2b6e2d13a2..c64156d04a 100644 --- a/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/WxOpenMaService.java +++ b/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/WxOpenMaService.java @@ -295,6 +295,14 @@ public interface WxOpenMaService extends WxMaService { */ WxOpenResult unbindTester(String wechatid) throws WxErrorException; + /** + * 解除绑定小程序体验者,其他平台绑定的体验者无法获取到wechatid,可用此方法解绑,详见文档 + * https://developers.weixin.qq.com/doc/oplatform/Third-party_Platforms/Mini_Programs/unbind_tester.html + * + * @param userstr 人员对应的唯一字符串, 可通过获取已绑定的体验者列表获取人员对应的字符串 + */ + WxOpenResult unbindTesterByUserstr(String userstr) throws WxErrorException; + /** * 获得体验者列表 */ diff --git a/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/AbstractWxOpenInRedisConfigStorage.java b/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/AbstractWxOpenInRedisConfigStorage.java index 0d0b1bf255..52799da57c 100644 --- a/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/AbstractWxOpenInRedisConfigStorage.java +++ b/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/AbstractWxOpenInRedisConfigStorage.java @@ -13,6 +13,9 @@ public abstract class AbstractWxOpenInRedisConfigStorage extends WxOpenInMemoryC protected final static String AUTHORIZER_REFRESH_TOKEN_KEY = "wechat_authorizer_refresh_token:"; protected final static String AUTHORIZER_ACCESS_TOKEN_KEY = "wechat_authorizer_access_token:"; + + protected final static String LOCK_KEY = "wechat_lock:"; + protected final static String JSAPI_TICKET_KEY = "wechat_jsapi_ticket:"; protected final static String CARD_API_TICKET_KEY = "wechat_card_api_ticket:"; @@ -26,6 +29,7 @@ public abstract class AbstractWxOpenInRedisConfigStorage extends WxOpenInMemoryC protected String authorizerAccessTokenKey; protected String jsapiTicketKey; protected String cardApiTicket; + protected String lockKey; @Override public void setComponentAppId(String componentAppId) { @@ -36,8 +40,9 @@ public void setComponentAppId(String componentAppId) { componentAccessTokenKey = prefix + COMPONENT_ACCESS_TOKEN_KEY.concat(componentAppId); authorizerRefreshTokenKey = prefix + AUTHORIZER_REFRESH_TOKEN_KEY.concat(componentAppId); authorizerAccessTokenKey = prefix + AUTHORIZER_ACCESS_TOKEN_KEY.concat(componentAppId); - this.jsapiTicketKey = JSAPI_TICKET_KEY.concat(componentAppId); - this.cardApiTicket = CARD_API_TICKET_KEY.concat(componentAppId); + lockKey = prefix + LOCK_KEY.concat(componentAppId); + jsapiTicketKey = prefix + JSAPI_TICKET_KEY.concat(componentAppId); + cardApiTicket = prefix + CARD_API_TICKET_KEY.concat(componentAppId); } protected String getKey(String prefix, String appId) { diff --git a/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenComponentServiceImpl.java b/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenComponentServiceImpl.java index 27e765698b..4483e0a0c8 100644 --- a/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenComponentServiceImpl.java +++ b/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenComponentServiceImpl.java @@ -24,6 +24,7 @@ import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.locks.Lock; /** * @author 007 @@ -109,8 +110,16 @@ public boolean checkSignature(String timestamp, String nonce, String signature) @Override public String getComponentAccessToken(boolean forceRefresh) throws WxErrorException { - - if (this.getWxOpenConfigStorage().isComponentAccessTokenExpired() || forceRefresh) { + final WxOpenConfigStorage config = this.getWxOpenConfigStorage(); + if (!config.isComponentAccessTokenExpired() && !forceRefresh) { + return config.getComponentAccessToken(); + } + Lock lock = config.getComponentAccessTokenLock(); + lock.lock(); + try { + if (!config.isComponentAccessTokenExpired() && !forceRefresh) { + return config.getComponentAccessToken(); + } JsonObject jsonObject = new JsonObject(); jsonObject.addProperty("component_appid", getWxOpenConfigStorage().getComponentAppId()); jsonObject.addProperty("component_appsecret", getWxOpenConfigStorage().getComponentAppSecret()); @@ -118,9 +127,11 @@ public String getComponentAccessToken(boolean forceRefresh) throws WxErrorExcept String responseContent = this.getWxOpenService().post(API_COMPONENT_TOKEN_URL, jsonObject.toString()); WxOpenComponentAccessToken componentAccessToken = WxOpenComponentAccessToken.fromJson(responseContent); - getWxOpenConfigStorage().updateComponentAccessToken(componentAccessToken); + config.updateComponentAccessToken(componentAccessToken); + return config.getComponentAccessToken(); + } finally { + lock.unlock(); } - return this.getWxOpenConfigStorage().getComponentAccessToken(); } @Override @@ -144,7 +155,18 @@ public String post(String uri, String postData, String accessTokenKey) throws Wx */ if (error.getErrorCode() == 42001 || error.getErrorCode() == 40001 || error.getErrorCode() == 40014) { // 强制设置wxMpConfigStorage它的access token过期了,这样在下一次请求里就会刷新access token - this.getWxOpenConfigStorage().expireComponentAccessToken(); + Lock lock = this.getWxOpenConfigStorage().getComponentAccessTokenLock(); + lock.lock(); + try { + if (StringUtils.equals(componentAccessToken, this.getWxOpenConfigStorage().getComponentAccessToken())) { + this.getWxOpenConfigStorage().expireComponentAccessToken(); + } + } catch (Exception ex) { + this.getWxOpenConfigStorage().expireComponentAccessToken(); + } finally { + lock.unlock(); + } + if (this.getWxOpenConfigStorage().autoRefreshToken()) { return this.post(uri, postData, accessTokenKey); } @@ -177,7 +199,17 @@ public String get(String uri, String accessTokenKey) throws WxErrorException { */ if (error.getErrorCode() == 42001 || error.getErrorCode() == 40001 || error.getErrorCode() == 40014) { // 强制设置wxMpConfigStorage它的access token过期了,这样在下一次请求里就会刷新access token - this.getWxOpenConfigStorage().expireComponentAccessToken(); + Lock lock = this.getWxOpenConfigStorage().getComponentAccessTokenLock(); + lock.lock(); + try { + if (StringUtils.equals(componentAccessToken, this.getWxOpenConfigStorage().getComponentAccessToken())) { + this.getWxOpenConfigStorage().expireComponentAccessToken(); + } + } catch (Exception ex) { + this.getWxOpenConfigStorage().expireComponentAccessToken(); + } finally { + lock.unlock(); + } if (this.getWxOpenConfigStorage().autoRefreshToken()) { return this.get(uri, accessTokenKey); } @@ -346,8 +378,16 @@ public void setAuthorizerOption(String authorizerAppid, String optionName, Strin @Override public String getAuthorizerAccessToken(String appId, boolean forceRefresh) throws WxErrorException { - - if (this.getWxOpenConfigStorage().isAuthorizerAccessTokenExpired(appId) || forceRefresh) { + WxOpenConfigStorage config = getWxOpenConfigStorage(); + if (!config.isAuthorizerAccessTokenExpired(appId) && !forceRefresh) { + return config.getAuthorizerAccessToken(appId); + } + Lock lock = config.getWxMpConfigStorage(appId).getAccessTokenLock(); + lock.lock(); + try { + if (!config.isAuthorizerAccessTokenExpired(appId) && !forceRefresh) { + return config.getAuthorizerAccessToken(appId); + } JsonObject jsonObject = new JsonObject(); jsonObject.addProperty("component_appid", getWxOpenConfigStorage().getComponentAppId()); jsonObject.addProperty("authorizer_appid", appId); @@ -355,9 +395,11 @@ public String getAuthorizerAccessToken(String appId, boolean forceRefresh) throw String responseContent = post(API_AUTHORIZER_TOKEN_URL, jsonObject.toString()); WxOpenAuthorizerAccessToken wxOpenAuthorizerAccessToken = WxOpenAuthorizerAccessToken.fromJson(responseContent); - getWxOpenConfigStorage().updateAuthorizerAccessToken(appId, wxOpenAuthorizerAccessToken); + config.updateAuthorizerAccessToken(appId, wxOpenAuthorizerAccessToken); + return config.getAuthorizerAccessToken(appId); + } finally { + lock.unlock(); } - return this.getWxOpenConfigStorage().getAuthorizerAccessToken(appId); } @Override diff --git a/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenInMemoryConfigStorage.java b/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenInMemoryConfigStorage.java index 491dc6b7bc..723ec3806e 100644 --- a/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenInMemoryConfigStorage.java +++ b/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenInMemoryConfigStorage.java @@ -4,10 +4,10 @@ import cn.binarywang.wx.miniapp.config.WxMaConfig; import lombok.Data; import me.chanjar.weixin.common.bean.WxAccessToken; +import me.chanjar.weixin.common.enums.TicketType; import me.chanjar.weixin.common.util.http.apache.ApacheHttpClientBuilder; import me.chanjar.weixin.mp.bean.WxMpHostConfig; import me.chanjar.weixin.mp.config.WxMpConfigStorage; -import me.chanjar.weixin.mp.enums.TicketType; import me.chanjar.weixin.open.api.WxOpenConfigStorage; import me.chanjar.weixin.open.bean.WxOpenAuthorizerAccessToken; import me.chanjar.weixin.open.bean.WxOpenComponentAccessToken; @@ -44,8 +44,7 @@ public class WxOpenInMemoryConfigStorage implements WxOpenConfigStorage { private MapauthorizerAccessTokens = new ConcurrentHashMap<>(); private Map jsapiTickets = new ConcurrentHashMap<>(); private Map cardApiTickets = new ConcurrentHashMap<>(); - - + private Map locks = new ConcurrentHashMap<>(); @Override public boolean isComponentAccessTokenExpired() { @@ -62,6 +61,35 @@ public void updateComponentAccessToken(WxOpenComponentAccessToken componentAcces updateComponentAccessToken(componentAccessToken.getComponentAccessToken(), componentAccessToken.getExpiresIn()); } + private Lock accessTokenLockInstance; + + @Override + public Lock getComponentAccessTokenLock() { + if (this.accessTokenLockInstance == null) { + synchronized (this) { + if (this.accessTokenLockInstance == null) { + this.accessTokenLockInstance = getLockByKey("componentAccessTokenLock"); + } + } + } + return this.accessTokenLockInstance; + } + + @Override + public Lock getLockByKey(String key) { + Lock lock = locks.get(key); + if (lock == null) { + synchronized (this) { + lock = locks.get(key); + if (lock == null) { + lock = new ReentrantLock(); + locks.put(key, lock); + } + } + } + return lock; + } + @Override public WxMpConfigStorage getWxMpConfigStorage(String appId) { return new WxOpenInnerConfigStorage(this, appId); @@ -79,7 +107,8 @@ public void updateComponentAccessToken(String componentAccessToken, int expiresI } @Override - public void setWxOpenInfo(String componentAppId, String componentAppSecret, String componentToken, String componentAesKey) { + public void setWxOpenInfo(String componentAppId, String componentAppSecret, String componentToken, + String componentAesKey) { setComponentAppId(componentAppId); setComponentAppSecret(componentAppSecret); setComponentToken(componentToken); @@ -146,7 +175,8 @@ public void expireAuthorizerAccessToken(String appId) { @Override public void updateAuthorizerAccessToken(String appId, WxOpenAuthorizerAccessToken authorizerAccessToken) { - updateAuthorizerAccessToken(appId, authorizerAccessToken.getAuthorizerAccessToken(), authorizerAccessToken.getExpiresIn()); + updateAuthorizerAccessToken(appId, authorizerAccessToken.getAuthorizerAccessToken(), + authorizerAccessToken.getExpiresIn()); } @Override @@ -202,13 +232,24 @@ private static class Token { private static class WxOpenInnerConfigStorage implements WxMpConfigStorage, WxMaConfig { private WxOpenConfigStorage wxOpenConfigStorage; private String appId; - private Lock accessTokenLock = new ReentrantLock(); - private Lock jsapiTicketLock = new ReentrantLock(); - private Lock cardApiTicketLock = new ReentrantLock(); + /** + * 小程序原始ID + */ + private volatile String originalId; + /** + * 云环境ID + */ + private volatile String cloudEnv; + private final Lock accessTokenLock; + private final Lock jsapiTicketLock; + private final Lock cardApiTicketLock; private WxOpenInnerConfigStorage(WxOpenConfigStorage wxOpenConfigStorage, String appId) { this.wxOpenConfigStorage = wxOpenConfigStorage; this.appId = appId; + this.accessTokenLock = wxOpenConfigStorage.getLockByKey(appId + ":accessTokenLock"); + this.jsapiTicketLock = wxOpenConfigStorage.getLockByKey(appId + ":jsapiTicketLock"); + this.cardApiTicketLock = wxOpenConfigStorage.getLockByKey(appId + ":cardApiTicketLock"); } @Override @@ -325,6 +366,24 @@ public String getAppid() { return this.appId; } + @Override + public String getOriginalId() { + return originalId; + } + + public void setOriginalId(String originalId) { + this.originalId = originalId; + } + + @Override + public String getCloudEnv() { + return this.cloudEnv; + } + + public void setCloudEnv(String cloudEnv) { + this.cloudEnv = cloudEnv; + } + @Override public void expireAccessToken() { wxOpenConfigStorage.expireAuthorizerAccessToken(appId); diff --git a/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenInRedisConfigStorage.java b/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenInRedisConfigStorage.java index f1b903d472..31c199bba7 100644 --- a/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenInRedisConfigStorage.java +++ b/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenInRedisConfigStorage.java @@ -1,166 +1,140 @@ package me.chanjar.weixin.open.api.impl; +import lombok.NonNull; +import me.chanjar.weixin.common.redis.JedisWxRedisOps; +import me.chanjar.weixin.common.redis.WxRedisOps; import redis.clients.jedis.Jedis; -import redis.clients.jedis.JedisPool; import redis.clients.util.Pool; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.Lock; + /** * @author 007 */ public class WxOpenInRedisConfigStorage extends AbstractWxOpenInRedisConfigStorage { - protected final Pool jedisPool; + private final WxRedisOps redisOps; public WxOpenInRedisConfigStorage(Pool jedisPool) { - this.jedisPool = jedisPool; + this(jedisPool, null); } - public WxOpenInRedisConfigStorage(Pool jedisPool, String keyPrefix) { - this.jedisPool = jedisPool; - this.keyPrefix = keyPrefix; + public WxOpenInRedisConfigStorage(@NonNull Pool jedisPool, String keyPrefix) { + this(new JedisWxRedisOps(jedisPool), keyPrefix); } - public WxOpenInRedisConfigStorage(JedisPool jedisPool) { - this.jedisPool = jedisPool; + public WxOpenInRedisConfigStorage(@NonNull WxRedisOps redisOps, String keyPrefix) { + this.redisOps = redisOps; + this.keyPrefix = keyPrefix; } @Override public String getComponentVerifyTicket() { - try (Jedis jedis = this.jedisPool.getResource()) { - return jedis.get(this.componentVerifyTicketKey); - } + return redisOps.getValue(this.componentVerifyTicketKey); } @Override public void setComponentVerifyTicket(String componentVerifyTicket) { - try (Jedis jedis = this.jedisPool.getResource()) { - jedis.set(this.componentVerifyTicketKey, componentVerifyTicket); - } + redisOps.setValue(this.componentVerifyTicketKey, componentVerifyTicket, Integer.MAX_VALUE, TimeUnit.SECONDS); } @Override public String getComponentAccessToken() { - try (Jedis jedis = this.jedisPool.getResource()) { - return jedis.get(this.componentAccessTokenKey); - } + return redisOps.getValue(this.componentAccessTokenKey); } @Override public boolean isComponentAccessTokenExpired() { - try (Jedis jedis = this.jedisPool.getResource()) { - return jedis.ttl(this.componentAccessTokenKey) < 2; - } + Long expire = redisOps.getExpire(this.componentAccessTokenKey); + return expire == null || expire < 2; } @Override public void expireComponentAccessToken() { - try (Jedis jedis = this.jedisPool.getResource()) { - jedis.expire(this.componentAccessTokenKey, 0); - } + redisOps.expire(this.componentAccessTokenKey, 0, TimeUnit.SECONDS); } @Override public void updateComponentAccessToken(String componentAccessToken, int expiresInSeconds) { - try (Jedis jedis = this.jedisPool.getResource()) { - jedis.setex(this.componentAccessTokenKey, expiresInSeconds - 200, componentAccessToken); - } + redisOps.setValue(this.componentAccessTokenKey, componentAccessToken, expiresInSeconds - 200, TimeUnit.SECONDS); } @Override public String getAuthorizerRefreshToken(String appId) { - try (Jedis jedis = this.jedisPool.getResource()) { - return jedis.get(this.getKey(this.authorizerRefreshTokenKey, appId)); - } + return redisOps.getValue(this.getKey(this.authorizerRefreshTokenKey, appId)); } @Override public void setAuthorizerRefreshToken(String appId, String authorizerRefreshToken) { - try (Jedis jedis = this.jedisPool.getResource()) { - jedis.set(this.getKey(this.authorizerRefreshTokenKey, appId), authorizerRefreshToken); - } + redisOps.setValue(this.getKey(this.authorizerRefreshTokenKey, appId), authorizerRefreshToken, 0, TimeUnit.SECONDS); } @Override public String getAuthorizerAccessToken(String appId) { - try (Jedis jedis = this.jedisPool.getResource()) { - return jedis.get(this.getKey(this.authorizerAccessTokenKey, appId)); - } + return redisOps.getValue(this.getKey(this.authorizerAccessTokenKey, appId)); } @Override public boolean isAuthorizerAccessTokenExpired(String appId) { - try (Jedis jedis = this.jedisPool.getResource()) { - return jedis.ttl(this.getKey(this.authorizerAccessTokenKey, appId)) < 2; - } + Long expire = redisOps.getExpire(this.getKey(this.authorizerAccessTokenKey, appId)); + return expire == null || expire < 2; } @Override public void expireAuthorizerAccessToken(String appId) { - try (Jedis jedis = this.jedisPool.getResource()) { - jedis.expire(this.getKey(this.authorizerAccessTokenKey, appId), 0); - } + redisOps.expire(this.getKey(this.authorizerAccessTokenKey, appId), 0, TimeUnit.SECONDS); } @Override public void updateAuthorizerAccessToken(String appId, String authorizerAccessToken, int expiresInSeconds) { - try (Jedis jedis = this.jedisPool.getResource()) { - jedis.setex(this.getKey(this.authorizerAccessTokenKey, appId), expiresInSeconds - 200, authorizerAccessToken); - } + redisOps.setValue(this.getKey(this.authorizerAccessTokenKey, appId), authorizerAccessToken, expiresInSeconds - 200, TimeUnit.SECONDS); } @Override public String getJsapiTicket(String appId) { - try (Jedis jedis = this.jedisPool.getResource()) { - return jedis.get(this.getKey(this.jsapiTicketKey, appId)); - } + return redisOps.getValue(this.getKey(this.jsapiTicketKey, appId)); } @Override public boolean isJsapiTicketExpired(String appId) { - try (Jedis jedis = this.jedisPool.getResource()) { - return jedis.ttl(this.getKey(this.jsapiTicketKey, appId)) < 2; - } + Long expire = redisOps.getExpire(this.getKey(this.jsapiTicketKey, appId)); + return expire == null || expire < 2; } @Override public void expireJsapiTicket(String appId) { - try (Jedis jedis = this.jedisPool.getResource()) { - jedis.expire(this.getKey(this.jsapiTicketKey, appId), 0); - } + redisOps.expire(this.getKey(this.jsapiTicketKey, appId), 0, TimeUnit.SECONDS); } @Override public void updateJsapiTicket(String appId, String jsapiTicket, int expiresInSeconds) { - try (Jedis jedis = this.jedisPool.getResource()) { - jedis.setex(this.getKey(this.jsapiTicketKey, appId), expiresInSeconds - 200, jsapiTicket); - } + redisOps.setValue(this.getKey(this.jsapiTicketKey, appId), jsapiTicket, expiresInSeconds - 200, TimeUnit.SECONDS); } @Override public String getCardApiTicket(String appId) { - try (Jedis jedis = this.jedisPool.getResource()) { - return jedis.get(this.getKey(this.cardApiTicket, appId)); - } + return redisOps.getValue(this.getKey(this.cardApiTicket, appId)); } @Override public boolean isCardApiTicketExpired(String appId) { - try (Jedis jedis = this.jedisPool.getResource()) { - return jedis.ttl(this.getKey(this.cardApiTicket, appId)) < 2; - } + Long expire = redisOps.getExpire(this.getKey(this.cardApiTicket, appId)); + return expire == null || expire < 2; } @Override public void expireCardApiTicket(String appId) { - try (Jedis jedis = this.jedisPool.getResource()) { - jedis.expire(this.getKey(this.cardApiTicket, appId), 0); - } + redisOps.expire(this.getKey(this.cardApiTicket, appId), 0, TimeUnit.SECONDS); } @Override public void updateCardApiTicket(String appId, String cardApiTicket, int expiresInSeconds) { - try (Jedis jedis = this.jedisPool.getResource()) { - jedis.setex(this.getKey(this.cardApiTicket, appId), expiresInSeconds - 200, cardApiTicket); - } + redisOps.setValue(this.getKey(this.cardApiTicket, appId), cardApiTicket, expiresInSeconds - 200, TimeUnit.SECONDS); + } + + @Override + public Lock getLockByKey(String key) { + return redisOps.getLock(key); } } diff --git a/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenInRedissonConfigStorage.java b/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenInRedissonConfigStorage.java index 7fdf1ba1b6..070d9ebf88 100644 --- a/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenInRedissonConfigStorage.java +++ b/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenInRedissonConfigStorage.java @@ -1,128 +1,140 @@ package me.chanjar.weixin.open.api.impl; -import java.util.concurrent.TimeUnit; +import lombok.NonNull; +import me.chanjar.weixin.common.redis.RedissonWxRedisOps; +import me.chanjar.weixin.common.redis.WxRedisOps; import org.redisson.api.RedissonClient; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.Lock; + /** * @author yangyidian * @date 2020/01/06 **/ -public class WxOpenInRedissonConfigStorage extends AbstractWxOpenInRedisConfigStorage{ - - private RedissonClient redissonClient; - - public WxOpenInRedissonConfigStorage(RedissonClient redissonClient, String keyPrefix) { - this.keyPrefix = keyPrefix; - this.redissonClient = redissonClient; - } - - public WxOpenInRedissonConfigStorage(RedissonClient redissonClient) { - this.redissonClient = redissonClient; - } - - @Override - public String getComponentVerifyTicket() { - Object value = redissonClient.getBucket(this.componentVerifyTicketKey).get(); - return value == null ? null : value.toString(); - } - - @Override - public void setComponentVerifyTicket(String componentVerifyTicket) { - redissonClient.getBucket(this.componentVerifyTicketKey).set(componentVerifyTicket); - } - - @Override - public String getComponentAccessToken() { - Object value = redissonClient.getBucket(this.componentAccessTokenKey).get(); - return value == null ? null : value.toString(); - } - - @Override - public boolean isComponentAccessTokenExpired() { - return redissonClient.getBucket(this.componentAccessTokenKey).remainTimeToLive() < 2; - } - - @Override - public void expireComponentAccessToken() { - redissonClient.getBucket(this.componentAccessTokenKey).expire(0, TimeUnit.SECONDS); - } - - @Override - public void updateComponentAccessToken(String componentAccessToken, int expiresInSeconds) { - redissonClient.getBucket(this.componentAccessTokenKey).set(componentAccessToken, expiresInSeconds - 200, TimeUnit.SECONDS); - } - - @Override - public String getAuthorizerRefreshToken(String appId) { - Object value = redissonClient.getBucket(this.getKey(this.authorizerRefreshTokenKey, appId)).get(); - return value == null ? null : value.toString(); - } - - @Override - public void setAuthorizerRefreshToken(String appId, String authorizerRefreshToken) { - redissonClient.getBucket(this.getKey(this.authorizerRefreshTokenKey, appId)).set(authorizerRefreshToken); - } - - @Override - public String getAuthorizerAccessToken(String appId) { - Object value = redissonClient.getBucket(this.getKey(this.authorizerAccessTokenKey, appId)).get(); - return value == null ? null : value.toString(); - } - - @Override - public boolean isAuthorizerAccessTokenExpired(String appId) { - return redissonClient.getBucket(this.getKey(this.authorizerAccessTokenKey, appId)).remainTimeToLive() < 2; - } - - @Override - public void expireAuthorizerAccessToken(String appId) { - redissonClient.getBucket(this.getKey(this.authorizerAccessTokenKey, appId)).expire(0, TimeUnit.SECONDS); - } - - @Override - public void updateAuthorizerAccessToken(String appId, String authorizerAccessToken, int expiresInSeconds) { - redissonClient.getBucket(this.getKey(this.authorizerAccessTokenKey, appId)).set(authorizerAccessToken, expiresInSeconds - 200, TimeUnit.SECONDS); - } - - @Override - public String getJsapiTicket(String appId) { - Object value = redissonClient.getBucket(this.getKey(this.jsapiTicketKey, appId)).get(); - return value == null ? null : value.toString(); - } - - @Override - public boolean isJsapiTicketExpired(String appId) { - return redissonClient.getBucket(this.getKey(this.jsapiTicketKey, appId)).remainTimeToLive() < 2; - } - - @Override - public void expireJsapiTicket(String appId) { - redissonClient.getBucket(this.getKey(this.jsapiTicketKey, appId)).expire(0, TimeUnit.SECONDS); - } - - @Override - public void updateJsapiTicket(String appId, String jsapiTicket, int expiresInSeconds) { - redissonClient.getBucket(this.getKey(this.jsapiTicketKey, appId)).set(jsapiTicket, expiresInSeconds - 200, TimeUnit.SECONDS); - } - - @Override - public String getCardApiTicket(String appId) { - Object value = redissonClient.getBucket(this.getKey(this.cardApiTicket, appId)).get(); - return value == null ? null : value.toString(); - } - - @Override - public boolean isCardApiTicketExpired(String appId) { - return redissonClient.getBucket(this.getKey(this.cardApiTicket, appId)).remainTimeToLive() < 2; - } - - @Override - public void expireCardApiTicket(String appId) { - redissonClient.getBucket(this.getKey(this.cardApiTicket, appId)).expire(0 ,TimeUnit.SECONDS); - } - - @Override - public void updateCardApiTicket(String appId, String cardApiTicket, int expiresInSeconds) { - redissonClient.getBucket(this.getKey(this.cardApiTicket, appId)).set(cardApiTicket, expiresInSeconds - 200, TimeUnit.SECONDS); - } +public class WxOpenInRedissonConfigStorage extends AbstractWxOpenInRedisConfigStorage { + + private final WxRedisOps redisOps; + + public WxOpenInRedissonConfigStorage(@NonNull RedissonClient redissonClient, String keyPrefix) { + this(new RedissonWxRedisOps(redissonClient), keyPrefix); + } + + public WxOpenInRedissonConfigStorage(@NonNull RedissonClient redissonClient) { + this(redissonClient, null); + } + + private WxOpenInRedissonConfigStorage(@NonNull WxRedisOps redisOps, String keyPrefix) { + this.redisOps = redisOps; + this.keyPrefix = keyPrefix; + } + + @Override + public String getComponentVerifyTicket() { + return redisOps.getValue(this.componentVerifyTicketKey); + } + + @Override + public void setComponentVerifyTicket(String componentVerifyTicket) { + redisOps.setValue(this.componentVerifyTicketKey, componentVerifyTicket, Integer.MAX_VALUE, TimeUnit.SECONDS); + } + + @Override + public String getComponentAccessToken() { + return redisOps.getValue(this.componentAccessTokenKey); + } + + @Override + public boolean isComponentAccessTokenExpired() { + Long expire = redisOps.getExpire(this.componentAccessTokenKey); + return expire == null || expire < 2; + } + + @Override + public void expireComponentAccessToken() { + redisOps.expire(this.componentAccessTokenKey, 0, TimeUnit.SECONDS); + } + + @Override + public void updateComponentAccessToken(String componentAccessToken, int expiresInSeconds) { + redisOps.setValue(this.componentAccessTokenKey, componentAccessToken, expiresInSeconds - 200, TimeUnit.SECONDS); + } + + @Override + public String getAuthorizerRefreshToken(String appId) { + return redisOps.getValue(this.getKey(this.authorizerRefreshTokenKey, appId)); + } + + @Override + public void setAuthorizerRefreshToken(String appId, String authorizerRefreshToken) { + redisOps.setValue(this.getKey(this.authorizerRefreshTokenKey, appId), authorizerRefreshToken, 0, TimeUnit.SECONDS); + } + + @Override + public String getAuthorizerAccessToken(String appId) { + return redisOps.getValue(this.getKey(this.authorizerAccessTokenKey, appId)); + } + + @Override + public boolean isAuthorizerAccessTokenExpired(String appId) { + Long expire = redisOps.getExpire(this.getKey(this.authorizerAccessTokenKey, appId)); + return expire == null || expire < 2; + } + + @Override + public void expireAuthorizerAccessToken(String appId) { + redisOps.expire(this.getKey(this.authorizerAccessTokenKey, appId), 0, TimeUnit.SECONDS); + } + + @Override + public void updateAuthorizerAccessToken(String appId, String authorizerAccessToken, int expiresInSeconds) { + redisOps.setValue(this.getKey(this.authorizerAccessTokenKey, appId), authorizerAccessToken, expiresInSeconds - 200, TimeUnit.SECONDS); + } + + @Override + public String getJsapiTicket(String appId) { + return redisOps.getValue(this.getKey(this.jsapiTicketKey, appId)); + } + + @Override + public boolean isJsapiTicketExpired(String appId) { + Long expire = redisOps.getExpire(this.getKey(this.jsapiTicketKey, appId)); + return expire == null || expire < 2; + } + + @Override + public void expireJsapiTicket(String appId) { + redisOps.expire(this.getKey(this.jsapiTicketKey, appId), 0, TimeUnit.SECONDS); + } + + @Override + public void updateJsapiTicket(String appId, String jsapiTicket, int expiresInSeconds) { + redisOps.setValue(this.getKey(this.jsapiTicketKey, appId), jsapiTicket, expiresInSeconds - 200, TimeUnit.SECONDS); + } + + @Override + public String getCardApiTicket(String appId) { + return redisOps.getValue(this.getKey(this.cardApiTicket, appId)); + } + + @Override + public boolean isCardApiTicketExpired(String appId) { + Long expire = redisOps.getExpire(this.getKey(this.cardApiTicket, appId)); + return expire == null || expire < 2; + } + + @Override + public void expireCardApiTicket(String appId) { + redisOps.expire(this.getKey(this.cardApiTicket, appId), 0, TimeUnit.SECONDS); + } + + @Override + public void updateCardApiTicket(String appId, String cardApiTicket, int expiresInSeconds) { + redisOps.setValue(this.getKey(this.cardApiTicket, appId), cardApiTicket, expiresInSeconds - 200, TimeUnit.SECONDS); + } + + @Override + public Lock getLockByKey(String key) { + return redisOps.getLock(key); + } } diff --git a/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenMaServiceImpl.java b/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenMaServiceImpl.java index 1d30a57f19..6cbf584c33 100644 --- a/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenMaServiceImpl.java +++ b/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenMaServiceImpl.java @@ -194,6 +194,20 @@ public WxOpenResult unbindTester(String wechatid) throws WxErrorException { return WxMaGsonBuilder.create().fromJson(response, WxOpenResult.class); } + /** + * 解除绑定小程序体验者 + * @param userstr 人员对应的唯一字符串, 可通过获取已绑定的体验者列表获取人员对应的字符串 + * @return + * @throws WxErrorException + */ + @Override + public WxOpenResult unbindTesterByUserstr(String userstr) throws WxErrorException { + JsonObject paramJson = new JsonObject(); + paramJson.addProperty("userstr", userstr); + String response = post(API_UNBIND_TESTER, GSON.toJson(paramJson)); + return WxMaGsonBuilder.create().fromJson(response, WxOpenResult.class); + } + /** * 获得体验者列表 * diff --git a/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenServiceAbstractImpl.java b/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenServiceAbstractImpl.java index b7084c9eb1..e4e66890b3 100644 --- a/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenServiceAbstractImpl.java +++ b/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenServiceAbstractImpl.java @@ -42,7 +42,7 @@ public void setWxOpenConfigStorage(WxOpenConfigStorage wxOpenConfigStorage) { */ public abstract void initHttp(); - protected synchronized T execute(RequestExecutor executor, String uri, E data) throws WxErrorException { + protected T execute(RequestExecutor executor, String uri, E data) throws WxErrorException { try { T result = executor.execute(uri, data, WxType.Open); this.log.debug("\n【请求地址】: {}\n【请求参数】:{}\n【响应数据】:{}", uri, data, result); diff --git a/weixin-java-open/src/main/java/me/chanjar/weixin/open/executor/MaQrCodeJoddHttpRequestExecutor.java b/weixin-java-open/src/main/java/me/chanjar/weixin/open/executor/MaQrCodeJoddHttpRequestExecutor.java index f376dd4546..c06fbafa74 100644 --- a/weixin-java-open/src/main/java/me/chanjar/weixin/open/executor/MaQrCodeJoddHttpRequestExecutor.java +++ b/weixin-java-open/src/main/java/me/chanjar/weixin/open/executor/MaQrCodeJoddHttpRequestExecutor.java @@ -4,7 +4,7 @@ import jodd.http.HttpRequest; import jodd.http.HttpResponse; import jodd.http.ProxyInfo; -import jodd.util.MimeTypes; +import jodd.net.MimeTypes; import jodd.util.StringPool; import me.chanjar.weixin.common.WxType; import me.chanjar.weixin.common.error.WxError; diff --git a/weixin-java-pay/pom.xml b/weixin-java-pay/pom.xml index 7d9100c5cb..db7da98eeb 100644 --- a/weixin-java-pay/pom.xml +++ b/weixin-java-pay/pom.xml @@ -5,7 +5,7 @@ com.github.binarywang wx-java -3.7.0 +3.8.0 4.0.0 @@ -38,12 +38,12 @@commons-beanutils commons-beanutils -1.9.3 +1.9.4 org.bouncycastle bcpkix-jdk15on -1.59 +1.65 @@ -70,6 +70,20 @@ +org.projectlombok lombok + +com.fasterxml.jackson.core +jackson-databind +2.9.7 ++ +com.google.code.gson +gson ++ joda-time +joda-time +compile +diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/entpay/EntPayResult.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/entpay/EntPayResult.java index 9863e83bcc..23a3cb7f23 100644 --- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/entpay/EntPayResult.java +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/entpay/EntPayResult.java @@ -20,6 +20,8 @@ @NoArgsConstructor @XStreamAlias("xml") public class EntPayResult extends BaseWxPayResult { + private static final long serialVersionUID = 8523569987269603097L; + /** * 商户号. */ diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/notify/WxPayNotifyResponse.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/notify/WxPayNotifyResponse.java index ed1353284f..3b1cfe7a84 100644 --- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/notify/WxPayNotifyResponse.java +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/notify/WxPayNotifyResponse.java @@ -60,4 +60,31 @@ public static String success(String msg) { return xstream.toXML(response).replace("\n", "").replace(" ", ""); } + /** + * Fail string. + * + * @param msg the msg + * @return the string + */ + public static String failResp(String msg) { + return generateXml(FAIL, msg); + } + + /** + * Success string. + * + * @param msg the msg + * @return the string + */ + public static String successResp(String msg) { + return generateXml(SUCCESS, msg); + } + + + /** + * 使用格式化字符串生成xml字符串 + */ + private static String generateXml(String code, String msg) { + return String.format(" ", code, msg); + } } diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/order/WxPayAppOrderResult.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/order/WxPayAppOrderResult.java index c37efadbb1..17037b303a 100644 --- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/order/WxPayAppOrderResult.java +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/order/WxPayAppOrderResult.java @@ -1,7 +1,9 @@ package com.github.binarywang.wxpay.bean.order; +import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; +import lombok.NoArgsConstructor; import java.io.Serializable; @@ -16,6 +18,8 @@ */ @Data @Builder +@NoArgsConstructor +@AllArgsConstructor public class WxPayAppOrderResult implements Serializable { private static final long serialVersionUID = 5408678833978707228L; diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/order/WxPayMpOrderResult.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/order/WxPayMpOrderResult.java index 7fe7234f1c..3ec6a7e09b 100644 --- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/order/WxPayMpOrderResult.java +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/order/WxPayMpOrderResult.java @@ -1,8 +1,10 @@ package com.github.binarywang.wxpay.bean.order; import com.thoughtworks.xstream.annotations.XStreamAlias; +import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; +import lombok.NoArgsConstructor; import java.io.Serializable; @@ -17,6 +19,8 @@ */ @Data @Builder +@NoArgsConstructor +@AllArgsConstructor public class WxPayMpOrderResult implements Serializable { private static final long serialVersionUID = -7966682379048446567L; diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/order/WxPayMwebOrderResult.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/order/WxPayMwebOrderResult.java index 78487f9966..046885e624 100644 --- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/order/WxPayMwebOrderResult.java +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/order/WxPayMwebOrderResult.java @@ -3,6 +3,7 @@ import com.thoughtworks.xstream.annotations.XStreamAlias; import lombok.AllArgsConstructor; import lombok.Data; +import lombok.NoArgsConstructor; import java.io.Serializable; @@ -16,6 +17,7 @@ */ @Data @AllArgsConstructor +@NoArgsConstructor public class WxPayMwebOrderResult implements Serializable { private static final long serialVersionUID = 8866329695767762066L; diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/order/WxPayNativeOrderResult.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/order/WxPayNativeOrderResult.java index 134f5cf883..a94615d159 100644 --- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/order/WxPayNativeOrderResult.java +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/order/WxPayNativeOrderResult.java @@ -2,6 +2,7 @@ import lombok.AllArgsConstructor; import lombok.Data; +import lombok.NoArgsConstructor; import java.io.Serializable; @@ -15,6 +16,7 @@ */ @Data @AllArgsConstructor +@NoArgsConstructor public class WxPayNativeOrderResult implements Serializable { private static final long serialVersionUID = 887792717425241444L; diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/payscore/Detail.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/payscore/Detail.java new file mode 100644 index 0000000000..b52c2abc1b --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/payscore/Detail.java @@ -0,0 +1,36 @@ +package com.github.binarywang.wxpay.bean.payscore; + +import com.google.gson.annotations.SerializedName; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * 明细. + * + * @author doger.wang + * @date 2020-05-19 + */ +@Data +@NoArgsConstructor +public class Detail implements Serializable { + private static final long serialVersionUID = -3901373259400050385L; + /** + * seq : 1 + * amount : 900 + * paid_type : NEWTON + * paid_time : 20091225091210 + * transaction_id : 15646546545165651651 + */ + @SerializedName("seq") + private int seq; + @SerializedName("amount") + private int amount; + @SerializedName("paid_type") + private String paidType; + @SerializedName("paid_time") + private String paidTime; + @SerializedName("transaction_id") + private String transactionId; +} diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/payscore/Location.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/payscore/Location.java new file mode 100644 index 0000000000..b3c82f7d96 --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/payscore/Location.java @@ -0,0 +1,27 @@ +package com.github.binarywang.wxpay.bean.payscore; + +import com.google.gson.annotations.SerializedName; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * 服务位置信息. + * + * @author doger.wang + * @date 2020-05-19 + */ +@Data +@NoArgsConstructor +public class Location implements Serializable { + private static final long serialVersionUID = -4510224826631515344L; + /** + * start_location : 嗨客时尚主题展餐厅 + * end_location : 嗨客时尚主题展餐厅 + */ + @SerializedName("start_location") + private String startLocation; + @SerializedName("end_location") + private String endLocation; +} diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/payscore/PayScoreNotifyData.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/payscore/PayScoreNotifyData.java new file mode 100644 index 0000000000..81d5568bcd --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/payscore/PayScoreNotifyData.java @@ -0,0 +1,56 @@ +package com.github.binarywang.wxpay.bean.payscore; + +import com.google.gson.annotations.SerializedName; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * 微信支付分确认订单跟支付回调对象 + * + * @author doger.wang + * @date 2020/5/14 12:18 + */ +@NoArgsConstructor +@Data +public class PayScoreNotifyData implements Serializable { + private static final long serialVersionUID = -8538014389773390989L; + + /** + * id : EV-2018022511223320873 + * create_time : 20180225112233 + * resource_type : encrypt-resource + * event_type : PAYSCORE.USER_CONFIRM + * resource : {"algorithm":"AEAD_AES_256_GCM","ciphertext":"...","nonce":"...","associated_data":""} + */ + @SerializedName("id") + private String id; + @SerializedName("create_time") + private String createTime; + @SerializedName("resource_type") + private String resourceType; + @SerializedName("event_type") + private String eventType; + @SerializedName("resource") + private Resource resource; + + @Data + public static class Resource implements Serializable { + private static final long serialVersionUID = 8530711804335261449L; + /** + * algorithm : AEAD_AES_256_GCM + * ciphertext : ... + * nonce : ... + * associated_data : + */ + @SerializedName("algorithm") + private String algorithm; + @SerializedName("ciphertext") + private String cipherText; + @SerializedName("nonce") + private String nonce; + @SerializedName("associated_data") + private String associatedData; + } +} diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/payscore/PostDiscount.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/payscore/PostDiscount.java new file mode 100644 index 0000000000..ebd2cf2b39 --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/payscore/PostDiscount.java @@ -0,0 +1,31 @@ +package com.github.binarywang.wxpay.bean.payscore; + +import com.google.gson.annotations.SerializedName; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * 后付费商户优惠. + * + * @author doger.wang + * @date 2020-05-19 + */ +@Data +@NoArgsConstructor +public class PostDiscount implements Serializable { + private static final long serialVersionUID = 2764537888242763379L; + /** + * name : 满20减1元 + * description : 不与其他优惠叠加 + */ + @SerializedName("name") + private String name; + @SerializedName("description") + private String description; + @SerializedName("count") + private int count; + @SerializedName("amount") + private int amount; +} diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/payscore/PostPayment.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/payscore/PostPayment.java new file mode 100644 index 0000000000..fef0b5ab8b --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/payscore/PostPayment.java @@ -0,0 +1,33 @@ +package com.github.binarywang.wxpay.bean.payscore; + +import com.google.gson.annotations.SerializedName; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * 后付费项目. + * + * @author doger.wang + * @date 2020-05-19 + */ +@Data +@NoArgsConstructor +public class PostPayment implements Serializable { + private static final long serialVersionUID = 2007722927556382895L; + /** + * name : 就餐费用服务费 + * amount : 4000 + * description : 就餐人均100元服务费:100/小时 + * count : 1 + */ + @SerializedName("name") + private String name; + @SerializedName("amount") + private int amount; + @SerializedName("description") + private String description; + @SerializedName("count") + private int count; +} diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/payscore/RiskFund.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/payscore/RiskFund.java new file mode 100644 index 0000000000..c6bd840186 --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/payscore/RiskFund.java @@ -0,0 +1,30 @@ +package com.github.binarywang.wxpay.bean.payscore; + +import com.google.gson.annotations.SerializedName; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * 订单风险金信息. + * + * @author doger.wang + * @date 2020-05-19 + */ +@Data +@NoArgsConstructor +public class RiskFund implements Serializable { + private static final long serialVersionUID = -3583406084396059152L; + /** + * name : ESTIMATE_ORDER_COST + * amount : 10000 + * description : 就餐的预估费用 + */ + @SerializedName("name") + private String name; + @SerializedName("amount") + private int amount; + @SerializedName("description") + private String description; +} diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/payscore/TimeRange.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/payscore/TimeRange.java new file mode 100644 index 0000000000..08b86d6eda --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/payscore/TimeRange.java @@ -0,0 +1,27 @@ +package com.github.binarywang.wxpay.bean.payscore; + +import com.google.gson.annotations.SerializedName; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * 服务时间范围. + * + * @author doger.wang + * @date 2020-05-19 + */ +@Data +@NoArgsConstructor +public class TimeRange implements Serializable { + private static final long serialVersionUID = 8169562173656314930L; + /** + * start_time : 20091225091010 + * end_time : 20091225121010 + */ + @SerializedName("start_time") + private String startTime; + @SerializedName("end_time") + private String endTime; +} diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/payscore/WxPayScoreRequest.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/payscore/WxPayScoreRequest.java new file mode 100644 index 0000000000..e76801b907 --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/payscore/WxPayScoreRequest.java @@ -0,0 +1,79 @@ +package com.github.binarywang.wxpay.bean.payscore; + +import com.google.gson.annotations.SerializedName; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.Accessors; + +import java.io.Serializable; +import java.util.List; + +/** + * @author doger.wang + * @date 2020/5/12 16:36 + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +@Accessors(chain = true) +public class WxPayScoreRequest implements Serializable { + private static final long serialVersionUID = 364764508076146082L; + + /** + * out_order_no : 1234323JKHDFE1243252 + * appid : wxd678efh567hg6787 + * service_id : 500001 + * service_introduction : 某某酒店 + * post_payments : [{"name":"就餐费用服务费","amount":4000,"description":"就餐人均100元服务费:100/小时","count":1}] + * post_discounts : [{"name":"满20减1元","description":"不与其他优惠叠加"}] + * time_range : {"start_time":"20091225091010","end_time":"20091225121010"} + * location : {"start_location":"嗨客时尚主题展餐厅","end_location":"嗨客时尚主题展餐厅"} + * risk_fund : {"name":"ESTIMATE_ORDER_COST","amount":10000,"description":"就餐的预估费用"} + * attach : Easdfowealsdkjfnlaksjdlfkwqoi&wl3l2sald + * notify_url : https://api.test.com + * openid : oUpF8uMuAJO_M2pxb1Q9zNjWeS6o + * need_user_confirm : true + */ + @SerializedName("out_order_no") + private String outOrderNo; + @SerializedName("appid") + private String appid; + @SerializedName("service_id") + private String serviceId; + @SerializedName("service_introduction") + private String serviceIntroduction; + @SerializedName("time_range") + private TimeRange timeRange; + @SerializedName("location") + private Location location; + @SerializedName("risk_fund") + private RiskFund riskFund; + @SerializedName("attach") + private String attach; + @SerializedName("notify_url") + private String notifyUrl; + @SerializedName("openid") + private String openid; + @SerializedName("need_user_confirm") + private boolean needUserConfirm; + @SerializedName("profit_sharing") + private boolean profitSharing; + @SerializedName("post_payments") + private List postPayments; + @SerializedName("post_discounts") + private List postDiscounts; + @SerializedName("total_amount") + private int totalAmount; + @SerializedName("reason") + private String reason; + @SerializedName("goods_tag") + private String goodsTag; + @SerializedName("type") + private String type; + @SerializedName("detail") + private Detail detail; + +} diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/payscore/WxPayScoreResult.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/payscore/WxPayScoreResult.java new file mode 100644 index 0000000000..506148ad96 --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/payscore/WxPayScoreResult.java @@ -0,0 +1,109 @@ +package com.github.binarywang.wxpay.bean.payscore; + +import com.google.gson.annotations.SerializedName; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; +import java.util.List; +import java.util.Map; + +/** + * @author doger.wang + * @date 2020/5/12 17:05 + */ +@NoArgsConstructor +@Data +public class WxPayScoreResult implements Serializable { + private static final long serialVersionUID = 8809250065540275770L; + + /** + * appid : wxd678efh567hg6787 + * mchid : 1230000109 + * out_order_no : 1234323JKHDFE1243252 + * service_id : 500001 + * service_introduction : 某某酒店 + * state : CREATED + * state_description : MCH_COMPLETE + * post_payments : [{"name":"就餐费用服务费","amount":4000,"description":"就餐人均100元服务费:100/小时","count":1}] + * post_discounts : [{"name":"满20减1元","description":"不与其他优惠叠加"}] + * risk_fund : {"name":" ESTIMATE_ORDER_COST","amount":10000,"description":"就餐的预估费用"} + * time_range : {"start_time":"20091225091010","end_time":"20091225121010"} + * location : {"start_location":"嗨客时尚主题展餐厅","end_location":"嗨客时尚主题展餐厅"} + * attach : Easdfowealsdkjfnlaksjdlfkwqoi&wl3l2sald + * notify_url : https://api.test.com + * order_id : 15646546545165651651 + * package : DJIOSQPYWDxsjdldeuwhdodwxasd_dDiodnwjh9we + */ + @SerializedName("appid") + private String appid; + @SerializedName("mchid") + private String mchid; + @SerializedName("out_order_no") + private String outOrderNo; + @SerializedName("service_id") + private String serviceId; + @SerializedName("service_introduction") + private String serviceIntroduction; + @SerializedName("state") + private String state; + @SerializedName("state_description") + private String stateDescription; + @SerializedName("risk_fund") + private RiskFund riskFund; + @SerializedName("time_range") + private TimeRange timeRange; + @SerializedName("location") + private Location location; + @SerializedName("attach") + private String attach; + @SerializedName("notify_url") + private String notifyUrl; + @SerializedName("order_id") + private String orderId; + @SerializedName("package") + private String packageX; + @SerializedName("post_payments") + private List postPayments; + @SerializedName("post_discounts") + private List postDiscounts; + @SerializedName("need_collection") + private boolean needCollection; + /** + * 收款信息 + */ + @SerializedName("collection") + private Collection collection; + /** + * 用于跳转的sign注意区分需确认模式和无需确认模式的数据差别。创单接口会返回,查询请自行组装 + */ + @SerializedName("payScoreSignInfo") + private Map payScoreSignInfo; + + /** + * 收款信息 + */ + @Data + @NoArgsConstructor + public static class Collection implements Serializable { + private static final long serialVersionUID = 2279516555276133086L; + /** + * state : USER_PAID + * total_amount : 3900 + * paying_amount : 3000 + * paid_amount : 900 + * details : [{"seq":1,"amount":900,"paid_type":"NEWTON","paid_time":"20091225091210","transaction_id":"15646546545165651651"}] + */ + @SerializedName("state") + private String state; + @SerializedName("total_amount") + private int totalAmount; + @SerializedName("paying_amount") + private int payingAmount; + @SerializedName("paid_amount") + private int paidAmount; + @SerializedName("details") + private List details; + } + +} diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/profitsharing/ProfitSharingFinishRequest.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/profitsharing/ProfitSharingFinishRequest.java index 3a0ee93648..c8200b4cc9 100644 --- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/profitsharing/ProfitSharingFinishRequest.java +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/profitsharing/ProfitSharingFinishRequest.java @@ -66,10 +66,15 @@ public class ProfitSharingFinishRequest extends BaseWxPayRequest { private String description; @Override - protected void checkConstraints() throws WxPayException { + protected void checkConstraints() { this.setSignType(WxPayConstants.SignType.HMAC_SHA256); } + @Override + protected boolean ignoreSubAppId() { + return true; + } + @Override protected void storeMap(Map map) { map.put("transaction_id", transactionId); diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/profitsharing/ProfitSharingQueryRequest.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/profitsharing/ProfitSharingQueryRequest.java index 5f5282dbd6..d342153a94 100644 --- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/profitsharing/ProfitSharingQueryRequest.java +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/profitsharing/ProfitSharingQueryRequest.java @@ -61,6 +61,11 @@ public boolean ignoreAppid() { return true; } + @Override + protected boolean ignoreSubAppId() { + return true; + } + @Override protected void storeMap(Map map) { map.put("transaction_id", transactionId); diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/profitsharing/ProfitSharingQueryResult.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/profitsharing/ProfitSharingQueryResult.java index 49fdf74552..03dd6b212a 100644 --- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/profitsharing/ProfitSharingQueryResult.java +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/profitsharing/ProfitSharingQueryResult.java @@ -4,12 +4,15 @@ import com.google.gson.FieldNamingPolicy; import com.google.gson.Gson; import com.google.gson.GsonBuilder; +import com.google.gson.reflect.TypeToken; import com.thoughtworks.xstream.annotations.XStreamAlias; import lombok.Data; import lombok.EqualsAndHashCode; import lombok.NoArgsConstructor; import org.w3c.dom.Document; +import java.util.List; + /** * @author Wang GuangXin 2019/10/22 15:51 * @version 1.0 @@ -33,7 +36,7 @@ public class ProfitSharingQueryResult extends BaseWxPayResult { /** * 微信分账单号 */ - @XStreamAlias("orderId") + @XStreamAlias("order_id") private String orderId; /** * 分账单状态 @@ -49,7 +52,11 @@ public class ProfitSharingQueryResult extends BaseWxPayResult { * 分账接收方列表 */ @XStreamAlias("receivers") - private String receivers; + private String receiversJson; + /** + * 分账接收方列表json转换后的对象 + */ + private List receivers; /** * 分账金额 */ @@ -61,27 +68,30 @@ public class ProfitSharingQueryResult extends BaseWxPayResult { @XStreamAlias("description") private String description; - public ProfitSharingQueryResult.Receivers formatReceivers() { + public List formatReceivers() { GsonBuilder gsonBuilder = new GsonBuilder(); gsonBuilder.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES); Gson gson = gsonBuilder.create(); - return gson.fromJson(receivers, Receivers.class); + final List receivers = gson.fromJson(receiversJson, new TypeToken >() { + }.getType()); + this.receivers = receivers; + return receivers; } @Override protected void loadXML(Document d) { transactionId = readXMLString(d, "transaction_id"); outOrderNo = readXMLString(d, "out_order_no"); - orderId = readXMLString(d, "orderId"); + orderId = readXMLString(d, "order_id"); status = readXMLString(d, "status"); closeReason = readXMLString(d, "close_reason"); - receivers = readXMLString(d, "receivers"); + receiversJson = readXMLString(d, "receivers"); amount = readXMLInteger(d, "amount"); description = readXMLString(d, "description"); } @Data - public class Receivers { + public class Receiver { /** * 分账接收方类型 */ diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/request/BaseWxPayRequest.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/request/BaseWxPayRequest.java index d60e56893b..f19935e7e1 100644 --- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/request/BaseWxPayRequest.java +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/request/BaseWxPayRequest.java @@ -170,6 +170,13 @@ private void checkFields() throws WxPayException { */ protected abstract void checkConstraints() throws WxPayException; + /** + * 是否需要nonce_str + */ + protected boolean needNonceStr() { + return true; + } + /** * 如果配置中已经设置,可以不设置值. * @@ -221,8 +228,6 @@ public String toXML() { /** * 使用快速算法组装xml - * - * @return */ private String toFastXml() { try { @@ -297,7 +302,7 @@ protected String[] getIgnoredParamsForSign() { * 注意:不含sign属性 */ public Map
getSignParams() { - Map map = new HashMap<>(); + Map map = new HashMap<>(8); map.put("appid", appid); map.put("mch_id", mchId); map.put("sub_appid", subAppId); @@ -365,7 +370,7 @@ public void checkAndSign(WxPayConfig config) throws WxPayException { } } - if (StringUtils.isBlank(getNonceStr())) { + if (needNonceStr() && StringUtils.isBlank(getNonceStr())) { this.setNonceStr(String.valueOf(System.currentTimeMillis())); } diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/request/WxPayFaceAuthInfoRequest.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/request/WxPayFaceAuthInfoRequest.java index cef831a3d6..1e81b0ad41 100644 --- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/request/WxPayFaceAuthInfoRequest.java +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/request/WxPayFaceAuthInfoRequest.java @@ -2,6 +2,7 @@ import com.thoughtworks.xstream.annotations.XStreamAlias; import lombok.*; +import lombok.experimental.Accessors; import me.chanjar.weixin.common.annotation.Required; import java.util.Map; @@ -16,12 +17,14 @@ * @author XxPay */ @Data +@Accessors(chain = true) @EqualsAndHashCode(callSuper = true) @Builder(builderMethodName = "newBuilder") @NoArgsConstructor @AllArgsConstructor @XStreamAlias("xml") public class WxPayFaceAuthInfoRequest extends BaseWxPayRequest { + private static final long serialVersionUID = -2909189635374300870L; /** * @@ -127,13 +130,13 @@ protected void checkConstraints() { @Override protected void storeMap(Map*/ @XStreamAlias("detail") + @XStreamConverter(value = XStreamCDataConverter.class) private String detail; /** @@ -124,6 +127,7 @@ public class WxPayUnifiedOrderRequest extends BaseWxPayRequest { *map) { + map.put("now", now); + map.put("version", version); + map.put("rawdata", rawdata); map.put("store_id", storeId); map.put("store_name", storeName); map.put("device_id", deviceId); map.put("attach", attach); - map.put("rawdata", rawdata); - map.put("now", now); - map.put("version", version); } } diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/request/WxPayMicropayRequest.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/request/WxPayMicropayRequest.java index cdded3110d..779dbdce79 100644 --- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/request/WxPayMicropayRequest.java +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/request/WxPayMicropayRequest.java @@ -21,6 +21,8 @@ @AllArgsConstructor @XStreamAlias("xml") public class WxPayMicropayRequest extends BaseWxPayRequest { + private static final long serialVersionUID = 100577773033376092L; + /** * * 字段名:设备号. @@ -205,7 +207,6 @@ public class WxPayMicropayRequest extends BaseWxPayRequest { * 描述:Y,传入Y时,支付成功消息和支付详情页将出现开票入口。需要在微信支付商户平台或微信公众平台开通电子发票功能,传此字段才可生效 ***/ - @Required @XStreamAlias("receipt") private String receipt; @@ -273,6 +274,7 @@ protected void storeMap(Mapmap) { map.put("time_expire", timeExpire); map.put("auth_code", authCode); map.put("scene_info", sceneInfo); + map.put("profit_sharing",profitSharing); } } diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/request/WxPayQueryExchangeRateRequest.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/request/WxPayQueryExchangeRateRequest.java new file mode 100644 index 0000000000..c4b453b9bd --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/request/WxPayQueryExchangeRateRequest.java @@ -0,0 +1,61 @@ +package com.github.binarywang.wxpay.bean.request; + +import com.github.binarywang.wxpay.exception.WxPayException; +import com.thoughtworks.xstream.annotations.XStreamAlias; +import lombok.*; +import lombok.experimental.Accessors; + +import java.util.Map; + +/** + * 查询汇率请求. + * + * @author Binary Wang + * @date 2020-05-23 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@Builder +@Accessors(chain = true) +@NoArgsConstructor +@AllArgsConstructor +@XStreamAlias("xml") +public class WxPayQueryExchangeRateRequest extends BaseWxPayRequest { + private static final long serialVersionUID = -8796516942563060554L; + /** + * 币种 + * fee_type + * 是 + * String(10) + * USD + * 外币币种 + */ + @XStreamAlias("fee_type") + private String feeType; + + /** + * 日期 + * date + * 是 + * String(14) + * 20150807 + * 格式为yyyyMMdd,如2009年12月25日表示为20091225。时区为GMT+8 beijing + */ + @XStreamAlias("date") + private String date; + + @Override + protected void checkConstraints() throws WxPayException { + + } + + @Override + protected void storeMap(Map map) { + + } + + @Override + protected boolean needNonceStr() { + return false; + } +} diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/request/WxPayUnifiedOrderRequest.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/request/WxPayUnifiedOrderRequest.java index fc5949dfdf..3a80c82787 100644 --- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/request/WxPayUnifiedOrderRequest.java +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/request/WxPayUnifiedOrderRequest.java @@ -4,9 +4,11 @@ import com.github.binarywang.wxpay.constant.WxPayConstants.TradeType; import com.github.binarywang.wxpay.exception.WxPayException; import com.thoughtworks.xstream.annotations.XStreamAlias; +import com.thoughtworks.xstream.annotations.XStreamConverter; import lombok.*; import lombok.experimental.Accessors; import me.chanjar.weixin.common.annotation.Required; +import me.chanjar.weixin.common.util.xml.XStreamCDataConverter; import org.apache.commons.lang3.StringUtils; import java.util.Map; @@ -111,6 +113,7 @@ public class WxPayUnifiedOrderRequest extends BaseWxPayRequest { * * 查询订单 返回结果对象 @@ -77,7 +73,7 @@ public class WxPayOrderQueryResult extends BaseWxPayResult { ** 是否关注公众账号. * is_subscribe - * 否 + * 是 * String(1) * Y * 用户是否关注公众账号,Y-关注,N-未关注,仅在公众账号类型支付有效 @@ -86,6 +82,32 @@ public class WxPayOrderQueryResult extends BaseWxPayResult { @XStreamAlias("is_subscribe") private String isSubscribe; + /** + *+ * 用户子标识 . + * sub_openid + * 否 + * String(128) + * oUpF8uMuAJO_M2pxb1Q9zNjWeS6o + * 用户在子商户appid下的唯一标识 + *+ */ + @XStreamAlias("sub_openid") + private String subOpenid; + + /** + *+ * 是否关注子公众账号. + * sub_is_subscribe + * 否 + * String(1) + * Y + * 用户是否关注子公众账号,Y-关注,N-未关注(机构商户不返回) + *+ */ + @XStreamAlias("sub_is_subscribe") + private String isSubscribeSub; + /** ** 交易类型. @@ -125,6 +147,25 @@ public class WxPayOrderQueryResult extends BaseWxPayResult { @XStreamAlias("bank_type") private String bankType; + /** + *+ * 商品详情. + * detail + * 否 + * String(8192) + * 商品详细列表,使用Json格式,传输签名前请务必使用CDATA标签将JSON文本串保护起来。如果使用了单品优惠,会有单品优惠信息返回 + * + * discount_detail []: + * └ goods_id String 必填 32 商品的编号 + * └ goods_name String 必填 256 商品名称 + * └ coupon_batch_id String 必填 代金券批次ID + * └ coupon_id String 必填 代金卷ID + * └ coupon_fee Int 必填 代金券支付金额,单位为分 + *+ **/ + @XStreamAlias("detail") + private String detail; + /** ** 订单金额. @@ -138,19 +179,6 @@ public class WxPayOrderQueryResult extends BaseWxPayResult { @XStreamAlias("total_fee") private Integer totalFee; - /** - *- * 应结订单金额. - * settlement_total_fee - * 否 - * Int - * 100 - * 应结订单金额=订单金额-非充值代金券金额,应结订单金额<=订单金额。 - *- */ - @XStreamAlias("settlement_total_fee") - private Integer settlementTotalFee; - /** ** 货币种类. @@ -164,6 +192,19 @@ public class WxPayOrderQueryResult extends BaseWxPayResult { @XStreamAlias("fee_type") private String feeType; + /** + *+ * 应结订单金额. + * settlement_total_fee + * 否 + * Int + * 100 + * 应结订单金额=订单金额-非充值代金券金额,应结订单金额<=订单金额。 + *+ */ + @XStreamAlias("settlement_total_fee") + private Integer settlementTotalFee; + /** ** 现金支付金额. @@ -240,6 +281,7 @@ public class WxPayOrderQueryResult extends BaseWxPayResult { */ @XStreamAlias("out_trade_no") private String outTradeNo; + /** ** 附加数据. @@ -252,6 +294,7 @@ public class WxPayOrderQueryResult extends BaseWxPayResult { */ @XStreamAlias("attach") private String attach; + /** ** 支付完成时间. @@ -264,6 +307,7 @@ public class WxPayOrderQueryResult extends BaseWxPayResult { */ @XStreamAlias("time_end") private String timeEnd; + /** ** 交易状态描述. @@ -312,6 +356,11 @@ protected void loadXML(Document d) { cashFeeType = readXMLString(d, "cash_fee_type"); couponFee = readXMLInteger(d, "coupon_fee"); couponCount = readXMLInteger(d, "coupon_count"); + this.transactionId = readXMLString(d, "transaction_id"); + this.outTradeNo = readXMLString(d, "out_trade_no"); + this.attach = readXMLString(d, "attach"); + this.timeEnd = readXMLString(d, "time_end"); + this.tradeStateDesc = readXMLString(d, "trade_state_desc"); } /** diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/result/WxPayQueryExchangeRateResult.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/result/WxPayQueryExchangeRateResult.java new file mode 100644 index 0000000000..d44d282e0f --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/result/WxPayQueryExchangeRateResult.java @@ -0,0 +1,58 @@ +package com.github.binarywang.wxpay.bean.result; + +import com.thoughtworks.xstream.annotations.XStreamAlias; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import org.w3c.dom.Document; + +/** + * 汇率查询响应. + * + * @author Binary Wang + * @date 2020-05-23 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@NoArgsConstructor +@XStreamAlias("xml") +public class WxPayQueryExchangeRateResult extends BaseWxPayResult { + private static final long serialVersionUID = 2269734222658532364L; + + /** + * 币种 + * fee_type + * 是 + * String(10) + * SUCCESS 外币币种,详细请见参数规定 + */ + @XStreamAlias("fee_type") + private String feeType; + + /** + * 汇率时间 + * rate_time + * 是 + * String(14) + * 20150807131545 + * 格式:yyyyMMddhhmmss + */ + @XStreamAlias("rate_time") + private String rateTime; + + /** + * 现汇卖出价 + * rate + * 是 + * String(15) + * 系统错误 + * 外币标准单位乘以100折算为人民币的金额,保留4位小数(如:100美元按当时汇率折算返回的先汇卖出价是628.2100) + */ + @XStreamAlias("rate") + private String rate; + + @Override + protected void loadXML(Document d) { + + } +} diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/result/WxPayRedpackQueryResult.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/result/WxPayRedpackQueryResult.java index 91c6eb7f22..669e01f7fb 100644 --- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/result/WxPayRedpackQueryResult.java +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/result/WxPayRedpackQueryResult.java @@ -1,9 +1,5 @@ package com.github.binarywang.wxpay.bean.result; -import java.io.Serializable; -import java.util.ArrayList; -import java.util.List; - import com.thoughtworks.xstream.annotations.XStreamAlias; import lombok.Data; import lombok.EqualsAndHashCode; @@ -12,6 +8,10 @@ import org.w3c.dom.Node; import org.w3c.dom.NodeList; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + /** ** Created by Binary Wang on 2016-11-28. @@ -241,6 +241,7 @@ protected void loadXML(Document d) { hbType = readXMLString(d, "hb_type"); totalNum = readXMLInteger(d, "total_num"); totalAmount = readXMLInteger(d, "total_amount"); + reason = readXMLString(d, "reason"); sendTime = readXMLString(d, "send_time"); refundTime = readXMLString(d, "refund_time"); refundAmount = readXMLInteger(d, "refund_amount"); diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/result/WxPaySendRedpackResult.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/result/WxPaySendRedpackResult.java index adc6a688fc..49390da897 100644 --- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/result/WxPaySendRedpackResult.java +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/result/WxPaySendRedpackResult.java @@ -10,7 +10,7 @@ /** * 向微信用户个人发现金红包返回结果 - * https://pay.weixin.qq.com/wiki/doc/api/cash_coupon.php?chapter=13_5 + * https://pay.weixin.qq.com/wiki/doc/api/tools/cash_coupon.php?chapter=13_4&index=3 * * @author kane */ diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/config/WxPayConfig.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/config/WxPayConfig.java index c0ca2cf552..6d9bebdc6d 100644 --- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/config/WxPayConfig.java +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/config/WxPayConfig.java @@ -1,15 +1,27 @@ package com.github.binarywang.wxpay.config; import com.github.binarywang.wxpay.exception.WxPayException; +import com.github.binarywang.wxpay.v3.WxPayV3HttpClientBuilder; +import com.github.binarywang.wxpay.v3.auth.AutoUpdateCertificatesVerifier; +import com.github.binarywang.wxpay.v3.auth.PrivateKeySigner; +import com.github.binarywang.wxpay.v3.auth.WxPayCredentials; +import com.github.binarywang.wxpay.v3.auth.WxPayValidator; +import com.github.binarywang.wxpay.v3.util.PemUtils; +import jodd.util.ResourcesUtil; import lombok.Data; import org.apache.commons.io.IOUtils; +import org.apache.commons.lang3.RegExUtils; import org.apache.commons.lang3.StringUtils; +import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.ssl.SSLContexts; import javax.net.ssl.SSLContext; import java.io.*; import java.net.URL; +import java.nio.charset.StandardCharsets; import java.security.KeyStore; +import java.security.PrivateKey; +import java.util.Collections; /** * 微信支付配置 @@ -19,6 +31,8 @@ @Data public class WxPayConfig { private static final String DEFAULT_PAY_BASE_URL = "https://api.mch.weixin.qq.com"; + private static final String PROBLEM_MSG = "证书文件【%s】有问题,请核实!"; + private static final String NOT_FOUND_MSG = "证书文件【%s】不存在,请核实!"; /** * 微信支付接口请求地址域名部分. @@ -85,6 +99,39 @@ public class WxPayConfig { */ private String keyPath; + /** + * apiclient_key.pem证书文件的绝对路径或者以classpath:开头的类路径. + */ + private String privateKeyPath; + /** + * apiclient_cert.pem证书文件的绝对路径或者以classpath:开头的类路径. + */ + private String privateCertPath; + + /** + * apiV3 秘钥值. + */ + private String apiV3Key; + + /** + * apiV3 证书序列号值 + */ + private String certSerialNo; + + + /** + * 微信支付分serviceId + */ + private String serviceId; + + /** + * 微信支付分回调地址 + */ + private String payScoreNotifyUrl; + + private CloseableHttpClient apiV3HttpClient; + + /** * p12证书文件内容的字节数组. */ @@ -108,6 +155,7 @@ public class WxPayConfig { /** * 返回所设置的微信支付接口请求地址域名. + * * @return 微信支付接口请求地址域名 */ public String getPayBaseUrl() { @@ -138,16 +186,20 @@ public SSLContext initSSLContext() throws WxPayException { } final String prefix = "classpath:"; - String fileHasProblemMsg = "证书文件【" + this.getKeyPath() + "】有问题,请核实!"; - String fileNotFoundMsg = "证书文件【" + this.getKeyPath() + "】不存在,请核实!"; + String fileHasProblemMsg = String.format(PROBLEM_MSG, this.getKeyPath()); + String fileNotFoundMsg = String.format(NOT_FOUND_MSG, this.getKeyPath()); if (this.getKeyPath().startsWith(prefix)) { - String path = StringUtils.removeFirst(this.getKeyPath(), prefix); + String path = RegExUtils.removeFirst(this.getKeyPath(), prefix); if (!path.startsWith("/")) { path = "/" + path; } - inputStream = WxPayConfig.class.getResourceAsStream(path); - if (inputStream == null) { - throw new WxPayException(fileNotFoundMsg); + try { + inputStream = ResourcesUtil.getResourceAsStream(path); + if (inputStream == null) { + throw new WxPayException(fileNotFoundMsg); + } + } catch (Exception e) { + throw new WxPayException(fileNotFoundMsg, e); } } else if (this.getKeyPath().startsWith("http://") || this.getKeyPath().startsWith("https://")) { try { @@ -185,4 +237,77 @@ public SSLContext initSSLContext() throws WxPayException { } } + /** + * 初始化api v3请求头 自动签名验签 + * 方法参照微信官方https://github.com/wechatpay-apiv3/wechatpay-apache-httpclient + * + * @return org.apache.http.impl.client.CloseableHttpClient + * @author doger.wang + **/ + public CloseableHttpClient initApiV3HttpClient() throws WxPayException { + String privateKeyPath = this.getPrivateKeyPath(); + String privateCertPath = this.getPrivateCertPath(); + String certSerialNo = this.getCertSerialNo(); + String apiV3Key = this.getApiV3Key(); + if (StringUtils.isBlank(privateKeyPath)) { + throw new WxPayException("请确保privateKeyPath已设置"); + } + if (StringUtils.isBlank(privateCertPath)) { + throw new WxPayException("请确保privateCertPath已设置"); + } + if (StringUtils.isBlank(certSerialNo)) { + throw new WxPayException("请确保certSerialNo证书序列号已设置"); + } + if (StringUtils.isBlank(apiV3Key)) { + throw new WxPayException("请确保apiV3Key值已设置"); + } + + InputStream keyInputStream = null; + InputStream certInputStream = null; + final String prefix = "classpath:"; + if (privateKeyPath.startsWith(prefix)) { + String keypath = RegExUtils.removeFirst(privateKeyPath, prefix); + if (!keypath.startsWith("/")) { + keypath = "/" + keypath; + } + try { + keyInputStream = ResourcesUtil.getResourceAsStream(keypath); + if (keyInputStream == null) { + throw new WxPayException(String.format(NOT_FOUND_MSG, this.getPrivateKeyPath())); + } + } catch (Exception e) { + throw new WxPayException(String.format(NOT_FOUND_MSG, this.getPrivateKeyPath()), e); + } + } + + if (privateCertPath.startsWith(prefix)) { + String certpath = RegExUtils.removeFirst(privateCertPath, prefix); + if (!certpath.startsWith("/")) { + certpath = "/" + certpath; + } + try { + certInputStream = ResourcesUtil.getResourceAsStream(certpath); + if (certInputStream == null) { + throw new WxPayException(String.format(NOT_FOUND_MSG, this.getPrivateCertPath())); + } + } catch (Exception e) { + throw new WxPayException(String.format(NOT_FOUND_MSG, this.getPrivateCertPath()), e); + } + } + + try { + PrivateKey merchantPrivateKey = PemUtils.loadPrivateKey(keyInputStream); + CloseableHttpClient httpClient = WxPayV3HttpClientBuilder.create() + .withMerchant(mchId, certSerialNo, merchantPrivateKey) + .withWechatpay(Collections.singletonList(PemUtils.loadCertificate(certInputStream))) + .withValidator(new WxPayValidator(new AutoUpdateCertificatesVerifier( + new WxPayCredentials(mchId, new PrivateKeySigner(certSerialNo, merchantPrivateKey)), + apiV3Key.getBytes(StandardCharsets.UTF_8)))) + .build(); + this.apiV3HttpClient = httpClient; + return httpClient; + } catch (Exception e) { + throw new WxPayException("v3请求构造异常!", e); + } + } } diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/constant/WxPayConstants.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/constant/WxPayConstants.java index 4f8480b6a2..fbc499fedd 100644 --- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/constant/WxPayConstants.java +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/constant/WxPayConstants.java @@ -1,5 +1,10 @@ package com.github.binarywang.wxpay.constant; +import com.github.binarywang.wxpay.bean.order.WxPayAppOrderResult; +import com.github.binarywang.wxpay.bean.order.WxPayMpOrderResult; +import com.github.binarywang.wxpay.bean.order.WxPayMwebOrderResult; +import com.github.binarywang.wxpay.bean.order.WxPayNativeOrderResult; +import com.github.binarywang.wxpay.bean.result.WxPayMicropayResult; import com.google.common.collect.Lists; import org.apache.commons.lang3.time.FastDateFormat; @@ -103,6 +108,55 @@ public static class TradeType { * 刷卡支付有单独的支付接口,不调用统一下单接口 */ public static final String MICROPAY = "MICROPAY"; + + @SuppressWarnings("unused") + public abstract static class Specific{ + + public abstract String getType(); + + private Specific() { + } + + public static Specific NATIVE = + new Specific () { + @Override + public String getType() { + return TradeType.NATIVE; + } + }; + + public static Specific APP = + new Specific () { + @Override + public String getType() { + return TradeType.APP; + } + }; + + public static Specific JSAPI = + new Specific () { + @Override + public String getType() { + return TradeType.JSAPI; + } + }; + + public static Specific MWEB = + new Specific () { + @Override + public String getType() { + return TradeType.MWEB; + } + }; + + public static Specific MICROPAY = + new Specific () { + @Override + public String getType() { + return TradeType.MICROPAY; + } + }; + } } /** diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/PayScoreService.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/PayScoreService.java new file mode 100644 index 0000000000..d4d3b245e3 --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/PayScoreService.java @@ -0,0 +1,140 @@ +package com.github.binarywang.wxpay.service; + +import com.github.binarywang.wxpay.bean.payscore.PayScoreNotifyData; +import com.github.binarywang.wxpay.bean.payscore.WxPayScoreRequest; +import com.github.binarywang.wxpay.bean.payscore.WxPayScoreResult; +import com.github.binarywang.wxpay.exception.WxPayException; + +import java.net.URISyntaxException; + +/** + * + * 支付分相关服务类. + * 微信支付分是对个人的身份特质、支付行为、使用历史等情况的综合计算分值,旨在为用户提供更简单便捷的生活方式。 + * 微信用户可以在具体应用场景中,开通微信支付分。开通后,用户可以在【微信—>钱包—>支付分】中查看分数和使用记录。 + * (即需在应用场景中使用过一次,钱包才会出现支付分入口) + * + * Created by doger.wang on 2020/05/12. + *+ * + * @author doger.wang + */ +public interface PayScoreService { + /** + *+ * 支付分创建订单API. + * 文档详见: https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/payscore/chapter3_1.shtml + * 接口链接:https://api.mch.weixin.qq.com/v3/payscore/serviceorder + *+ * + * @param request 请求对象 + * @return WxPayScoreResult wx pay score result + * @throws WxPayException the wx pay exception + */ + WxPayScoreResult createServiceOrder(WxPayScoreRequest request) throws WxPayException; + + /** + *+ * 支付分查询订单API. + * 文档详见: https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/payscore/chapter3_2.shtml + * 接口链接:https://api.mch.weixin.qq.com/v3/payscore/serviceorder + *+ * + * @param outOrderNo the out order no + * @param queryId the query id + * @return the wx pay score result + * @throws WxPayException the wx pay exception + */ + WxPayScoreResult queryServiceOrder(String outOrderNo, String queryId) throws WxPayException; + + /** + *+ * 支付分取消订单API. + * 文档详见: https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/payscore/chapter3_3.shtml + * 接口链接:https://api.mch.weixin.qq.com/v3/payscore/serviceorder/{out_order_no}/cancel + *+ * + * @param outOrderNo the out order no + * @param reason the reason + * @return com.github.binarywang.wxpay.bean.payscore.WxPayScoreResult wx pay score result + * @throws WxPayException the wx pay exception + */ + WxPayScoreResult cancelServiceOrder(String outOrderNo, String reason) throws WxPayException; + + /** + *+ * 支付分修改订单金额API. + * 文档详见: https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/payscore/chapter3_4.shtml + * 接口链接:https://api.mch.weixin.qq.com/v3/payscore/serviceorder/{out_order_no}/modify + *+ * + * @param request the request + * @return the wx pay score result + * @throws WxPayException the wx pay exception + */ + WxPayScoreResult modifyServiceOrder(WxPayScoreRequest request) throws WxPayException; + + /** + *+ * 支付分完结订单API. + * 文档详见: https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/payscore/chapter3_5.shtml + * 请求URL:https://api.mch.weixin.qq.com/v3/payscore/serviceorder/{out_order_no}/complete + *+ * + * @param request the request + * @return the wx pay score result + * @throws WxPayException the wx pay exception + */ + WxPayScoreResult completeServiceOrder(WxPayScoreRequest request) throws WxPayException; + + /** + *+ * 支付分订单收款API. + * 文档详见: https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/payscore/chapter3_6.shtml + * 请求URL:https://api.mch.weixin.qq.com/v3/payscore/serviceorder/{out_order_no}/pay + * + *+ * + * @param outOrderNo the out order no + * @return the wx pay score result + * @throws WxPayException the wx pay exception + */ + WxPayScoreResult payServiceOrder(String outOrderNo) throws WxPayException; + + /** + *+ * 支付分订单收款API. + * 文档详见: https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/payscore/chapter3_7.shtml + * 请求URL: https://api.mch.weixin.qq.com/v3/payscore/serviceorder/{out_order_no}/sync + *+ * + * @param request the request + * @return the wx pay score result + * @throws WxPayException the wx pay exception + */ + WxPayScoreResult syncServiceOrder(WxPayScoreRequest request) throws WxPayException; + + /** + *+ * 支付分回调内容解析方法 + * 文档详见: https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/payscore/chapter5_2.shtml + *+ * + * @param data the data + * @return the wx pay score result + */ + PayScoreNotifyData parseNotifyData(String data); + + /** + *+ * 支付分回调NotifyData解密resource + * 文档详见: https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/payscore/chapter5_2.shtml + *+ * + * @param data the data + * @return the wx pay score result + * @throws WxPayException the wx pay exception + */ + WxPayScoreResult decryptNotifyDataResource(PayScoreNotifyData data) throws WxPayException; + +} diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/WxPayService.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/WxPayService.java index 52f99af6c4..74958c1f91 100644 --- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/WxPayService.java +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/WxPayService.java @@ -8,9 +8,11 @@ import com.github.binarywang.wxpay.bean.request.*; import com.github.binarywang.wxpay.bean.result.*; import com.github.binarywang.wxpay.config.WxPayConfig; +import com.github.binarywang.wxpay.constant.WxPayConstants; import com.github.binarywang.wxpay.exception.WxPayException; import java.io.File; +import java.net.URI; import java.util.Date; import java.util.Map; @@ -53,6 +55,25 @@ public interface WxPayService { */ String post(String url, String requestStr, boolean useKey) throws WxPayException; + /** + * 发送post请求,得到响应字符串. + * + * @param url 请求地址 + * @param requestStr 请求信息 + * @return 返回请求结果字符串 string + * @throws WxPayException the wx pay exception + */ + String postV3(String url, String requestStr) throws WxPayException; + + /** + * 发送get V3请求,得到响应字符串. + * + * @param url 请求地址 + * @return 返回请求结果字符串 string + * @throws WxPayException the wx pay exception + */ + String getV3(URI url) throws WxPayException; + /** * 获取企业付款服务类. * @@ -74,6 +95,14 @@ public interface WxPayService { */ ProfitSharingService getProfitSharingService(); + + /** + * 获取支付分服务类. + * + * @return the ent pay service + */ + PayScoreService getPayScoreService(); + /** * 设置企业付款服务类,允许开发者自定义实现类. * @@ -166,6 +195,17 @@ public interface WxPayService { */T createOrder(WxPayUnifiedOrderRequest request) throws WxPayException; + /** + * 调用统一下单接口,并组装生成支付所需参数对象. + * + * @param specificTradeType 将使用的交易方式,不能为 null + * @param request 统一下单请求参数,设定的 tradeType 及配置里的 tradeType 将被忽略,转而使用 specificTradeType + * @return 返回 {@link WxPayConstants.TradeType.Specific} 指定的类 + * @throws WxPayException the wx pay exception + * @see WxPayService#createOrder(WxPayUnifiedOrderRequest) + */ + T createOrder(WxPayConstants.TradeType.Specific specificTradeType, WxPayUnifiedOrderRequest request) throws WxPayException; + /** * 统一下单(详见https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=9_1) * 在发起微信支付前,需要调用统一下单接口,获取"预支付交易会话标识" @@ -717,4 +757,18 @@ WxPayRefundQueryResult refundQuery(String transactionId, String outTradeNo, Stri */ WxPayFacepayResult facepay(WxPayFacepayRequest request) throws WxPayException; + /** + * 查询汇率 + * + * 应用场景:商户网站的商品以外币标价时,通过该接口可以实时查询到微信使用的转换汇率。汇率更新时间为北京时间上午10:00,一天更新一次。 + * 文档地址:https://pay.weixin.qq.com/wiki/doc/api/app/app_jw.php?chapter=9_15&index=12 + * 接口链接:https://api.mch.weixin.qq.com/pay/queryexchagerate + *+ * + * @param feeType 外币币种 + * @param date 日期,格式为yyyyMMdd,如2009年12月25日表示为20091225。时区为GMT+8 beijing + * @return . + * @throws WxPayException . + */ + WxPayQueryExchangeRateResult queryExchangeRate(String feeType, String date) throws WxPayException; } diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/BaseWxPayServiceImpl.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/BaseWxPayServiceImpl.java index 61dcee09ba..4d61522f58 100644 --- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/BaseWxPayServiceImpl.java +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/BaseWxPayServiceImpl.java @@ -13,13 +13,11 @@ import com.github.binarywang.wxpay.bean.request.*; import com.github.binarywang.wxpay.bean.result.*; import com.github.binarywang.wxpay.config.WxPayConfig; +import com.github.binarywang.wxpay.constant.WxPayConstants; import com.github.binarywang.wxpay.constant.WxPayConstants.SignType; import com.github.binarywang.wxpay.constant.WxPayConstants.TradeType; import com.github.binarywang.wxpay.exception.WxPayException; -import com.github.binarywang.wxpay.service.EntPayService; -import com.github.binarywang.wxpay.service.ProfitSharingService; -import com.github.binarywang.wxpay.service.RedpackService; -import com.github.binarywang.wxpay.service.WxPayService; +import com.github.binarywang.wxpay.service.*; import com.github.binarywang.wxpay.util.SignUtils; import com.github.binarywang.wxpay.util.XmlConfig; import com.google.common.base.Joiner; @@ -63,6 +61,7 @@ public abstract class BaseWxPayServiceImpl implements WxPayService { private EntPayService entPayService = new EntPayServiceImpl(this); private ProfitSharingService profitSharingService = new ProfitSharingServiceImpl(this); private RedpackService redpackService = new RedpackServiceImpl(this); + private PayScoreService payScoreService = new PayScoreServiceImpl(this); /** * The Config. @@ -79,6 +78,11 @@ public ProfitSharingService getProfitSharingService() { return profitSharingService; } + @Override + public PayScoreService getPayScoreService() { + return payScoreService; + } + @Override public RedpackService getRedpackService() { return this.redpackService; @@ -154,7 +158,7 @@ public WxPayOrderNotifyResult parseOrderNotifyResult(String xmlData) throws WxPa log.debug("微信支付异步通知请求参数:{}", xmlData); WxPayOrderNotifyResult result = WxPayOrderNotifyResult.fromXML(xmlData); log.debug("微信支付异步通知请求解析后的对象:{}", result); - result.checkResult(this, this.getConfig().getSignType(), false); + result.checkResult(this, result.getSignType(), false); return result; } catch (WxPayException e) { throw e; @@ -277,7 +281,7 @@ publicT createOrder(WxPayUnifiedOrderRequest request) throws WxPayException } String timestamp = String.valueOf(System.currentTimeMillis() / 1000); - String nonceStr = String.valueOf(System.currentTimeMillis()); + String nonceStr = unifiedOrderResult.getNonceStr(); switch (request.getTradeType()) { case TradeType.MWEB: { return (T) new WxPayMwebOrderResult(unifiedOrderResult.getMwebUrl()); @@ -351,6 +355,15 @@ public T createOrder(WxPayUnifiedOrderRequest request) throws WxPayException } + @Override + public T createOrder(TradeType.Specific specificTradeType, WxPayUnifiedOrderRequest request) throws WxPayException { + if (specificTradeType == null) { + throw new IllegalArgumentException("specificTradeType 不能为 null"); + } + request.setTradeType(specificTradeType.getType()); + return createOrder(request); + } + @Override public WxPayUnifiedOrderResult unifiedOrder(WxPayUnifiedOrderRequest request) throws WxPayException { request.checkAndSign(this.getConfig()); @@ -374,7 +387,7 @@ public Map getPayInfo(WxPayUnifiedOrderRequest request) throws W Map payInfo = new HashMap<>(); String timestamp = String.valueOf(System.currentTimeMillis() / 1000); - String nonceStr = String.valueOf(System.currentTimeMillis()); + String nonceStr = unifiedOrderResult.getNonceStr(); if (TradeType.NATIVE.equals(request.getTradeType())) { payInfo.put("codeUrl", unifiedOrderResult.getCodeURL()); } else if (TradeType.APP.equals(request.getTradeType())) { @@ -795,6 +808,10 @@ public String queryComment(WxPayQueryCommentRequest request) throws WxPayExcepti @Override public WxPayFaceAuthInfoResult getWxPayFaceAuthInfo(WxPayFaceAuthInfoRequest request) throws WxPayException { + if (StringUtils.isEmpty(request.getSignType())) { + request.setSignType(WxPayConstants.SignType.MD5); + } + request.checkAndSign(this.getConfig()); String url = "https://payapp.weixin.qq.com/face/get_wxpayface_authinfo"; String responseContent = this.post(url, request.toXML(), false); @@ -814,4 +831,18 @@ public WxPayFacepayResult facepay(WxPayFacepayRequest request) throws WxPayExcep return result; } + @Override + public WxPayQueryExchangeRateResult queryExchangeRate(String feeType, String date) throws WxPayException { + WxPayQueryExchangeRateRequest request = new WxPayQueryExchangeRateRequest(); + request.setFeeType(feeType); + request.setDate(date); + + request.checkAndSign(this.getConfig()); + + String url = this.getPayBaseUrl() + "/pay/queryexchagerate"; + String responseContent = this.post(url, request.toXML(), false); + WxPayQueryExchangeRateResult result = BaseWxPayResult.fromXML(responseContent, WxPayQueryExchangeRateResult.class); + result.checkResult(this, request.getSignType(), true); + return result; + } } diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/PayScoreServiceImpl.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/PayScoreServiceImpl.java new file mode 100644 index 0000000000..14950a5b2d --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/PayScoreServiceImpl.java @@ -0,0 +1,172 @@ +package com.github.binarywang.wxpay.service.impl; + +import com.github.binarywang.wxpay.bean.payscore.PayScoreNotifyData; +import com.github.binarywang.wxpay.bean.payscore.WxPayScoreRequest; +import com.github.binarywang.wxpay.bean.payscore.WxPayScoreResult; +import com.github.binarywang.wxpay.config.WxPayConfig; +import com.github.binarywang.wxpay.exception.WxPayException; +import com.github.binarywang.wxpay.service.PayScoreService; +import com.github.binarywang.wxpay.service.WxPayService; +import com.github.binarywang.wxpay.v3.util.AesUtils; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import lombok.RequiredArgsConstructor; +import org.apache.commons.lang3.StringUtils; +import org.apache.http.client.utils.URIBuilder; + +import java.io.IOException; +import java.net.URISyntaxException; +import java.security.GeneralSecurityException; +import java.util.HashMap; +import java.util.Map; + +/** + * @author doger.wang + * @date 2020/5/14 9:43 + */ +@RequiredArgsConstructor +public class PayScoreServiceImpl implements PayScoreService { + private static final Gson GSON = new GsonBuilder().create(); + private final WxPayService payService; + + @Override + public WxPayScoreResult createServiceOrder(WxPayScoreRequest request) throws WxPayException { + boolean needUserConfirm = request.isNeedUserConfirm(); + WxPayConfig config = this.payService.getConfig(); + String url = this.payService.getPayBaseUrl() + "/v3/payscore/serviceorder"; + request.setAppid(config.getAppId()); + request.setServiceId(config.getServiceId()); + request.setNotifyUrl(config.getPayScoreNotifyUrl()); + String result = payService.postV3(url, GSON.toJson(request)); + WxPayScoreResult wxPayScoreCreateResult = GSON.fromJson(result, WxPayScoreResult.class); + + //补充算一下签名给小程序跳转用 + String currentTimeMillis = System.currentTimeMillis() + ""; + Map signMap = new HashMap<>(8); + signMap.put("mch_id", config.getMchId()); + if (needUserConfirm) { + signMap.put("package", wxPayScoreCreateResult.getPackageX()); + } else { + signMap.put("service_id", config.getServiceId()); + signMap.put("out_order_no", request.getOutOrderNo()); + } + signMap.put("timestamp", currentTimeMillis); + signMap.put("nonce_str", currentTimeMillis); + signMap.put("sign_type", "HMAC-SHA256"); + String sign = AesUtils.createSign(signMap, config.getMchKey()); + signMap.put("sign", sign); + wxPayScoreCreateResult.setPayScoreSignInfo(signMap); + return wxPayScoreCreateResult; + } + + @Override + public WxPayScoreResult queryServiceOrder(String outOrderNo, String queryId) throws WxPayException { + WxPayConfig config = this.payService.getConfig(); + String url = this.payService.getPayBaseUrl() + "/v3/payscore/serviceorder"; + + URIBuilder uriBuilder; + try { + uriBuilder = new URIBuilder(url); + } catch (URISyntaxException e) { + throw new WxPayException("未知异常!", e); + } + + if (StringUtils.isAllEmpty(outOrderNo, queryId) || !StringUtils.isAnyEmpty(outOrderNo, queryId)) { + throw new WxPayException("out_order_no,query_id不允许都填写或都不填写"); + } + if (StringUtils.isNotEmpty(outOrderNo)) { + uriBuilder.setParameter("out_order_no", outOrderNo); + } + if (StringUtils.isNotEmpty(queryId)) { + uriBuilder.setParameter("query_id", queryId); + } + uriBuilder.setParameter("service_id", config.getServiceId()); + uriBuilder.setParameter("appid", config.getAppId()); + try { + String result = payService.getV3(uriBuilder.build()); + return GSON.fromJson(result, WxPayScoreResult.class); + } catch (URISyntaxException e) { + throw new WxPayException("未知异常!", e); + } + + } + + @Override + public WxPayScoreResult cancelServiceOrder(String outOrderNo, String reason) throws WxPayException { + WxPayConfig config = this.payService.getConfig(); + String url = String.format("%s/v3/payscore/serviceorder/%s/cancel", this.payService.getPayBaseUrl(), outOrderNo); + HashMap map = new HashMap<>(4); + map.put("appid", config.getAppId()); + map.put("service_id", config.getServiceId()); + map.put("reason", reason); + String result = payService.postV3(url, GSON.toJson(map)); + return GSON.fromJson(result, WxPayScoreResult.class); + } + + @Override + public WxPayScoreResult modifyServiceOrder(WxPayScoreRequest request) throws WxPayException { + WxPayConfig config = this.payService.getConfig(); + String outOrderNo = request.getOutOrderNo(); + String url = String.format("%s/v3/payscore/serviceorder/%s/modify", this.payService.getPayBaseUrl(), outOrderNo); + request.setAppid(config.getAppId()); + request.setServiceId(config.getServiceId()); + request.setOutOrderNo(null); + String result = payService.postV3(url, GSON.toJson(request)); + return GSON.fromJson(result, WxPayScoreResult.class); + } + + @Override + public WxPayScoreResult completeServiceOrder(WxPayScoreRequest request) throws WxPayException { + WxPayConfig config = this.payService.getConfig(); + String outOrderNo = request.getOutOrderNo(); + String url = String.format("%s/v3/payscore/serviceorder/%s/complete", this.payService.getPayBaseUrl(), outOrderNo); + request.setAppid(config.getAppId()); + request.setServiceId(config.getServiceId()); + request.setOutOrderNo(null); + String result = payService.postV3(url, GSON.toJson(request)); + return GSON.fromJson(result, WxPayScoreResult.class); + } + + @Override + public WxPayScoreResult payServiceOrder(String outOrderNo) throws WxPayException { + WxPayConfig config = this.payService.getConfig(); + String url = String.format("%s/v3/payscore/serviceorder/%s/pay", this.payService.getPayBaseUrl(), outOrderNo); + HashMap map = new HashMap<>(2); + map.put("appid", config.getAppId()); + map.put("service_id", config.getServiceId()); + String result = payService.postV3(url, GSON.toJson(map)); + return GSON.fromJson(result, WxPayScoreResult.class); + } + + @Override + public WxPayScoreResult syncServiceOrder(WxPayScoreRequest request) throws WxPayException { + WxPayConfig config = this.payService.getConfig(); + String outOrderNo = request.getOutOrderNo(); + String url = String.format("%s/v3/payscore/serviceorder/%s/sync", this.payService.getPayBaseUrl(), outOrderNo); + request.setAppid(config.getAppId()); + request.setServiceId(config.getServiceId()); + request.setOutOrderNo(null); + String result = payService.postV3(url, GSON.toJson(request)); + return GSON.fromJson(result, WxPayScoreResult.class); + } + + @Override + public PayScoreNotifyData parseNotifyData(String data) { + return GSON.fromJson(data, PayScoreNotifyData.class); + } + + @Override + public WxPayScoreResult decryptNotifyDataResource(PayScoreNotifyData data) throws WxPayException { + PayScoreNotifyData.Resource resource = data.getResource(); + String cipherText = resource.getCipherText(); + String associatedData = resource.getAssociatedData(); + String nonce = resource.getNonce(); + String apiV3Key = this.payService.getConfig().getApiV3Key(); + try { + String s = AesUtils.decryptToString(associatedData, nonce, cipherText, apiV3Key); + return GSON.fromJson(s, WxPayScoreResult.class); + } catch (GeneralSecurityException | IOException e) { + throw new WxPayException("解析报文异常!", e); + } + } +} diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/ProfitSharingServiceImpl.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/ProfitSharingServiceImpl.java index 09e305d19a..1055320997 100644 --- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/ProfitSharingServiceImpl.java +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/ProfitSharingServiceImpl.java @@ -81,6 +81,7 @@ public ProfitSharingQueryResult profitSharingQuery(ProfitSharingQueryRequest req String responseContent = this.payService.post(url, request.toXML(), true); ProfitSharingQueryResult result = BaseWxPayResult.fromXML(responseContent, ProfitSharingQueryResult.class); + result.formatReceivers(); result.checkResult(this.payService, request.getSignType(), true); return result; } diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/WxPayServiceApacheHttpImpl.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/WxPayServiceApacheHttpImpl.java index 1703c200f5..1b2d08984a 100644 --- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/WxPayServiceApacheHttpImpl.java +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/WxPayServiceApacheHttpImpl.java @@ -1,26 +1,23 @@ package com.github.binarywang.wxpay.service.impl; -import java.io.UnsupportedEncodingException; -import java.nio.charset.StandardCharsets; -import javax.net.ssl.SSLContext; - import com.github.binarywang.wxpay.bean.WxPayApiData; -import com.github.binarywang.wxpay.bean.request.WxPayQueryCommentRequest; -import com.github.binarywang.wxpay.bean.request.WxPayRedpackQueryRequest; -import com.github.binarywang.wxpay.bean.result.WxPayCommonResult; -import com.github.binarywang.wxpay.bean.result.WxPayRedpackQueryResult; import com.github.binarywang.wxpay.exception.WxPayException; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; import jodd.util.Base64; import org.apache.commons.lang3.StringUtils; import org.apache.http.HttpHost; +import org.apache.http.HttpStatus; import org.apache.http.auth.AuthScope; import org.apache.http.auth.UsernamePasswordCredentials; import org.apache.http.client.CredentialsProvider; 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.client.methods.HttpPost; import org.apache.http.conn.ssl.DefaultHostnameVerifier; import org.apache.http.conn.ssl.SSLConnectionSocketFactory; +import org.apache.http.entity.ContentType; import org.apache.http.entity.StringEntity; import org.apache.http.impl.client.BasicCredentialsProvider; import org.apache.http.impl.client.CloseableHttpClient; @@ -28,9 +25,9 @@ import org.apache.http.impl.client.HttpClients; import org.apache.http.util.EntityUtils; -import com.github.binarywang.wxpay.bean.WxPayApiData; -import com.github.binarywang.wxpay.exception.WxPayException; -import jodd.util.Base64; +import javax.net.ssl.SSLContext; +import java.net.URI; +import java.nio.charset.StandardCharsets; /** * @@ -41,6 +38,8 @@ * @author Binary Wang */ public class WxPayServiceApacheHttpImpl extends BaseWxPayServiceImpl { + private final static JsonParser JSON_PARSER = new JsonParser(); + @Override public byte[] postForBytes(String url, String requestStr, boolean useKey) throws WxPayException { try { @@ -90,14 +89,71 @@ public String post(String url, String requestStr, boolean useKey) throws WxPayEx } } - private StringEntity createEntry(String requestStr) { - try { - return new StringEntity(new String(requestStr.getBytes(StandardCharsets.UTF_8), StandardCharsets.ISO_8859_1)); - } catch (UnsupportedEncodingException e) { - //cannot happen - this.log.error(e.getMessage(), e); - return null; + @Override + public String postV3(String url, String requestStr) throws WxPayException { + CloseableHttpClient httpClient = this.createApiV3HttpClient(); + HttpPost httpPost = this.createHttpPost(url, requestStr); + httpPost.addHeader("Accept", "application/json"); + httpPost.addHeader("Content-Type", "application/json"); + try (CloseableHttpResponse response = httpClient.execute(httpPost)) { + //v3已经改为通过状态码判断200 204 成功 + int statusCode = response.getStatusLine().getStatusCode(); + String responseString = EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8); + if (HttpStatus.SC_OK == statusCode || HttpStatus.SC_NO_CONTENT == statusCode) { + this.log.info("\n【请求地址】:{}\n【请求数据】:{}\n【响应数据】:{}", url, requestStr, responseString); + return responseString; + } else { + //有错误提示信息返回 + JsonObject jsonObject = JSON_PARSER.parse(responseString).getAsJsonObject(); + throw new WxPayException(jsonObject.get("message").getAsString()); + } + } catch (Exception e) { + this.log.error("\n【请求地址】:{}\n【请求数据】:{}\n【异常信息】:{}", url, requestStr, e.getMessage()); + throw new WxPayException(e.getMessage(), e); + } finally { + httpPost.releaseConnection(); } + + + } + + @Override + public String getV3(URI url) throws WxPayException { + CloseableHttpClient httpClient = this.createApiV3HttpClient(); + HttpGet httpGet = new HttpGet(url); + httpGet.addHeader("Accept", "application/json"); + httpGet.addHeader("Content-Type", "application/json"); + try (CloseableHttpResponse response = httpClient.execute(httpGet)) { + //v3已经改为通过状态码判断200 204 成功 + int statusCode = response.getStatusLine().getStatusCode(); + String responseString = EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8); + if (HttpStatus.SC_OK == statusCode || HttpStatus.SC_NO_CONTENT == statusCode) { + this.log.info("\n【请求地址】:{}\n【响应数据】:{}", url, responseString); + return responseString; + } else { + //有错误提示信息返回 + JsonObject jsonObject = JSON_PARSER.parse(responseString).getAsJsonObject(); + throw new WxPayException(jsonObject.get("message").getAsString()); + } + } catch (Exception e) { + this.log.error("\n【请求地址】:{}\n【异常信息】:{}", url, e.getMessage()); + throw new WxPayException(e.getMessage(), e); + } finally { + httpGet.releaseConnection(); + } + } + + private CloseableHttpClient createApiV3HttpClient() throws WxPayException { + CloseableHttpClient apiV3HttpClient = this.getConfig().getApiV3HttpClient(); + if (null == apiV3HttpClient) { + return this.getConfig().initApiV3HttpClient(); + } + return apiV3HttpClient; + } + + private StringEntity createEntry(String requestStr) { + return new StringEntity(requestStr, ContentType.create("application/json", "utf-8")); + //return new StringEntity(new String(requestStr.getBytes(StandardCharsets.UTF_8), StandardCharsets.ISO_8859_1)); } private HttpClientBuilder createHttpClientBuilder(boolean useKey) throws WxPayException { diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/WxPayServiceJoddHttpImpl.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/WxPayServiceJoddHttpImpl.java index 81d35614d5..b37160f90f 100644 --- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/WxPayServiceJoddHttpImpl.java +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/WxPayServiceJoddHttpImpl.java @@ -1,12 +1,9 @@ package com.github.binarywang.wxpay.service.impl; +import java.net.URI; import java.nio.charset.StandardCharsets; import javax.net.ssl.SSLContext; -import com.github.binarywang.wxpay.bean.request.WxPayQueryCommentRequest; -import com.github.binarywang.wxpay.bean.request.WxPayRedpackQueryRequest; -import com.github.binarywang.wxpay.bean.result.WxPayCommonResult; -import com.github.binarywang.wxpay.bean.result.WxPayRedpackQueryResult; import org.apache.commons.lang3.StringUtils; import com.github.binarywang.wxpay.bean.WxPayApiData; @@ -19,10 +16,6 @@ import jodd.http.net.SSLSocketHttpConnectionProvider; import jodd.http.net.SocketHttpConnectionProvider; import jodd.util.Base64; -import org.apache.commons.lang3.StringUtils; - -import javax.net.ssl.SSLContext; -import java.nio.charset.StandardCharsets; /** * 微信支付请求实现类,jodd-http实现. @@ -67,6 +60,16 @@ public String post(String url, String requestStr, boolean useKey) throws WxPayEx } } + @Override + public String postV3(String url, String requestStr) throws WxPayException { + return null; + } + + @Override + public String getV3(URI url) throws WxPayException { + return null; + } + private HttpRequest buildHttpRequest(String url, String requestStr, boolean useKey) throws WxPayException { HttpRequest request = HttpRequest .post(url) diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/v3/Credentials.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/v3/Credentials.java new file mode 100644 index 0000000000..d5102d1fa9 --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/v3/Credentials.java @@ -0,0 +1,11 @@ +package com.github.binarywang.wxpay.v3; + +import java.io.IOException; +import org.apache.http.client.methods.HttpUriRequest; + +public interface Credentials { + + String getSchema(); + + String getToken(HttpUriRequest request) throws IOException; +} diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/v3/SignatureExec.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/v3/SignatureExec.java new file mode 100644 index 0000000000..a28dfdcd63 --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/v3/SignatureExec.java @@ -0,0 +1,88 @@ +package com.github.binarywang.wxpay.v3; + +import java.io.IOException; +import org.apache.http.HttpEntity; +import org.apache.http.HttpException; +import org.apache.http.StatusLine; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpEntityEnclosingRequestBase; +import org.apache.http.client.methods.HttpExecutionAware; +import org.apache.http.client.methods.HttpRequestWrapper; +import org.apache.http.client.methods.HttpUriRequest; +import org.apache.http.client.methods.RequestBuilder; +import org.apache.http.client.protocol.HttpClientContext; +import org.apache.http.conn.routing.HttpRoute; +import org.apache.http.entity.ByteArrayEntity; +import org.apache.http.impl.execchain.ClientExecChain; +import org.apache.http.util.EntityUtils; + +public class SignatureExec implements ClientExecChain { + final ClientExecChain mainExec; + final Credentials credentials; + final Validator validator; + + SignatureExec(Credentials credentials, Validator validator, ClientExecChain mainExec) { + this.credentials = credentials; + this.validator = validator; + this.mainExec = mainExec; + } + + protected HttpEntity newRepeatableEntity(HttpEntity entity) throws IOException { + byte[] content = EntityUtils.toByteArray(entity); + ByteArrayEntity newEntity = new ByteArrayEntity(content); + newEntity.setContentEncoding(entity.getContentEncoding()); + newEntity.setContentType(entity.getContentType()); + + return newEntity; + } + + protected void convertToRepeatableResponseEntity(CloseableHttpResponse response) throws IOException { + HttpEntity entity = response.getEntity(); + if (entity != null && !entity.isRepeatable()) { + response.setEntity(newRepeatableEntity(entity)); + } + } + + protected void convertToRepeatableRequestEntity(HttpUriRequest request) throws IOException { + if (request instanceof HttpEntityEnclosingRequestBase) { + HttpEntity entity = ((HttpEntityEnclosingRequestBase) request).getEntity(); + if (entity != null && !entity.isRepeatable()) { + ((HttpEntityEnclosingRequestBase) request).setEntity(newRepeatableEntity(entity)); + } + } + } + + @Override + public CloseableHttpResponse execute(HttpRoute route, HttpRequestWrapper request, + HttpClientContext context, HttpExecutionAware execAware) throws IOException, HttpException { + if (request.getURI().getHost().endsWith(".mch.weixin.qq.com")) { + return executeWithSignature(route, request, context, execAware); + } else { + return mainExec.execute(route, request, context, execAware); + } + } + + private CloseableHttpResponse executeWithSignature(HttpRoute route, HttpRequestWrapper request, + HttpClientContext context, HttpExecutionAware execAware) throws IOException, HttpException { + HttpUriRequest newRequest = RequestBuilder.copy(request.getOriginal()).build(); + convertToRepeatableRequestEntity(newRequest); + // 添加认证信息 + newRequest.addHeader("Authorization", + credentials.getSchema() + " " + credentials.getToken(newRequest)); + + // 执行 + CloseableHttpResponse response = mainExec.execute( + route, HttpRequestWrapper.wrap(newRequest), context, execAware); + + // 对成功应答验签 + StatusLine statusLine = response.getStatusLine(); + if (statusLine.getStatusCode() >= 200 && statusLine.getStatusCode() < 300) { + convertToRepeatableResponseEntity(response); + if (!validator.validate(response)) { + throw new HttpException("应答的微信支付签名验证失败"); + } + } + return response; + } + +} diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/v3/Validator.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/v3/Validator.java new file mode 100644 index 0000000000..cdeb8ac279 --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/v3/Validator.java @@ -0,0 +1,8 @@ +package com.github.binarywang.wxpay.v3; + +import java.io.IOException; +import org.apache.http.client.methods.CloseableHttpResponse; + +public interface Validator { + boolean validate(CloseableHttpResponse response) throws IOException; +} diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/v3/WxPayV3HttpClientBuilder.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/v3/WxPayV3HttpClientBuilder.java new file mode 100644 index 0000000000..986a8f4cb1 --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/v3/WxPayV3HttpClientBuilder.java @@ -0,0 +1,75 @@ +package com.github.binarywang.wxpay.v3; + + +import java.security.PrivateKey; +import java.security.cert.X509Certificate; +import java.util.List; + +import com.github.binarywang.wxpay.v3.auth.CertificatesVerifier; +import com.github.binarywang.wxpay.v3.auth.PrivateKeySigner; +import com.github.binarywang.wxpay.v3.auth.WxPayCredentials; +import com.github.binarywang.wxpay.v3.auth.WxPayValidator; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClientBuilder; +import org.apache.http.impl.execchain.ClientExecChain; + +public class WxPayV3HttpClientBuilder extends HttpClientBuilder { + private Credentials credentials; + private Validator validator; + + static final String OS = System.getProperty("os.name") + "/" + System.getProperty("os.version"); + static final String VERSION = System.getProperty("java.version"); + + private WxPayV3HttpClientBuilder() { + super(); + + String userAgent = String.format( + "WechatPay-Apache-HttpClient/%s (%s) Java/%s", + getClass().getPackage().getImplementationVersion(), + OS, + VERSION == null ? "Unknown" : VERSION); + setUserAgent(userAgent); + } + + public static WxPayV3HttpClientBuilder create() { + return new WxPayV3HttpClientBuilder(); + } + + public WxPayV3HttpClientBuilder withMerchant(String merchantId, String serialNo, PrivateKey privateKey) { + this.credentials = + new WxPayCredentials(merchantId, new PrivateKeySigner(serialNo, privateKey)); + return this; + } + + public WxPayV3HttpClientBuilder withCredentials(Credentials credentials) { + this.credentials = credentials; + return this; + } + + public WxPayV3HttpClientBuilder withWechatpay(Listcertificates) { + this.validator = new WxPayValidator(new CertificatesVerifier(certificates)); + return this; + } + + public WxPayV3HttpClientBuilder withValidator(Validator validator) { + this.validator = validator; + return this; + } + + @Override + public CloseableHttpClient build() { + if (credentials == null) { + throw new IllegalArgumentException("缺少身份认证信息"); + } + if (validator == null) { + throw new IllegalArgumentException("缺少签名验证信息"); + } + + return super.build(); + } + + @Override + protected ClientExecChain decorateProtocolExec(final ClientExecChain requestExecutor) { + return new SignatureExec(this.credentials, this.validator, requestExecutor); + } +} diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/v3/auth/AutoUpdateCertificatesVerifier.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/v3/auth/AutoUpdateCertificatesVerifier.java new file mode 100644 index 0000000000..59ad93cb77 --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/v3/auth/AutoUpdateCertificatesVerifier.java @@ -0,0 +1,179 @@ +package com.github.binarywang.wxpay.v3.auth; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.github.binarywang.wxpay.v3.Credentials; +import com.github.binarywang.wxpay.v3.Validator; +import com.github.binarywang.wxpay.v3.WxPayV3HttpClientBuilder; +import com.github.binarywang.wxpay.v3.util.AesUtils; +import com.github.binarywang.wxpay.v3.util.PemUtils; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.util.EntityUtils; +import org.joda.time.Instant; +import org.joda.time.Minutes; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.security.GeneralSecurityException; +import java.security.cert.CertificateExpiredException; +import java.security.cert.CertificateNotYetValidException; +import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.locks.ReentrantLock; + +/** + * 在原有CertificatesVerifier基础上,增加自动更新证书功能 + * + * @author doger.wang + */ +@Slf4j +public class AutoUpdateCertificatesVerifier implements Verifier { + /** + * 证书下载地址 + */ + private static final String CERT_DOWNLOAD_PATH = "https://api.mch.weixin.qq.com/v3/certificates"; + + /** + * 上次更新时间 + */ + private volatile Instant instant; + + /** + * 证书更新间隔时间,单位为分钟 + */ + private final int minutesInterval; + + private CertificatesVerifier verifier; + + private final Credentials credentials; + + private final byte[] apiV3Key; + + private final ReentrantLock lock = new ReentrantLock(); + + /** + * 时间间隔枚举,支持一小时、六小时以及十二小时 + */ + @Getter + @RequiredArgsConstructor + public enum TimeInterval { + /** + * 一小时 + */ + OneHour(60), + /** + * 六小时 + */ + SixHours(60 * 6), + /** + * 十二小时 + */ + TwelveHours(60 * 12); + + private final int minutes; + } + + public AutoUpdateCertificatesVerifier(Credentials credentials, byte[] apiV3Key) { + this(credentials, apiV3Key, TimeInterval.OneHour.getMinutes()); + } + + public AutoUpdateCertificatesVerifier(Credentials credentials, byte[] apiV3Key, int minutesInterval) { + this.credentials = credentials; + this.apiV3Key = apiV3Key; + this.minutesInterval = minutesInterval; + //构造时更新证书 + try { + autoUpdateCert(); + instant = Instant.now(); + } catch (IOException | GeneralSecurityException e) { + throw new RuntimeException(e); + } + } + + @Override + public boolean verify(String serialNumber, byte[] message, String signature) { + if (instant == null || Minutes.minutesBetween(instant, Instant.now()).getMinutes() >= minutesInterval) { + if (lock.tryLock()) { + try { + autoUpdateCert(); + //更新时间 + instant = Instant.now(); + } catch (GeneralSecurityException | IOException e) { + log.warn("Auto update cert failed, exception = " + e); + } finally { + lock.unlock(); + } + } + } + return verifier.verify(serialNumber, message, signature); + } + + private void autoUpdateCert() throws IOException, GeneralSecurityException { + CloseableHttpClient httpClient = WxPayV3HttpClientBuilder.create() + .withCredentials(credentials) + .withValidator(verifier == null ? new Validator() { + @Override + public boolean validate(CloseableHttpResponse response) throws IOException { + return true; + } + } : new WxPayValidator(verifier)) + .build(); + + HttpGet httpGet = new HttpGet(CERT_DOWNLOAD_PATH); + httpGet.addHeader("Accept", "application/json"); + + CloseableHttpResponse response = httpClient.execute(httpGet); + int statusCode = response.getStatusLine().getStatusCode(); + String body = EntityUtils.toString(response.getEntity()); + if (statusCode == 200) { + List newCertList = deserializeToCerts(apiV3Key, body); + if (newCertList.isEmpty()) { + log.warn("Cert list is empty"); + return; + } + this.verifier = new CertificatesVerifier(newCertList); + } else { + log.warn("Auto update cert failed, statusCode = " + statusCode + ",body = " + body); + } + } + + /** + * 反序列化证书并解密 + */ + private List deserializeToCerts(byte[] apiV3Key, String body) throws GeneralSecurityException, IOException { + AesUtils decryptor = new AesUtils(apiV3Key); + ObjectMapper mapper = new ObjectMapper(); + JsonNode dataNode = mapper.readTree(body).get("data"); + List newCertList = new ArrayList<>(); + if (dataNode != null) { + for (int i = 0, count = dataNode.size(); i < count; i++) { + JsonNode encryptCertificateNode = dataNode.get(i).get("encrypt_certificate"); + //解密 + String cert = decryptor.decryptToString( + encryptCertificateNode.get("associated_data").toString().replaceAll("\"", "") + .getBytes(StandardCharsets.UTF_8), + encryptCertificateNode.get("nonce").toString().replaceAll("\"", "") + .getBytes(StandardCharsets.UTF_8), + encryptCertificateNode.get("ciphertext").toString().replaceAll("\"", "")); + + X509Certificate x509Cert = PemUtils + .loadCertificate(new ByteArrayInputStream(cert.getBytes(StandardCharsets.UTF_8))); + try { + x509Cert.checkValidity(); + } catch (CertificateExpiredException | CertificateNotYetValidException e) { + continue; + } + newCertList.add(x509Cert); + } + } + + return newCertList; + } +} diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/v3/auth/CertificatesVerifier.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/v3/auth/CertificatesVerifier.java new file mode 100644 index 0000000000..9853cf2eef --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/v3/auth/CertificatesVerifier.java @@ -0,0 +1,43 @@ +package com.github.binarywang.wxpay.v3.auth; + +import java.math.BigInteger; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.security.Signature; +import java.security.SignatureException; +import java.security.cert.X509Certificate; +import java.util.Base64; +import java.util.HashMap; +import java.util.List; + +public class CertificatesVerifier implements Verifier { + private final HashMap certificates = new HashMap<>(); + + public CertificatesVerifier(List list) { + + for (X509Certificate item : list) { + certificates.put(item.getSerialNumber(), item); + } + } + + private boolean verify(X509Certificate certificate, byte[] message, String signature) { + try { + Signature sign = Signature.getInstance("SHA256withRSA"); + sign.initVerify(certificate); + sign.update(message); + return sign.verify(Base64.getDecoder().decode(signature)); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException("当前Java环境不支持SHA256withRSA", e); + } catch (SignatureException e) { + throw new RuntimeException("签名验证过程发生了错误", e); + } catch (InvalidKeyException e) { + throw new RuntimeException("无效的证书", e); + } + } + + @Override + public boolean verify(String serialNumber, byte[] message, String signature) { + BigInteger val = new BigInteger(serialNumber, 16); + return certificates.containsKey(val) && verify(certificates.get(val), message, signature); + } +} diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/v3/auth/PrivateKeySigner.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/v3/auth/PrivateKeySigner.java new file mode 100644 index 0000000000..37ec51cf58 --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/v3/auth/PrivateKeySigner.java @@ -0,0 +1,37 @@ +package com.github.binarywang.wxpay.v3.auth; + +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.Signature; +import java.security.SignatureException; +import java.util.Base64; + +public class PrivateKeySigner implements Signer { + private String certificateSerialNumber; + + private PrivateKey privateKey; + + public PrivateKeySigner(String serialNumber, PrivateKey privateKey) { + this.certificateSerialNumber = serialNumber; + this.privateKey = privateKey; + } + + @Override + public SignatureResult sign(byte[] message) { + try { + Signature sign = Signature.getInstance("SHA256withRSA"); + sign.initSign(privateKey); + sign.update(message); + + return new SignatureResult( + Base64.getEncoder().encodeToString(sign.sign()), certificateSerialNumber); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException("当前Java环境不支持SHA256withRSA", e); + } catch (SignatureException e) { + throw new RuntimeException("签名计算失败", e); + } catch (InvalidKeyException e) { + throw new RuntimeException("无效的私钥", e); + } + } +} diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/v3/auth/Signer.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/v3/auth/Signer.java new file mode 100644 index 0000000000..7255a1b433 --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/v3/auth/Signer.java @@ -0,0 +1,15 @@ +package com.github.binarywang.wxpay.v3.auth; + +public interface Signer { + SignatureResult sign(byte[] message); + + class SignatureResult { + String sign; + String certificateSerialNumber; + + public SignatureResult(String sign, String serialNumber) { + this.sign = sign; + this.certificateSerialNumber = serialNumber; + } + } +} diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/v3/auth/Verifier.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/v3/auth/Verifier.java new file mode 100644 index 0000000000..ed591a4ef2 --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/v3/auth/Verifier.java @@ -0,0 +1,5 @@ +package com.github.binarywang.wxpay.v3.auth; + +public interface Verifier { + boolean verify(String serialNumber, byte[] message, String signature); +} diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/v3/auth/WxPayCredentials.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/v3/auth/WxPayCredentials.java new file mode 100644 index 0000000000..2654e8675c --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/v3/auth/WxPayCredentials.java @@ -0,0 +1,91 @@ +package com.github.binarywang.wxpay.v3.auth; + + +import java.io.IOException; +import java.net.URI; +import java.security.SecureRandom; + +import com.github.binarywang.wxpay.v3.Credentials; +import lombok.extern.slf4j.Slf4j; +import org.apache.http.client.methods.HttpEntityEnclosingRequestBase; +import org.apache.http.client.methods.HttpUriRequest; +import org.apache.http.util.EntityUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@Slf4j +public class WxPayCredentials implements Credentials { + private static final String SYMBOLS = + "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; + private static final SecureRandom RANDOM = new SecureRandom(); + protected String merchantId; + protected Signer signer; + + public WxPayCredentials(String merchantId, Signer signer) { + this.merchantId = merchantId; + this.signer = signer; + } + + public String getMerchantId() { + return merchantId; + } + + protected long generateTimestamp() { + return System.currentTimeMillis() / 1000; + } + + protected String generateNonceStr() { + char[] nonceChars = new char[32]; + for (int index = 0; index < nonceChars.length; ++index) { + nonceChars[index] = SYMBOLS.charAt(RANDOM.nextInt(SYMBOLS.length())); + } + return new String(nonceChars); + } + + @Override + public final String getSchema() { + return "WECHATPAY2-SHA256-RSA2048"; + } + + @Override + public final String getToken(HttpUriRequest request) throws IOException { + String nonceStr = generateNonceStr(); + long timestamp = generateTimestamp(); + + String message = buildMessage(nonceStr, timestamp, request); + log.debug("authorization message=[{}]", message); + + Signer.SignatureResult signature = signer.sign(message.getBytes("utf-8")); + + String token = "mchid=\"" + getMerchantId() + "\"," + + "nonce_str=\"" + nonceStr + "\"," + + "timestamp=\"" + timestamp + "\"," + + "serial_no=\"" + signature.certificateSerialNumber + "\"," + + "signature=\"" + signature.sign + "\""; + log.debug("authorization token=[{}]", token); + + return token; + } + + protected final String buildMessage(String nonce, long timestamp, HttpUriRequest request) + throws IOException { + URI uri = request.getURI(); + String canonicalUrl = uri.getRawPath(); + if (uri.getQuery() != null) { + canonicalUrl += "?" + uri.getRawQuery(); + } + + String body = ""; + // PATCH,POST,PUT + if (request instanceof HttpEntityEnclosingRequestBase) { + body = EntityUtils.toString(((HttpEntityEnclosingRequestBase) request).getEntity()); + } + + return request.getRequestLine().getMethod() + "\n" + + canonicalUrl + "\n" + + timestamp + "\n" + + nonce + "\n" + + body + "\n"; + } + +} diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/v3/auth/WxPayValidator.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/v3/auth/WxPayValidator.java new file mode 100644 index 0000000000..c83f8853e3 --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/v3/auth/WxPayValidator.java @@ -0,0 +1,53 @@ +package com.github.binarywang.wxpay.v3.auth; + + +import java.io.IOException; + +import com.github.binarywang.wxpay.v3.Validator; +import lombok.extern.slf4j.Slf4j; +import org.apache.http.Header; +import org.apache.http.HttpEntity; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.util.EntityUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +@Slf4j +public class WxPayValidator implements Validator { + private Verifier verifier; + + public WxPayValidator(Verifier verifier) { + this.verifier = verifier; + } + + @Override + public final boolean validate(CloseableHttpResponse response) throws IOException { + Header serialNo = response.getFirstHeader("Wechatpay-Serial"); + Header sign = response.getFirstHeader("Wechatpay-Signature"); + Header timestamp = response.getFirstHeader("Wechatpay-TimeStamp"); + Header nonce = response.getFirstHeader("Wechatpay-Nonce"); + + // todo: check timestamp + if (timestamp == null || nonce == null || serialNo == null || sign == null) { + return false; + } + + String message = buildMessage(response); + return verifier.verify(serialNo.getValue(), message.getBytes("utf-8"), sign.getValue()); + } + + protected final String buildMessage(CloseableHttpResponse response) throws IOException { + String timestamp = response.getFirstHeader("Wechatpay-TimeStamp").getValue(); + String nonce = response.getFirstHeader("Wechatpay-Nonce").getValue(); + + String body = getResponseBody(response); + return timestamp + "\n" + + nonce + "\n" + + body + "\n"; + } + + protected final String getResponseBody(CloseableHttpResponse response) throws IOException { + HttpEntity entity = response.getEntity(); + + return (entity != null && entity.isRepeatable()) ? EntityUtils.toString(entity) : ""; + } +} diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/v3/util/AesUtils.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/v3/util/AesUtils.java new file mode 100644 index 0000000000..bd68cac1a4 --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/v3/util/AesUtils.java @@ -0,0 +1,107 @@ +package com.github.binarywang.wxpay.v3.util; + +import org.apache.commons.lang3.StringUtils; + +import java.io.IOException; +import java.security.GeneralSecurityException; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.util.Base64; +import java.util.Map; +import java.util.SortedMap; +import java.util.TreeMap; +import javax.crypto.Cipher; +import javax.crypto.Mac; +import javax.crypto.NoSuchPaddingException; +import javax.crypto.spec.GCMParameterSpec; +import javax.crypto.spec.SecretKeySpec; + +public class AesUtils { + + static final int KEY_LENGTH_BYTE = 32; + static final int TAG_LENGTH_BIT = 128; + private final byte[] aesKey; + + public AesUtils(byte[] key) { + if (key.length != KEY_LENGTH_BYTE) { + throw new IllegalArgumentException("无效的ApiV3Key,长度必须为32个字节"); + } + this.aesKey = key; + } + + public String decryptToString(byte[] associatedData, byte[] nonce, String ciphertext) + throws GeneralSecurityException, IOException { + try { + Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding"); + + SecretKeySpec key = new SecretKeySpec(aesKey, "AES"); + GCMParameterSpec spec = new GCMParameterSpec(TAG_LENGTH_BIT, nonce); + + cipher.init(Cipher.DECRYPT_MODE, key, spec); + cipher.updateAAD(associatedData); + + return new String(cipher.doFinal(Base64.getDecoder().decode(ciphertext)), "utf-8"); + } catch (NoSuchAlgorithmException | NoSuchPaddingException e) { + throw new IllegalStateException(e); + } catch (InvalidKeyException | InvalidAlgorithmParameterException e) { + throw new IllegalArgumentException(e); + } + } + + public static String decryptToString(String associatedData, String nonce, String ciphertext,String apiV3Key) + throws GeneralSecurityException, IOException { + try { + Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding"); + + SecretKeySpec key = new SecretKeySpec(apiV3Key.getBytes(), "AES"); + GCMParameterSpec spec = new GCMParameterSpec(TAG_LENGTH_BIT, nonce.getBytes()); + + cipher.init(Cipher.DECRYPT_MODE, key, spec); + cipher.updateAAD(associatedData.getBytes()); + + return new String(cipher.doFinal(Base64.getDecoder().decode(ciphertext)), "utf-8"); + } catch (NoSuchAlgorithmException | NoSuchPaddingException e) { + throw new IllegalStateException(e); + } catch (InvalidKeyException | InvalidAlgorithmParameterException e) { + throw new IllegalArgumentException(e); + } + } + + + public static String createSign(Map map, String mchKey) { + Map params = map; + SortedMap sortedMap = new TreeMap<>(params); + + StringBuilder toSign = new StringBuilder(); + for (String key : sortedMap.keySet()) { + String value = params.get(key); + if ("sign".equals(key) || StringUtils.isEmpty(value)) { + continue; + } + toSign.append(key).append("=").append(value).append("&"); + } + toSign.append("key=" + mchKey); + return HMACSHA256(toSign.toString(), mchKey); + + } + + public static String HMACSHA256(String data, String key) { + try { + Mac sha256_HMAC = Mac.getInstance("HmacSHA256"); + SecretKeySpec secret_key = new SecretKeySpec(key.getBytes("UTF-8"), "HmacSHA256"); + sha256_HMAC.init(secret_key); + byte[] array = sha256_HMAC.doFinal(data.getBytes("UTF-8")); + StringBuilder sb = new StringBuilder(); + for (byte item : array) { + sb.append(Integer.toHexString((item & 0xFF) | 0x100).substring(1, 3)); + } + return sb.toString().toUpperCase(); + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } + + +} diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/v3/util/PemUtils.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/v3/util/PemUtils.java new file mode 100644 index 0000000000..bf4d2657b5 --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/v3/util/PemUtils.java @@ -0,0 +1,60 @@ +package com.github.binarywang.wxpay.v3.util; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.security.KeyFactory; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.cert.CertificateException; +import java.security.cert.CertificateExpiredException; +import java.security.cert.CertificateFactory; +import java.security.cert.CertificateNotYetValidException; +import java.security.cert.X509Certificate; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.PKCS8EncodedKeySpec; +import java.util.Base64; + +public class PemUtils { + + public static PrivateKey loadPrivateKey(InputStream inputStream) { + try { + ByteArrayOutputStream array = new ByteArrayOutputStream(); + byte[] buffer = new byte[1024]; + int length; + while ((length = inputStream.read(buffer)) != -1) { + array.write(buffer, 0, length); + } + + String privateKey = array.toString("utf-8") + .replace("-----BEGIN PRIVATE KEY-----", "") + .replace("-----END PRIVATE KEY-----", "") + .replaceAll("\\s+", ""); + + KeyFactory kf = KeyFactory.getInstance("RSA"); + return kf.generatePrivate( + new PKCS8EncodedKeySpec(Base64.getDecoder().decode(privateKey))); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException("当前Java环境不支持RSA", e); + } catch (InvalidKeySpecException e) { + throw new RuntimeException("无效的密钥格式"); + } catch (IOException e) { + throw new RuntimeException("无效的密钥"); + } + } + + public static X509Certificate loadCertificate(InputStream inputStream) { + try { + CertificateFactory cf = CertificateFactory.getInstance("X509"); + X509Certificate cert = (X509Certificate) cf.generateCertificate(inputStream); + cert.checkValidity(); + return cert; + } catch (CertificateExpiredException e) { + throw new RuntimeException("证书已过期", e); + } catch (CertificateNotYetValidException e) { + throw new RuntimeException("证书尚未生效", e); + } catch (CertificateException e) { + throw new RuntimeException("无效的证书", e); + } + } +} diff --git a/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/bean/notify/WxPayNotifyResponseTest.java b/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/bean/notify/WxPayNotifyResponseTest.java index 1baaa8eadd..60be34e357 100644 --- a/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/bean/notify/WxPayNotifyResponseTest.java +++ b/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/bean/notify/WxPayNotifyResponseTest.java @@ -20,4 +20,22 @@ public void testSuccess() { " " + ""); } + + @Test + public void testSuccessResp() { + final String result = WxPayNotifyResponse.successResp("OK"); + assertThat(result).isEqualTo(" " + + " "); + } + + @Test + public void testFailResp() { + final String result = WxPayNotifyResponse.failResp("500"); + assertThat(result).isEqualTo("" + + " " + + " " + + " "); + } } diff --git a/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/bean/profitsharing/ProfitSharingQueryResultTest.java b/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/bean/profitsharing/ProfitSharingQueryResultTest.java new file mode 100644 index 0000000000..386101fed0 --- /dev/null +++ b/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/bean/profitsharing/ProfitSharingQueryResultTest.java @@ -0,0 +1,59 @@ +package com.github.binarywang.wxpay.bean.profitsharing; + +import org.testng.annotations.Test; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * 测试. + * + * @author Binary Wang + * @date 2020-03-22 + */ +@Test +public class ProfitSharingQueryResultTest { + + @Test + public void testFormatReceivers() { + ProfitSharingQueryResult result = new ProfitSharingQueryResult(); + result.setReceiversJson("[\n" + + "{\n" + + "\"type\": \"MERCHANT_ID\",\n" + + "\"account\":\"190001001\",\n" + + "\"amount\":100,\n" + + "\"description\": \"分到商户\",\n" + + "\"result\": \"SUCCESS\",\n" + + "\"finish_time\": \"20180608170132\"\n" + + "},\n" + + "{\n" + + "\"type\": \"PERSONAL_WECHATID\",\n" + + "\"account\":\"86693952\",\n" + + "\"amount\":888,\n" + + "\"description\": \"分到个人\",\n" + + "\"result\": \"SUCCESS\",\n" + + "\"finish_time\": \"20180608170132\"\n" + + "}\n" + + "]"); + + List" + + " " + + " receivers = result.formatReceivers(); + assertThat(receivers).isNotEmpty(); + + assertThat(receivers.get(0)).isNotNull(); + assertThat(receivers.get(0).getType()).isEqualTo("MERCHANT_ID"); + assertThat(receivers.get(0).getAccount()).isEqualTo("190001001"); + assertThat(receivers.get(0).getAmount()).isEqualTo(100); + assertThat(receivers.get(0).getDescription()).isEqualTo("分到商户"); + assertThat(receivers.get(0).getResult()).isEqualTo("SUCCESS"); + assertThat(receivers.get(0).getFinishTime()).isEqualTo("20180608170132"); + + assertThat(receivers.get(1)).isNotNull(); + assertThat(receivers.get(1).getType()).isEqualTo("PERSONAL_WECHATID"); + assertThat(receivers.get(1).getAccount()).isEqualTo("86693952"); + assertThat(receivers.get(1).getAmount()).isEqualTo(888); + assertThat(receivers.get(1).getDescription()).isEqualTo("分到个人"); + assertThat(receivers.get(1).getResult()).isEqualTo("SUCCESS"); + assertThat(receivers.get(1).getFinishTime()).isEqualTo("20180608170132"); + } +} diff --git a/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/config/WxPayConfigTest.java b/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/config/WxPayConfigTest.java index 9d3c13da42..fb46c58a4d 100644 --- a/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/config/WxPayConfigTest.java +++ b/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/config/WxPayConfigTest.java @@ -2,8 +2,6 @@ import org.testng.annotations.Test; -import static org.testng.Assert.*; - /** * * Created by BinaryWang on 2017/6/18. @@ -12,12 +10,12 @@ * @author Binary Wang */ public class WxPayConfigTest { - private WxPayConfig payConfig = new WxPayConfig(); + private final WxPayConfig payConfig = new WxPayConfig(); @Test public void testInitSSLContext_classpath() throws Exception { payConfig.setMchId("123"); - payConfig.setKeyPath("classpath:/abc.p12"); + payConfig.setKeyPath("classpath:/dlt.p12"); payConfig.initSSLContext(); } @@ -28,4 +26,9 @@ public void testInitSSLContext_http() throws Exception { payConfig.initSSLContext(); } + @Test + public void testInitSSLContext() throws Exception { + this.testInitSSLContext_classpath(); + this.testInitSSLContext_http(); + } } diff --git a/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/service/impl/BaseWxPayServiceImplTest.java b/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/service/impl/BaseWxPayServiceImplTest.java index ab850d0b5b..9e129bcbeb 100644 --- a/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/service/impl/BaseWxPayServiceImplTest.java +++ b/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/service/impl/BaseWxPayServiceImplTest.java @@ -20,8 +20,6 @@ import com.github.binarywang.wxpay.util.XmlConfig; import com.google.inject.Inject; import lombok.extern.slf4j.Slf4j; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.testng.annotations.DataProvider; import org.testng.annotations.Guice; import org.testng.annotations.Test; @@ -63,12 +61,13 @@ public void testUnifiedOrder() throws WxPayException { .notifyUrl("111111") .tradeType(TradeType.JSAPI) .openid(((XmlWxPayConfig) this.payService.getConfig()).getOpenid()) - .outTradeNo("1111112") + .outTradeNo("111111826") + .attach("#*#{\"pn\":\"粤B87965\",\"aid\":\"wx123\"}#*#") .build(); request.setSignType(SignType.HMAC_SHA256); WxPayUnifiedOrderResult result = this.payService.unifiedOrder(request); log.info(result.toString()); - log.warn(this.payService.getWxApiData().toString()); +// log.warn(this.payService.getWxApiData().toString()); } /** @@ -143,6 +142,24 @@ public void testCreateOrder_native() throws Exception { log.warn(this.payService.getWxApiData().toString()); } + @Test + public void testCreateOrderSpecific() throws Exception { + // Won't compile + // WxPayMpOrderResult result = payService.createOrder(TradeType.Specific.APP, new WxPayUnifiedOrderRequest()); + payService.createOrder( + TradeType.Specific.JSAPI, + WxPayUnifiedOrderRequest.newBuilder() + .body("我去") + .totalFee(1) + .productId("aaa") + .spbillCreateIp("11.1.11.1") + .notifyUrl("111111") + .outTradeNo("111111290") + .build() + ) + .getAppId(); + } + /** * Test get pay info. * @@ -600,10 +617,73 @@ public void testTestDownloadRawBill() { } @Test - public void testGetWxPayFaceAuthInfo() { + public void testGetWxPayFaceAuthInfo() throws WxPayException { + XmlConfig.fastMode = true; + final WxPayFaceAuthInfoRequest request = new WxPayFaceAuthInfoRequest() + .setStoreId("1").setRawdata("111").setNow("111").setVersion("111").setStoreName("2222").setDeviceId("111"); + request.setSignType("MD5"); + this.payService.getWxPayFaceAuthInfo(request); + } + + @Test + public void testFacepay() throws WxPayException { + final WxPayFacepayResult result = this.payService.facepay(WxPayFacepayRequest.newBuilder().build()); + } + + @Test + public void testGetEntPayService() { + // no need to test + } + + @Test + public void testGetProfitSharingService() { + // no need to test + } + + @Test + public void testGetRedpackService() { + // no need to test + } + + @Test + public void testSetEntPayService() { + // no need to test + } + + @Test + public void testGetPayBaseUrl() { + // no need to test + } + + @Test + public void testParseScanPayNotifyResult() { } @Test - public void testFacepay() { + public void testSendMiniProgramRedpack() { + } + + @Test + public void testSendRedpack() { + } + + @Test + public void testQueryRedpack() { + } + + @Test + public void testTestQueryRedpack() { + } + + @Test + public void testGetPayScoreService() { + // no need to test + } + + @Test + public void testQueryExchangeRate() throws WxPayException { + final WxPayQueryExchangeRateResult result = this.payService.queryExchangeRate("USD", "20200425"); + assertThat(result).isNotNull(); + System.out.println(result); } } diff --git a/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/service/impl/PayScoreServiceImplTest.java b/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/service/impl/PayScoreServiceImplTest.java new file mode 100644 index 0000000000..0aa6cafb2d --- /dev/null +++ b/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/service/impl/PayScoreServiceImplTest.java @@ -0,0 +1,116 @@ +package com.github.binarywang.wxpay.service.impl; + +import com.github.binarywang.wxpay.bean.payscore.WxPayScoreRequest; +import com.github.binarywang.wxpay.exception.WxPayException; +import com.github.binarywang.wxpay.service.WxPayService; +import com.github.binarywang.wxpay.testbase.ApiTestModule; +import com.google.inject.Inject; +import org.testng.annotations.Guice; +import org.testng.annotations.Test; + +import java.net.URISyntaxException; + +/** + * 测试代码,待补充完善. + * + * @author Binary Wang + * @date 2020-05-19 + */ +@Test +@Guice(modules = ApiTestModule.class) +public class PayScoreServiceImplTest { + @Inject + private WxPayService payService; + + @Test + + public void testCreateServiceOrder() throws WxPayException { + //测试数据 +/* { + "out_order_no":"QLS202005201058000201", + "appid":"", + "service_id":"", + "service_introduction":"租借服务", + "time_range":{ + "start_time":"OnAccept", + "end_time":"20200520225840" + }, + "location":{ + "start_location":"山", + "end_location":"山" + }, + "risk_fund":{ + "name":"DEPOSIT", + "amount":200, + "description":"丢失偿还费用2元/台" + }, + "attach":"", + "notify_url":"/pay/notify/payScore", + "openid":"", + "need_user_confirm":true, + "profit_sharing":false, + "post_payments":[ + { + "name":"租借服务", + "amount":100, + "description":"服务费:1元/台", + "count":1 + } + ], + "total_amount":0 + }*/ + + this.payService.getPayScoreService().createServiceOrder(WxPayScoreRequest.builder().build()); + } + + @Test + public void testQueryServiceOrder() throws URISyntaxException, WxPayException { + //两个参数选填一个 + this.payService.getPayScoreService().queryServiceOrder("11", ""); + } + + @Test + public void testCancelServiceOrder() throws WxPayException { + this.payService.getPayScoreService().cancelServiceOrder("11", "测试取消"); + } + + @Test + public void testModifyServiceOrder() { + } + + @Test + public void testCompleteServiceOrder() throws WxPayException { +/* { + "appid":"", + "service_id":"", + "time_range":{ + "end_time":"20200520111702" + }, + "need_user_confirm":false, + "profit_sharing":false, + "post_payments":[ + { + "name":"租借服务", + "amount":100, + "description":"服务费:1.0000元/台", + "count":1 + } + ], + "total_amount":100 + } +*/ + this.payService.getPayScoreService().completeServiceOrder(WxPayScoreRequest.builder().build()); + } + + @Test + public void testPayServiceOrder() { + } + + @Test + public void testSyncServiceOrder() { + } + + @Test + public void testDecryptNotifyData() { + } +}