Bläddra i källkod

Merge pull request #770 from Wechat-Group/develop

合并develop分支,发布3.2.0正式版本
master
Binary Wang 6 år sedan
committed by GitHub
förälder
incheckning
d854ad8811
Ingen känd nyckel hittad för denna signaturen i databasen GPG-nyckel ID: 4AEE18F83AFDEB23
100 ändrade filer med 2997 tillägg och 576 borttagningar
  1. +2
    -2
      pom.xml
  2. Binär
      qrcodes/cp_qrcode.png
  3. Binär
      qrcodes/mp_qrcode.jpg
  4. Binär
      qrcodes/wepay_qrcode_s.jpg
  5. +1
    -15
      quality-checks/google_checks.xml
  6. +2
    -2
      weixin-java-common/pom.xml
  7. +102
    -3
      weixin-java-common/src/main/java/me/chanjar/weixin/common/api/WxConsts.java
  8. +2
    -0
      weixin-java-common/src/main/java/me/chanjar/weixin/common/api/WxErrorExceptionHandler.java
  9. +2
    -0
      weixin-java-common/src/main/java/me/chanjar/weixin/common/api/WxMessageDuplicateChecker.java
  10. +2
    -0
      weixin-java-common/src/main/java/me/chanjar/weixin/common/api/WxMessageInMemoryDuplicateChecker.java
  11. +7
    -2
      weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/WxAccessToken.java
  12. +6
    -5
      weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/WxCardApiSignature.java
  13. +10
    -2
      weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/WxJsapiSignature.java
  14. +7
    -5
      weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/menu/WxMenu.java
  15. +12
    -5
      weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/menu/WxMenuButton.java
  16. +11
    -4
      weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/menu/WxMenuRule.java
  17. +11
    -4
      weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/result/WxMediaUploadResult.java
  18. +4
    -0
      weixin-java-common/src/main/java/me/chanjar/weixin/common/session/InternalSession.java
  19. +3
    -0
      weixin-java-common/src/main/java/me/chanjar/weixin/common/session/InternalSessionManager.java
  20. +10
    -3
      weixin-java-common/src/main/java/me/chanjar/weixin/common/session/StandardSession.java
  21. +3
    -0
      weixin-java-common/src/main/java/me/chanjar/weixin/common/session/StandardSessionFacade.java
  22. +8
    -5
      weixin-java-common/src/main/java/me/chanjar/weixin/common/session/StandardSessionManager.java
  23. +2
    -0
      weixin-java-common/src/main/java/me/chanjar/weixin/common/session/TooManyActiveSessionsException.java
  24. +3
    -0
      weixin-java-common/src/main/java/me/chanjar/weixin/common/session/WxSession.java
  25. +3
    -0
      weixin-java-common/src/main/java/me/chanjar/weixin/common/session/WxSessionManager.java
  26. +40
    -0
      weixin-java-common/src/main/java/me/chanjar/weixin/common/util/SignUtils.java
  27. +0
    -64
      weixin-java-common/src/main/java/me/chanjar/weixin/common/util/ToStringUtils.java
  28. +4
    -2
      weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/ApacheHttpDnsClientBuilder.java
  29. +13
    -15
      weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/DefaultApacheHttpClientBuilder.java
  30. +21
    -14
      weixin-java-common/src/main/java/me/chanjar/weixin/common/util/json/WxMediaUploadResultAdapter.java
  31. +1
    -1
      weixin-java-cp/pom.xml
  32. +129
    -0
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/WxCpConsts.java
  33. +25
    -1
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpAgentService.java
  34. +21
    -3
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpMediaService.java
  35. +18
    -4
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpOAuth2Service.java
  36. +28
    -14
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpUserService.java
  37. +36
    -5
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpAgentServiceImpl.java
  38. +18
    -11
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpMediaServiceImpl.java
  39. +26
    -9
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpOAuth2ServiceImpl.java
  40. +16
    -4
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpUserServiceImpl.java
  41. +21
    -12
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpAgent.java
  42. +9
    -7
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpInviteResult.java
  43. +9
    -7
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpMessageSendResult.java
  44. +9
    -7
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTagAddOrRemoveUsersResult.java
  45. +50
    -4
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpUser.java
  46. +31
    -1
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpUserDetail.java
  47. +126
    -0
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpUserExternalContactInfo.java
  48. +214
    -52
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpXmlMessage.java
  49. +13
    -3
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpXmlOutMessage.java
  50. +6
    -4
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/WxCpInMemoryConfigStorage.java
  51. +12
    -5
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/WxCpJedisConfigStorage.java
  52. +3
    -0
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/util/json/WxCpGsonBuilder.java
  53. +104
    -5
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/util/json/WxCpUserGsonAdapter.java
  54. +56
    -18
      weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/impl/WxCpAgentServiceImplTest.java
  55. +19
    -12
      weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/impl/WxCpMediaServiceImplTest.java
  56. +17
    -9
      weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/impl/WxCpUserServiceImplTest.java
  57. +3
    -3
      weixin-java-cp/src/test/java/me/chanjar/weixin/cp/bean/WxCpAgentTest.java
  58. +129
    -0
      weixin-java-cp/src/test/java/me/chanjar/weixin/cp/bean/WxCpUserExternalContactInfoTest.java
  59. +30
    -10
      weixin-java-cp/src/test/java/me/chanjar/weixin/cp/demo/WxCpDemoServer.java
  60. +124
    -0
      weixin-java-cp/src/test/java/me/chanjar/weixin/cp/util/json/WxCpUserGsonAdapterTest.java
  61. +1
    -1
      weixin-java-miniapp/pom.xml
  62. +7
    -3
      weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaCodeService.java
  63. +48
    -0
      weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaJsapiService.java
  64. +12
    -0
      weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaMsgService.java
  65. +24
    -1
      weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaService.java
  66. +12
    -0
      weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaUserService.java
  67. +18
    -8
      weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaCodeServiceImpl.java
  68. +75
    -0
      weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaJsapiServiceImpl.java
  69. +10
    -0
      weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaMsgServiceImpl.java
  70. +34
    -13
      weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaServiceImpl.java
  71. +37
    -2
      weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaUserServiceImpl.java
  72. +10
    -8
      weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/WxMaMessage.java
  73. +32
    -0
      weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/WxMaTemplateData.java
  74. +9
    -24
      weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/WxMaTemplateMessage.java
  75. +108
    -0
      weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/WxMaUniformMessage.java
  76. +40
    -34
      weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/code/WxMaCodeExtConfig.java
  77. +19
    -0
      weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/config/WxMaConfig.java
  78. +38
    -5
      weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/config/WxMaInMemoryConfig.java
  79. +2
    -0
      weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/util/json/WxMaGsonBuilder.java
  80. +4
    -7
      weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/util/json/WxMaTemplateMessageGsonAdapter.java
  81. +98
    -0
      weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/util/json/WxMaUniformMessageGsonAdapter.java
  82. +9
    -10
      weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/api/impl/WxMaCodeServiceImplTest.java
  83. +45
    -0
      weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/api/impl/WxMaJsapiServiceImplTest.java
  84. +30
    -9
      weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/api/impl/WxMaMsgServiceImplTest.java
  85. +11
    -5
      weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/api/impl/WxMaServiceImplTest.java
  86. +19
    -0
      weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/api/impl/WxMaUserServiceImplTest.java
  87. +7
    -6
      weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/bean/WxMaTemplateMessageTest.java
  88. +12
    -10
      weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/demo/WxMaDemoServer.java
  89. +1
    -0
      weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/test/TestConfig.java
  90. +112
    -0
      weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/util/json/WxMaUniformMessageGsonAdapterTest.java
  91. +1
    -1
      weixin-java-mp/pom.xml
  92. +61
    -1
      weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpCardService.java
  93. +7
    -5
      weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpInMemoryConfigStorage.java
  94. +58
    -9
      weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpKefuService.java
  95. +12
    -4
      weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpMaterialService.java
  96. +46
    -8
      weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpMemberCardService.java
  97. +93
    -4
      weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpCardServiceImpl.java
  98. +27
    -12
      weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpKefuServiceImpl.java
  99. +23
    -11
      weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpMaterialServiceImpl.java
  100. +191
    -12
      weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpMemberCardServiceImpl.java

+ 2
- 2
pom.xml Visa fil

@@ -6,10 +6,10 @@
<modelVersion>4.0.0</modelVersion>
<groupId>com.github.binarywang</groupId>
<artifactId>weixin-java-parent</artifactId>
<version>3.1.0</version>
<version>3.2.0</version>
<packaging>pom</packaging>
<name>Weixin Java Tools - Parent</name>
<description>微信公众号、企业号上级POM</description>
<description>微信开发Java SDK</description>
<url>https://github.com/wechat-group/weixin-java-tools</url>

<licenses>


Binär
qrcodes/cp_qrcode.png Visa fil

Före Efter
Bredd: 258  |  Höjd: 300  |  Storlek: 24 KiB

Binär
qrcodes/mp_qrcode.jpg Visa fil

Före Efter
Bredd: 258  |  Höjd: 300  |  Storlek: 36 KiB

Binär
qrcodes/wepay_qrcode_s.jpg Visa fil

Före Efter
Bredd: 258  |  Höjd: 300  |  Storlek: 22 KiB

+ 1
- 15
quality-checks/google_checks.xml Visa fil

@@ -83,20 +83,6 @@
<module name="EmptyLineSeparator">
<property name="allowNoEmptyLineBetweenFields" value="true"/>
</module>
<module name="SeparatorWrap">
<property name="id" value="SeparatorWrapDot"/>
<property name="tokens" value="DOT"/>
<property name="option" value="nl"/>
</module>
<module name="SeparatorWrap">
<property name="id" value="SeparatorWrapComma"/>
<property name="tokens" value="COMMA"/>
<property name="option" value="EOL"/>
</module>
<module name="SeparatorWrap">
<property name="id" value="SeparatorWrapMethodRef"/>
<property name="option" value="nl"/>
</module>
<module name="PackageName">
<property name="format" value="^[a-z]+(\.[a-z][a-z0-9]*)*$"/>
<message key="name.invalidPattern"
@@ -156,7 +142,7 @@
<module name="VariableDeclarationUsageDistance"/>
<module name="CustomImportOrder">
<property name="customImportOrderRules"
value="THIRD_PARTY_PACKAGE###SPECIAL_IMPORTS###STANDARD_JAVA_PACKAGE###STATIC"/>
value="SPECIAL_IMPORTS###STANDARD_JAVA_PACKAGE###THIRD_PARTY_PACKAGE###STATIC"/>
<property name="specialImportsRegExp" value="^javax\."/>
<property name="standardPackageRegExp" value="^java\."/>
<property name="sortImportsInGroupAlphabetically" value="true"/>


+ 2
- 2
weixin-java-common/pom.xml Visa fil

@@ -7,12 +7,12 @@
<parent>
<groupId>com.github.binarywang</groupId>
<artifactId>weixin-java-parent</artifactId>
<version>3.1.0</version>
<version>3.2.0</version>
</parent>

<artifactId>weixin-java-common</artifactId>
<name>Weixin Java Tools - Common</name>
<description>微信公众号、企业号Java SDK Common</description>
<description>微信开发Java SDK公共模块</description>

<dependencies>
<dependency>


+ 102
- 3
weixin-java-common/src/main/java/me/chanjar/weixin/common/api/WxConsts.java Visa fil

@@ -6,7 +6,7 @@ import java.util.Map;
/**
* 微信开发所使用到的常量类.
*
* @author chanjarster & binarywang
* @author Daniel Qian & binarywang
*/
public class WxConsts {
/**
@@ -82,7 +82,7 @@ public class WxConsts {
/**
* 小程序卡片(要求小程序与公众号已关联)
*/
public static final String MINIPROGRAMPAGE="miniprogrampage";
public static final String MINIPROGRAMPAGE = "miniprogrampage";
}

/**
@@ -151,14 +151,106 @@ public class WxConsts {
public static final String CLICK = "CLICK";
public static final String VIEW = "VIEW";
public static final String MASS_SEND_JOB_FINISH = "MASSSENDJOBFINISH";
/**
* 扫码推事件的事件推送
*/
public static final String SCANCODE_PUSH = "scancode_push";
/**
* 扫码推事件且弹出“消息接收中”提示框的事件推送.
*/
public static final String SCANCODE_WAITMSG = "scancode_waitmsg";
/**
* 弹出系统拍照发图的事件推送.
*/
public static final String PIC_SYSPHOTO = "pic_sysphoto";
/**
* 弹出拍照或者相册发图的事件推送.
*/
public static final String PIC_PHOTO_OR_ALBUM = "pic_photo_or_album";
/**
* 弹出微信相册发图器的事件推送.
*/
public static final String PIC_WEIXIN = "pic_weixin";
/**
* 弹出地理位置选择器的事件推送.
*/
public static final String LOCATION_SELECT = "location_select";

public static final String TEMPLATE_SEND_JOB_FINISH = "TEMPLATESENDJOBFINISH";
public static final String ENTER_AGENT = "enter_agent";
/**
* 微信小店 订单付款通知.
*/
public static final String MERCHANT_ORDER = "merchant_order";

/**
* 卡券事件:卡券通过审核
*/
public static final String CARD_PASS_CHECK = "card_pass_check";

/**
* 卡券事件:卡券未通过审核
*/
public static final String CARD_NOT_PASS_CHECK = "card_not_pass_check";

/**
* 卡券事件:用户领取卡券
*/
public static final String CARD_USER_GET_CARD = "user_get_card";

/**
* 卡券事件:用户转赠卡券
*/
public static final String CARD_USER_GIFTING_CARD = "user_gifting_card";


/**
* 卡券事件:用户核销卡券
*/
public static final String CARD_USER_CONSUME_CARD = "user_consume_card";


/**
* 卡券事件:用户通过卡券的微信买单完成时推送
*/
public static final String CARD_USER_PAY_FROM_PAY_CELL = "user_pay_from_pay_cell";


/**
* 卡券事件:用户提交会员卡开卡信息
*/
public static final String CARD_SUBMIT_MEMBERCARD_USER_INFO = "submit_membercard_user_info";

/**
* 卡券事件:用户打开查看卡券
*/
public static final String CARD_USER_VIEW_CARD = "user_view_card";

/**
* 卡券事件:用户删除卡券
*/
public static final String CARD_USER_DEL_CARD = "user_del_card";

/**
* 卡券事件:用户在卡券里点击查看公众号进入会话时(需要用户已经关注公众号)
*/
public static final String CARD_USER_ENTER_SESSION_FROM_CARD = "user_enter_session_from_card";

/**
* 卡券事件:当用户的会员卡积分余额发生变动时
*/
public static final String CARD_UPDATE_MEMBER_CARD = "update_member_card";

/**
* 卡券事件:当某个card_id的初始库存数大于200且当前库存小于等于100时,用户尝试领券会触发发送事件给商户,事件每隔12h发送一次
*/
public static final String CARD_SKU_REMIND = "card_sku_remind";

/**
* 卡券事件:当商户朋友的券券点发生变动时
*/
public static final String CARD_PAY_ORDER = "card_pay_order";


}

/**
@@ -230,10 +322,16 @@ public class WxConsts {
* 不弹出授权页面,直接跳转,只能获取用户openid.
*/
public static final String SNSAPI_BASE = "snsapi_base";

/**
* 弹出授权页面,可通过openid拿到昵称、性别、所在地。并且,即使在未关注的情况下,只要用户授权,也能获取其信息.
*/
public static final String SNSAPI_USERINFO = "snsapi_userinfo";

/**
* 手动授权,可获取成员的详细信息,包含手机、邮箱。只适用于企业微信或企业号.
*/
public static final String SNSAPI_PRIVATEINFO = "snsapi_privateinfo";
}

/**
@@ -252,4 +350,5 @@ public class WxConsts {
public static final String IMAGE = "image";
public static final String VIDEO = "video";
}

}

+ 2
- 0
weixin-java-common/src/main/java/me/chanjar/weixin/common/api/WxErrorExceptionHandler.java Visa fil

@@ -4,6 +4,8 @@ import me.chanjar.weixin.common.error.WxErrorException;

/**
* WxErrorException处理器.
*
* @author Daniel Qian
*/
public interface WxErrorExceptionHandler {



+ 2
- 0
weixin-java-common/src/main/java/me/chanjar/weixin/common/api/WxMessageDuplicateChecker.java Visa fil

@@ -5,6 +5,8 @@ package me.chanjar.weixin.common.api;
* 消息重复检查器.
* 微信服务器在五秒内收不到响应会断掉连接,并且重新发起请求,总共重试三次
* </pre>
*
* @author Daniel Qian
*/
public interface WxMessageDuplicateChecker {



+ 2
- 0
weixin-java-common/src/main/java/me/chanjar/weixin/common/api/WxMessageInMemoryDuplicateChecker.java Visa fil

@@ -9,6 +9,8 @@ import java.util.concurrent.atomic.AtomicBoolean;
* 默认消息重复检查器.
* 将每个消息id保存在内存里,每隔5秒清理已经过期的消息id,每个消息id的过期时间是15秒
* </pre>
*
* @author Daniel Qian
*/
public class WxMessageInMemoryDuplicateChecker implements WxMessageDuplicateChecker {



+ 7
- 2
weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/WxAccessToken.java Visa fil

@@ -1,10 +1,15 @@
package me.chanjar.weixin.common.bean;

import java.io.Serializable;

import lombok.Data;
import me.chanjar.weixin.common.util.json.WxGsonBuilder;

import java.io.Serializable;

/**
* access token.
*
* @author Daniel Qian
*/
@Data
public class WxAccessToken implements Serializable {
private static final long serialVersionUID = 8709719312922168909L;


+ 6
- 5
weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/WxCardApiSignature.java Visa fil

@@ -1,10 +1,12 @@
package me.chanjar.weixin.common.bean;

import lombok.Data;
import me.chanjar.weixin.common.util.ToStringUtils;

import java.io.Serializable;

import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;

import lombok.Data;

/**
* 卡券Api签名.
*
@@ -35,7 +37,6 @@ public class WxCardApiSignature implements Serializable {

@Override
public String toString() {
return ToStringUtils.toSimpleString(this);
return ToStringBuilder.reflectionToString(this, ToStringStyle.JSON_STYLE);
}

}

+ 10
- 2
weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/WxJsapiSignature.java Visa fil

@@ -1,13 +1,21 @@
package me.chanjar.weixin.common.bean;

import lombok.Data;

import java.io.Serializable;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
* jspai signature.
*
* @author Daniel Qian
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class WxJsapiSignature implements Serializable {
private static final long serialVersionUID = -1116808193154384804L;



+ 7
- 5
weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/menu/WxMenu.java Visa fil

@@ -1,9 +1,5 @@
package me.chanjar.weixin.common.bean.menu;

import lombok.Data;
import me.chanjar.weixin.common.util.ToStringUtils;
import me.chanjar.weixin.common.util.json.WxGsonBuilder;

import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Serializable;
@@ -11,6 +7,12 @@ import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;

import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;

import lombok.Data;
import me.chanjar.weixin.common.util.json.WxGsonBuilder;

/**
* 菜单(公众号和企业号共用的).
*
@@ -47,7 +49,7 @@ public class WxMenu implements Serializable {

@Override
public String toString() {
return ToStringUtils.toSimpleString(this);
return ToStringBuilder.reflectionToString(this, ToStringStyle.JSON_STYLE);
}

}

+ 12
- 5
weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/menu/WxMenuButton.java Visa fil

@@ -1,13 +1,20 @@
package me.chanjar.weixin.common.bean.menu;

import com.google.gson.annotations.SerializedName;
import lombok.Data;
import me.chanjar.weixin.common.util.ToStringUtils;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;

import com.google.gson.annotations.SerializedName;
import lombok.Data;

/**
* menu button.
*
* @author Daniel Qian
*/
@Data
public class WxMenuButton implements Serializable {
private static final long serialVersionUID = -1070939403109776555L;
@@ -76,7 +83,7 @@ public class WxMenuButton implements Serializable {

@Override
public String toString() {
return ToStringUtils.toSimpleString(this);
return ToStringBuilder.reflectionToString(this, ToStringStyle.JSON_STYLE);
}

}

+ 11
- 4
weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/menu/WxMenuRule.java Visa fil

@@ -1,11 +1,18 @@
package me.chanjar.weixin.common.bean.menu;

import java.io.Serializable;

import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;

import com.google.gson.annotations.SerializedName;
import lombok.Data;
import me.chanjar.weixin.common.util.ToStringUtils;

import java.io.Serializable;

/**
* menu rule.
*
* @author Daniel Qian
*/
@Data
public class WxMenuRule implements Serializable {
private static final long serialVersionUID = -4587181819499286670L;
@@ -24,6 +31,6 @@ public class WxMenuRule implements Serializable {

@Override
public String toString() {
return ToStringUtils.toSimpleString(this);
return ToStringBuilder.reflectionToString(this, ToStringStyle.JSON_STYLE);
}
}

+ 11
- 4
weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/result/WxMediaUploadResult.java Visa fil

@@ -1,15 +1,22 @@
package me.chanjar.weixin.common.bean.result;

import java.io.Serializable;

import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;

import lombok.Data;
import me.chanjar.weixin.common.util.ToStringUtils;
import me.chanjar.weixin.common.util.json.WxGsonBuilder;

import java.io.Serializable;

/**
*
* @author Daniel Qian
*/
@Data
public class WxMediaUploadResult implements Serializable {
private static final long serialVersionUID = 330834334738622341L;

private String url;
private String type;
private String mediaId;
private String thumbMediaId;
@@ -21,7 +28,7 @@ public class WxMediaUploadResult implements Serializable {

@Override
public String toString() {
return ToStringUtils.toSimpleString(this);
return ToStringBuilder.reflectionToString(this, ToStringStyle.JSON_STYLE);
}

}

+ 4
- 0
weixin-java-common/src/main/java/me/chanjar/weixin/common/session/InternalSession.java Visa fil

@@ -1,5 +1,9 @@
package me.chanjar.weixin.common.session;

/**
*
* @author Daniel Qian
*/
public interface InternalSession {

/**


+ 3
- 0
weixin-java-common/src/main/java/me/chanjar/weixin/common/session/InternalSessionManager.java Visa fil

@@ -1,5 +1,8 @@
package me.chanjar.weixin.common.session;

/**
* @author Daniel Qian
*/
public interface InternalSessionManager {

/**


+ 10
- 3
weixin-java-common/src/main/java/me/chanjar/weixin/common/session/StandardSession.java Visa fil

@@ -1,11 +1,18 @@
package me.chanjar.weixin.common.session;

import me.chanjar.weixin.common.util.res.StringManager;

import java.util.*;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;

import me.chanjar.weixin.common.util.res.StringManager;

/**
* @author Daniel Qian
*/
public class StandardSession implements WxSession, InternalSession {

/**


+ 3
- 0
weixin-java-common/src/main/java/me/chanjar/weixin/common/session/StandardSessionFacade.java Visa fil

@@ -2,6 +2,9 @@ package me.chanjar.weixin.common.session;

import java.util.Enumeration;

/**
* @author Daniel Qian
*/
public class StandardSessionFacade implements WxSession {

/**


+ 8
- 5
weixin-java-common/src/main/java/me/chanjar/weixin/common/session/StandardSessionManager.java Visa fil

@@ -1,15 +1,18 @@
package me.chanjar.weixin.common.session;

import me.chanjar.weixin.common.util.res.StringManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicBoolean;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import me.chanjar.weixin.common.util.res.StringManager;

/**
* 基于内存的session manager
* 基于内存的session manager.
*
* @author Daniel Qian
*/
public class StandardSessionManager implements WxSessionManager, InternalSessionManager {



+ 2
- 0
weixin-java-common/src/main/java/me/chanjar/weixin/common/session/TooManyActiveSessionsException.java Visa fil

@@ -19,6 +19,8 @@ package me.chanjar.weixin.common.session;
/**
* An exception that indicates the maximum number of active sessions has been
* reached and the server is refusing to create any new sessions.
*
* @author Daniel Qian
*/
public class TooManyActiveSessionsException
extends IllegalStateException {


+ 3
- 0
weixin-java-common/src/main/java/me/chanjar/weixin/common/session/WxSession.java Visa fil

@@ -2,6 +2,9 @@ package me.chanjar.weixin.common.session;

import java.util.Enumeration;

/**
* @author Daniel Qian
*/
public interface WxSession {

Object getAttribute(String name);


+ 3
- 0
weixin-java-common/src/main/java/me/chanjar/weixin/common/session/WxSessionManager.java Visa fil

@@ -1,5 +1,8 @@
package me.chanjar.weixin.common.session;

/**
* @author Daniel Qian
*/
public interface WxSessionManager {

/**


+ 40
- 0
weixin-java-common/src/main/java/me/chanjar/weixin/common/util/SignUtils.java Visa fil

@@ -0,0 +1,40 @@
package me.chanjar.weixin.common.util;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Hex;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;

/**
* <pre>
* 签名工具类
* Created by BinaryWang on 2018/7/11.
* </pre>
*
* @author <a href="https://github.com/binarywang">Binary Wang</a>
*/
@Slf4j
public class SignUtils {
/**
* HmacSHA256 签名算法
*
* @param message 签名数据
* @param key 签名密钥
*/
public static String createHmacSha256Sign(String message, String key) {
try {
Mac sha256 = Mac.getInstance("HmacSHA256");
SecretKeySpec secretKeySpec = new SecretKeySpec(key.getBytes(), "HmacSHA256");
sha256.init(secretKeySpec);
byte[] bytes = sha256.doFinal(message.getBytes());
return Hex.encodeHexString(bytes).toUpperCase();
} catch (NoSuchAlgorithmException | InvalidKeyException e) {
SignUtils.log.error(e.getMessage(), e);
}

return null;
}
}

+ 0
- 64
weixin-java-common/src/main/java/me/chanjar/weixin/common/util/ToStringUtils.java Visa fil

@@ -1,64 +0,0 @@
package me.chanjar.weixin.common.util;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;

/**
* <pre>
* 自定义的ToString方法,用于产生去掉空值属性的字符串
* Created by Binary Wang on 2016-10-27.
* </pre>
*
* @author <a href="https://github.com/binarywang">Binary Wang</a>
*/
public class ToStringUtils {
public static final ToStringStyle THE_STYLE = new SimpleMultiLineToStringStyle();

/**
* 用于产生去掉空值属性并以换行符分割各属性键值的toString字符串
*
* @param obj
*/
public static String toSimpleString(Object obj) {
String toStringResult = ToStringBuilder.reflectionToString(obj, THE_STYLE);
String[] split = toStringResult.split(SimpleMultiLineToStringStyle.LINE_SEPARATOR);
StringBuilder result = new StringBuilder();
for (String string : split) {
if (string.endsWith(SimpleMultiLineToStringStyle.NULL_TEXT)) {
continue;
}

result.append(string + SimpleMultiLineToStringStyle.LINE_SEPARATOR);
}

if (result.length() == 0) {
return "";
}

//如果没有非空的属性,就输出 <all null properties>
if (StringUtils.countMatches(result, SimpleMultiLineToStringStyle.LINE_SEPARATOR) == 2) {
return result.toString().split(SimpleMultiLineToStringStyle.LINE_SEPARATOR)[0]
+ "<all null values>]";
}

return result.deleteCharAt(result.length() - 1).toString();
}

private static class SimpleMultiLineToStringStyle extends ToStringStyle {
private static final long serialVersionUID = 4645306494220335355L;
private static final String LINE_SEPARATOR = "\n";
private static final String NULL_TEXT = "<null>";

public SimpleMultiLineToStringStyle() {
super();
this.setContentStart("[");
this.setFieldSeparator(LINE_SEPARATOR + " ");
this.setFieldSeparatorAtStart(true);
this.setContentEnd(LINE_SEPARATOR + "]");
this.setNullText(NULL_TEXT);
this.setUseShortClassName(true);
this.setUseIdentityHashCode(false);
}
}
}

+ 4
- 2
weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/ApacheHttpDnsClientBuilder.java Visa fil

@@ -1,6 +1,7 @@
package me.chanjar.weixin.common.util.http.apache;

import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpHost;
import org.apache.http.annotation.NotThreadSafe;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
@@ -236,9 +237,10 @@ public class ApacheHttpDnsClientBuilder implements ApacheHttpClientBuilder {
if (StringUtils.isNotBlank(this.httpProxyHost) && StringUtils.isNotBlank(this.httpProxyUsername)) {
// 使用代理服务器 需要用户认证的代理服务器
CredentialsProvider provider = new BasicCredentialsProvider();
provider.setCredentials(new AuthScope(this.httpProxyHost, this.httpProxyPort)
, new UsernamePasswordCredentials(this.httpProxyUsername, this.httpProxyPassword));
provider.setCredentials(new AuthScope(this.httpProxyHost, this.httpProxyPort),
new UsernamePasswordCredentials(this.httpProxyUsername, this.httpProxyPassword));
this.httpClientBuilder.setDefaultCredentialsProvider(provider);
this.httpClientBuilder.setProxy(new HttpHost(this.httpProxyHost, this.httpProxyPort));
}

if (StringUtils.isNotBlank(this.userAgent)) {


+ 13
- 15
weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/DefaultApacheHttpClientBuilder.java Visa fil

@@ -1,6 +1,7 @@
package me.chanjar.weixin.common.util.http.apache;

import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpHost;
import org.apache.http.annotation.NotThreadSafe;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
@@ -226,29 +227,26 @@ public class DefaultApacheHttpClientBuilder implements ApacheHttpClientBuilder {
.setConnectionManager(connectionManager)
.setConnectionManagerShared(true)
.setSSLSocketFactory(this.buildSSLConnectionSocketFactory())
.setDefaultRequestConfig(
RequestConfig.custom()
.setSocketTimeout(this.soTimeout)
.setConnectTimeout(this.connectionTimeout)
.setConnectionRequestTimeout(this.connectionRequestTimeout)
.build()
)
.setRetryHandler(this.httpRequestRetryHandler);

if (StringUtils.isNotBlank(this.httpProxyHost)
&& StringUtils.isNotBlank(this.httpProxyUsername)) {
.setDefaultRequestConfig(RequestConfig.custom()
.setSocketTimeout(this.soTimeout)
.setConnectTimeout(this.connectionTimeout)
.setConnectionRequestTimeout(this.connectionRequestTimeout)
.build()
).setRetryHandler(this.httpRequestRetryHandler);

if (StringUtils.isNotBlank(this.httpProxyHost) && StringUtils.isNotBlank(this.httpProxyUsername)) {
// 使用代理服务器 需要用户认证的代理服务器
CredentialsProvider provider = new BasicCredentialsProvider();
provider.setCredentials(
new AuthScope(this.httpProxyHost, this.httpProxyPort),
new UsernamePasswordCredentials(this.httpProxyUsername,
this.httpProxyPassword));
provider.setCredentials(new AuthScope(this.httpProxyHost, this.httpProxyPort),
new UsernamePasswordCredentials(this.httpProxyUsername, this.httpProxyPassword));
httpClientBuilder.setDefaultCredentialsProvider(provider);
httpClientBuilder.setProxy(new HttpHost(this.httpProxyHost, this.httpProxyPort));
}

if (StringUtils.isNotBlank(this.userAgent)) {
httpClientBuilder.setUserAgent(this.userAgent);
}

this.closeableHttpClient = httpClientBuilder.build();
prepared.set(true);
}


+ 21
- 14
weixin-java-common/src/main/java/me/chanjar/weixin/common/util/json/WxMediaUploadResultAdapter.java Visa fil

@@ -1,10 +1,14 @@
package me.chanjar.weixin.common.util.json;

import com.google.gson.*;
import me.chanjar.weixin.common.bean.result.WxMediaUploadResult;

import java.lang.reflect.Type;

import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import me.chanjar.weixin.common.bean.result.WxMediaUploadResult;

/**
* @author Daniel Qian
*/
@@ -12,22 +16,25 @@ public class WxMediaUploadResultAdapter implements JsonDeserializer<WxMediaUploa

@Override
public WxMediaUploadResult deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
WxMediaUploadResult uploadResult = new WxMediaUploadResult();
JsonObject uploadResultJsonObject = json.getAsJsonObject();
WxMediaUploadResult result = new WxMediaUploadResult();
JsonObject jsonObject = json.getAsJsonObject();
if (jsonObject.get("url") != null && !jsonObject.get("url").isJsonNull()) {
result.setUrl(GsonHelper.getAsString(jsonObject.get("url")));
}

if (uploadResultJsonObject.get("type") != null && !uploadResultJsonObject.get("type").isJsonNull()) {
uploadResult.setType(GsonHelper.getAsString(uploadResultJsonObject.get("type")));
if (jsonObject.get("type") != null && !jsonObject.get("type").isJsonNull()) {
result.setType(GsonHelper.getAsString(jsonObject.get("type")));
}
if (uploadResultJsonObject.get("media_id") != null && !uploadResultJsonObject.get("media_id").isJsonNull()) {
uploadResult.setMediaId(GsonHelper.getAsString(uploadResultJsonObject.get("media_id")));
if (jsonObject.get("media_id") != null && !jsonObject.get("media_id").isJsonNull()) {
result.setMediaId(GsonHelper.getAsString(jsonObject.get("media_id")));
}
if (uploadResultJsonObject.get("thumb_media_id") != null && !uploadResultJsonObject.get("thumb_media_id").isJsonNull()) {
uploadResult.setThumbMediaId(GsonHelper.getAsString(uploadResultJsonObject.get("thumb_media_id")));
if (jsonObject.get("thumb_media_id") != null && !jsonObject.get("thumb_media_id").isJsonNull()) {
result.setThumbMediaId(GsonHelper.getAsString(jsonObject.get("thumb_media_id")));
}
if (uploadResultJsonObject.get("created_at") != null && !uploadResultJsonObject.get("created_at").isJsonNull()) {
uploadResult.setCreatedAt(GsonHelper.getAsPrimitiveLong(uploadResultJsonObject.get("created_at")));
if (jsonObject.get("created_at") != null && !jsonObject.get("created_at").isJsonNull()) {
result.setCreatedAt(GsonHelper.getAsPrimitiveLong(jsonObject.get("created_at")));
}
return uploadResult;
return result;
}

}

+ 1
- 1
weixin-java-cp/pom.xml Visa fil

@@ -7,7 +7,7 @@
<parent>
<groupId>com.github.binarywang</groupId>
<artifactId>weixin-java-parent</artifactId>
<version>3.1.0</version>
<version>3.2.0</version>
</parent>

<artifactId>weixin-java-cp</artifactId>


+ 129
- 0
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/WxCpConsts.java Visa fil

@@ -0,0 +1,129 @@
package me.chanjar.weixin.cp;

/**
* <pre>
* 企业微信常量
* Created by Binary Wang on 2018/8/25.
* </pre>
*
* @author <a href="https://github.com/binarywang">Binary Wang</a>
*/
public class WxCpConsts {
/**
* 企业微信端推送过来的事件类型.
* 参考文档:https://work.weixin.qq.com/api/doc#12974
*/
public static class EventType {
/**
* 成员关注事件.
*/
public static final String SUBSCRIBE = "subscribe";

/**
* 成员取消关注事件.
*/
public static final String UNSUBSCRIBE = "unsubscribe";

/**
* 进入应用事件.
*/
public static final String ENTER_AGENT = "enter_agent";

/**
* 上报地理位置.
*/
public static final String LOCATION = "LOCATION";

/**
* 异步任务完成事件推送.
*/
public static final String BATCH_JOB_RESULT = "batch_job_result";

/**
* 企业微信通讯录变更事件.
*/
public static final String CHANGE_CONTACT = "change_contact";

/**
* 点击菜单拉取消息的事件推送.
*/
public static final String CLICK = "click";

/**
* 点击菜单跳转链接的事件推送.
*/
public static final String VIEW = "view";

/**
* 扫码推事件的事件推送.
*/
public static final String SCANCODE_PUSH = "scancode_push";

/**
* 扫码推事件且弹出“消息接收中”提示框的事件推送.
*/
public static final String SCANCODE_WAITMSG = "scancode_waitmsg";

/**
* 弹出系统拍照发图的事件推送.
*/
public static final String PIC_SYSPHOTO = "pic_sysphoto";

/**
* 弹出拍照或者相册发图的事件推送.
*/
public static final String PIC_PHOTO_OR_ALBUM = "pic_photo_or_album";

/**
* 弹出微信相册发图器的事件推送.
*/
public static final String PIC_WEIXIN = "pic_weixin";

/**
* 弹出地理位置选择器的事件推送.
*/
public static final String LOCATION_SELECT = "location_select";

}

/**
* 企业微信通讯录变更事件.
*/
public static class ContactChangeType {
/**
* 新增成员事件.
*/
public static final String CREATE_USER = "create_user";

/**
* 更新成员事件.
*/
public static final String UPDATE_USER = "update_user";

/**
* 删除成员事件.
*/
public static final String DELETE_USER = "delete_user";

/**
* 新增部门事件.
*/
public static final String CREATE_PARTY = "create_party";

/**
* 更新部门事件.
*/
public static final String UPDATE_PARTY = "update_party";

/**
* 删除部门事件.
*/
public static final String DELETE_PARTY = "delete_party";

/**
* 标签成员变更事件.
*/
public static final String UPDATE_TAG = "update_tag";

}
}

+ 25
- 1
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpAgentService.java Visa fil

@@ -3,9 +3,12 @@ package me.chanjar.weixin.cp.api;
import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.cp.bean.WxCpAgent;

import java.util.List;

/**
* <pre>
* 管理企业号应用
* 文档地址:https://work.weixin.qq.com/api/doc#10087
* Created by huansinho on 2018/4/13.
* </pre>
*
@@ -17,7 +20,7 @@ public interface WxCpAgentService {
* <pre>
* 获取企业号应用信息
* 该API用于获取企业号某个应用的基本信息,包括头像、昵称、帐号类型、认证类型、可见范围等信息
* 详情请见: http://qydev.weixin.qq.com/wiki/index.php?title=获取企业号应用
* 详情请见: https://work.weixin.qq.com/api/doc#10087
* </pre>
*
* @param agentId 企业应用的id
@@ -25,4 +28,25 @@ public interface WxCpAgentService {
*/
WxCpAgent get(Integer agentId) throws WxErrorException;

/**
* <pre>
* 设置应用.
* 仅企业可调用,可设置当前凭证对应的应用;第三方不可调用。
* 详情请见: https://work.weixin.qq.com/api/doc#10088
* </pre>
*
* @param agentInfo 应用信息
*/
void set(WxCpAgent agentInfo) throws WxErrorException;

/**
* <pre>
* 获取应用列表.
* 企业仅可获取当前凭证对应的应用;第三方仅可获取被授权的应用。
* 详情请见: https://work.weixin.qq.com/api/doc#11214
* </pre>
*
*/
List<WxCpAgent> list() throws WxErrorException;

}

+ 21
- 3
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpMediaService.java Visa fil

@@ -9,17 +9,20 @@ import me.chanjar.weixin.common.error.WxErrorException;

/**
* <pre>
* 媒体管理接口
* 媒体管理接口.
* Created by BinaryWang on 2017/6/24.
* </pre>
*
* @author <a href="https://github.com/binarywang">Binary Wang</a>
*/
public interface WxCpMediaService {
String MEDIA_GET_URL = "https://qyapi.weixin.qq.com/cgi-bin/media/get";
String MEDIA_UPLOAD_URL = "https://qyapi.weixin.qq.com/cgi-bin/media/upload?type=";
String IMG_UPLOAD_URL = "https://qyapi.weixin.qq.com/cgi-bin/media/uploadimg";

/**
* <pre>
* 上传多媒体文件
* 上传多媒体文件.
* 上传的多媒体文件有格式和大小限制,如下:
* 图片(image): 1M,支持JPG格式
* 语音(voice):2M,播放长度不超过60s,支持AMR\MP3格式
@@ -36,6 +39,8 @@ public interface WxCpMediaService {
throws WxErrorException, IOException;

/**
* 上传多媒体文件.
*
* @param mediaType 媒体类型
* @param file 文件对象
* @see #upload(String, String, InputStream)
@@ -44,7 +49,7 @@ public interface WxCpMediaService {

/**
* <pre>
* 下载多媒体文件
* 下载多媒体文件.
* 根据微信文档,视频文件下载不了,会返回null
* 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=上传下载多媒体文件
* </pre>
@@ -54,4 +59,17 @@ public interface WxCpMediaService {
*/
File download(String mediaId) throws WxErrorException;

/**
* <pre>
* 上传图片.
* 上传图片得到图片URL,该URL永久有效
* 返回的图片URL,仅能用于图文消息(mpnews)正文中的图片展示;若用于非企业微信域名下的页面,图片将被屏蔽。
* 每个企业每天最多可上传100张图片
* 接口url格式:https://qyapi.weixin.qq.com/cgi-bin/media/uploadimg?access_token=ACCESS_TOKEN
* </pre>
*
* @param file 上传的文件对象
* @return 返回图片url
*/
String uploadImg(File file) throws WxErrorException;
}

+ 18
- 4
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpOAuth2Service.java Visa fil

@@ -34,6 +34,19 @@ public interface WxCpOAuth2Service {
*/
String buildAuthorizationUrl(String redirectUri, String state);

/**
* <pre>
* 构造oauth2授权的url连接
* 详情请见: http://qydev.weixin.qq.com/wiki/index.php?title=企业获取code
* </pre>
*
* @param redirectUri 跳转链接地址
* @param state 状态码
* @param scope 取值参考me.chanjar.weixin.common.api.WxConsts.OAuth2Scope类
* @return url
*/
String buildAuthorizationUrl(String redirectUri, String state, String scope);

/**
* <pre>
* 用oauth2获取用户信息
@@ -51,16 +64,17 @@ public interface WxCpOAuth2Service {

/**
* <pre>
* 用oauth2获取用户信息
* 根据code获取成员信息
* http://qydev.weixin.qq.com/wiki/index.php?title=根据code获取成员信息
* https://work.weixin.qq.com/api/doc#10028/根据code获取成员信息
* 因为企业号oauth2.0必须在应用设置里设置通过ICP备案的可信域名,所以无法测试,因此这个方法很可能是坏的。
*
* 注意: 这个方法不使用WxCpConfigStorage里的agentId,需要开发人员自己给出
* </pre>
*
* @param agentId 企业号应用的id
* @param code 微信oauth授权返回的代码
* @return [userid, deviceid]
* @param code 通过成员授权获取到的code,最大为512字节。每次成员授权带上的code将不一样,code只能使用一次,5分钟未被使用自动过期。
* @return [UserId, DeviceId, OpenId, user_ticket, expires_in]
* @see #getUserInfo(String)
*/
String[] getUserInfo(Integer agentId, String code) throws WxErrorException;
@@ -77,7 +91,7 @@ public interface WxCpOAuth2Service {
* 需要有对应应用的使用权限,且成员必须在授权应用的可见范围内。
* </pre>
*
* @param userTicket 成员票据
* @param userTicket 成员票据
*/
WxCpUserDetail getUserDetail(String userTicket) throws WxErrorException;
}

+ 28
- 14
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpUserService.java Visa fil

@@ -1,11 +1,12 @@
package me.chanjar.weixin.cp.api;

import java.util.List;
import java.util.Map;

import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.cp.bean.WxCpInviteResult;
import me.chanjar.weixin.cp.bean.WxCpUser;

import java.util.List;
import java.util.Map;
import me.chanjar.weixin.cp.bean.WxCpUserExternalContactInfo;

/**
* <pre>
@@ -18,7 +19,7 @@ import java.util.Map;
public interface WxCpUserService {
/**
* <pre>
* 用在二次验证的时候
* 用在二次验证的时候.
* 企业在员工验证成功后,调用本方法告诉企业号平台该员工关注成功。
* </pre>
*
@@ -28,7 +29,7 @@ public interface WxCpUserService {

/**
* <pre>
* 获取部门成员(详情)
* 获取部门成员(详情).
*
* http://qydev.weixin.qq.com/wiki/index.php?title=管理成员#.E8.8E.B7.E5.8F.96.E9.83.A8.E9.97.A8.E6.88.90.E5.91.98.28.E8.AF.A6.E6.83.85.29
* </pre>
@@ -41,7 +42,7 @@ public interface WxCpUserService {

/**
* <pre>
* 获取部门成员
* 获取部门成员.
*
* http://qydev.weixin.qq.com/wiki/index.php?title=管理成员#.E8.8E.B7.E5.8F.96.E9.83.A8.E9.97.A8.E6.88.90.E5.91.98
* </pre>
@@ -53,14 +54,14 @@ public interface WxCpUserService {
List<WxCpUser> listSimpleByDepartment(Integer departId, Boolean fetchChild, Integer status) throws WxErrorException;

/**
* 新建用户
* 新建用户.
*
* @param user 用户对象
*/
void create(WxCpUser user) throws WxErrorException;

/**
* 更新用户
* 更新用户.
*
* @param user 用户对象
*/
@@ -68,7 +69,7 @@ public interface WxCpUserService {

/**
* <pre>
* 删除用户/批量删除成员
* 删除用户/批量删除成员.
* http://qydev.weixin.qq.com/wiki/index.php?title=管理成员#.E6.89.B9.E9.87.8F.E5.88.A0.E9.99.A4.E6.88.90.E5.91.98
* </pre>
*
@@ -77,7 +78,7 @@ public interface WxCpUserService {
void delete(String... userIds) throws WxErrorException;

/**
* 获取用户
* 获取用户.
*
* @param userid 用户id
*/
@@ -85,7 +86,7 @@ public interface WxCpUserService {

/**
* <pre>
* 邀请成员
* 邀请成员.
* 企业可通过接口批量邀请成员使用企业微信,邀请后将通过短信或邮件下发通知。
* 请求方式:POST(HTTPS)
* 请求地址: https://qyapi.weixin.qq.com/cgi-bin/batch/invite?access_token=ACCESS_TOKEN
@@ -113,14 +114,14 @@ public interface WxCpUserService {
* @param userId 企业内的成员id
* @param agentId 非必填,整型,仅用于发红包。其它场景该参数不要填,如微信支付、企业转账、电子发票
* @return map对象,可能包含以下值:
* - openid 企业微信成员userid对应的openid,若有传参agentid,则是针对该agentid的openid。否则是针对企业微信corpid的openid
* - appid 应用的appid,若请求包中不包含agentid则不返回appid。该appid在使用微信红包时会用到
* - openid 企业微信成员userid对应的openid,若有传参agentid,则是针对该agentid的openid。否则是针对企业微信corpid的openid
* - appid 应用的appid,若请求包中不包含agentid则不返回appid。该appid在使用微信红包时会用到
*/
Map<String, String> userId2Openid(String userId, Integer agentId) throws WxErrorException;

/**
* <pre>
* openid转userid
* openid转userid.
*
* 该接口主要应用于使用微信支付、微信红包和企业转账之后的结果查询。
* 开发者需要知道某个结果事件的openid对应企业微信内成员的信息时,可以通过调用该接口进行转换查询。
@@ -134,4 +135,17 @@ public interface WxCpUserService {
* @return userid 该openid在企业微信对应的成员userid
*/
String openid2UserId(String openid) throws WxErrorException;

/**
* 获取外部联系人详情.
* <pre>
* 企业可通过此接口,根据外部联系人的userid,拉取外部联系人详情。权限说明:
* 企业需要使用外部联系人管理secret所获取的accesstoken来调用
* 第三方应用需拥有“企业客户”权限。
* 第三方应用调用时,返回的跟进人follow_user仅包含应用可见范围之内的成员。
* </pre>
*
* @param userId 外部联系人的userid
*/
WxCpUserExternalContactInfo getExternalContact(String userId) throws WxErrorException;
}

+ 36
- 5
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpAgentServiceImpl.java Visa fil

@@ -1,9 +1,17 @@
package me.chanjar.weixin.cp.api.impl;

import com.google.gson.Gson;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.google.gson.reflect.TypeToken;
import me.chanjar.weixin.common.error.WxError;
import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.cp.api.WxCpAgentService;
import me.chanjar.weixin.cp.api.WxCpService;
import me.chanjar.weixin.cp.bean.WxCpAgent;
import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder;

import java.util.List;


/**
@@ -15,6 +23,8 @@ import me.chanjar.weixin.cp.bean.WxCpAgent;
* @author <a href="https://github.com/huansinho">huansinho</a>
*/
public class WxCpAgentServiceImpl implements WxCpAgentService {
private static final JsonParser JSON_PARSER = new JsonParser();

private WxCpService mainService;

public WxCpAgentServiceImpl(WxCpService mainService) {
@@ -23,15 +33,36 @@ public class WxCpAgentServiceImpl implements WxCpAgentService {

@Override
public WxCpAgent get(Integer agentId) throws WxErrorException {

String url = "https://qyapi.weixin.qq.com/cgi-bin/agent/get";
if (agentId != null) {
url += "?agentid=" + agentId;
} else {
if (agentId == null) {
throw new IllegalArgumentException("缺少agentid参数");
}

String url = "https://qyapi.weixin.qq.com/cgi-bin/agent/get?agentid=" + agentId;
String responseContent = this.mainService.get(url, null);
return WxCpAgent.fromJson(responseContent);
}

@Override
public void set(WxCpAgent agentInfo) throws WxErrorException {
String url = "https://qyapi.weixin.qq.com/cgi-bin/agent/set";
String responseContent = this.mainService.post(url, agentInfo.toJson());
JsonObject jsonObject = JSON_PARSER.parse(responseContent).getAsJsonObject();
if (jsonObject.get("errcode").getAsInt() != 0) {
throw new WxErrorException(WxError.fromJson(responseContent));
}
}

@Override
public List<WxCpAgent> list() throws WxErrorException {
String url = "https://qyapi.weixin.qq.com/cgi-bin/agent/list";
String responseContent = this.mainService.get(url, null);
JsonObject jsonObject = JSON_PARSER.parse(responseContent).getAsJsonObject();
if (jsonObject.get("errcode").getAsInt() != 0) {
throw new WxErrorException(WxError.fromJson(responseContent));
}

return WxCpGsonBuilder.create().fromJson(jsonObject.get("agentlist").toString(), new TypeToken<List<WxCpAgent>>() {
}.getType());
}

}

+ 18
- 11
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpMediaServiceImpl.java Visa fil

@@ -1,5 +1,10 @@
package me.chanjar.weixin.cp.api.impl;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.UUID;

import me.chanjar.weixin.common.bean.result.WxMediaUploadResult;
import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.common.util.fs.FileUtils;
@@ -8,17 +13,13 @@ import me.chanjar.weixin.common.util.http.MediaUploadRequestExecutor;
import me.chanjar.weixin.cp.api.WxCpMediaService;
import me.chanjar.weixin.cp.api.WxCpService;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.UUID;

/**
* <pre>
* 媒体管理接口
* 媒体管理接口.
* Created by Binary Wang on 2017-6-25.
* @author <a href="https://github.com/binarywang">Binary Wang</a>
* </pre>
*
* @author <a href="https://github.com/binarywang">Binary Wang</a>
*/
public class WxCpMediaServiceImpl implements WxCpMediaService {
private WxCpService mainService;
@@ -35,16 +36,22 @@ public class WxCpMediaServiceImpl implements WxCpMediaService {

@Override
public WxMediaUploadResult upload(String mediaType, File file) throws WxErrorException {
String url = "https://qyapi.weixin.qq.com/cgi-bin/media/upload?type=" + mediaType;
return this.mainService.execute(MediaUploadRequestExecutor.create(this.mainService.getRequestHttp()), url, file);
return this.mainService.execute(MediaUploadRequestExecutor.create(this.mainService.getRequestHttp()),
MEDIA_UPLOAD_URL + mediaType, file);
}

@Override
public File download(String mediaId) throws WxErrorException {
String url = "https://qyapi.weixin.qq.com/cgi-bin/media/get";
return this.mainService.execute(
BaseMediaDownloadRequestExecutor.create(this.mainService.getRequestHttp(),
this.mainService.getWxCpConfigStorage().getTmpDirFile()),
url, "media_id=" + mediaId);
MEDIA_GET_URL, "media_id=" + mediaId);
}

@Override
public String uploadImg(File file) throws WxErrorException {
final WxMediaUploadResult result = this.mainService
.execute(MediaUploadRequestExecutor.create(this.mainService.getRequestHttp()), IMG_UPLOAD_URL, file);
return result.getUrl();
}
}

+ 26
- 9
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpOAuth2ServiceImpl.java Visa fil

@@ -4,6 +4,7 @@ import com.google.gson.GsonBuilder;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import me.chanjar.weixin.common.api.WxConsts;
import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.common.util.http.URIUtil;
import me.chanjar.weixin.common.util.json.GsonHelper;
@@ -16,6 +17,8 @@ import me.chanjar.weixin.cp.bean.WxCpUserDetail;
*
* Created by Binary Wang on 2017-6-25.
* @author <a href="https://github.com/binarywang">Binary Wang</a>
*
* @author Binary Wang
* </pre>
*/
public class WxCpOAuth2ServiceImpl implements WxCpOAuth2Service {
@@ -35,16 +38,27 @@ public class WxCpOAuth2ServiceImpl implements WxCpOAuth2Service {

@Override
public String buildAuthorizationUrl(String redirectUri, String state) {
String url = "https://open.weixin.qq.com/connect/oauth2/authorize?";
url += "appid=" + this.mainService.getWxCpConfigStorage().getCorpId();
url += "&redirect_uri=" + URIUtil.encodeURIComponent(redirectUri);
url += "&response_type=code";
url += "&scope=snsapi_base";
return this.buildAuthorizationUrl(redirectUri, state, WxConsts.OAuth2Scope.SNSAPI_BASE);
}

@Override
public String buildAuthorizationUrl(String redirectUri, String state, String scope) {
StringBuilder url = new StringBuilder("https://open.weixin.qq.com/connect/oauth2/authorize?");
url.append("appid=").append(this.mainService.getWxCpConfigStorage().getCorpId());
url.append("&redirect_uri=").append(URIUtil.encodeURIComponent(redirectUri));
url.append("&response_type=code");
url.append("&scope=").append(scope);

if (WxConsts.OAuth2Scope.SNSAPI_PRIVATEINFO.equals(scope)
|| WxConsts.OAuth2Scope.SNSAPI_USERINFO.equals(scope)) {
url.append("&agentid=").append(this.mainService.getWxCpConfigStorage().getAgentId());
}

if (state != null) {
url += "&state=" + state;
url.append("&state=").append(state);
}
url += "#wechat_redirect";
return url;
url.append("#wechat_redirect");
return url.toString();
}

@Override
@@ -61,7 +75,10 @@ public class WxCpOAuth2ServiceImpl implements WxCpOAuth2Service {
JsonObject jo = je.getAsJsonObject();
return new String[]{GsonHelper.getString(jo, "UserId"),
GsonHelper.getString(jo, "DeviceId"),
GsonHelper.getString(jo, "OpenId")};
GsonHelper.getString(jo, "OpenId"),
GsonHelper.getString(jo, "user_ticket"),
GsonHelper.getString(jo, "expires_in")
};
}

@Override


+ 16
- 4
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpUserServiceImpl.java Visa fil

@@ -1,18 +1,23 @@
package me.chanjar.weixin.cp.api.impl;

import java.util.List;
import java.util.Map;

import com.google.common.collect.Maps;
import com.google.gson.*;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.google.gson.JsonPrimitive;
import com.google.gson.reflect.TypeToken;
import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.cp.api.WxCpService;
import me.chanjar.weixin.cp.api.WxCpUserService;
import me.chanjar.weixin.cp.bean.WxCpInviteResult;
import me.chanjar.weixin.cp.bean.WxCpUser;
import me.chanjar.weixin.cp.bean.WxCpUserExternalContactInfo;
import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder;

import java.util.List;
import java.util.Map;

/**
* <pre>
* Created by BinaryWang on 2017/6/24.
@@ -178,4 +183,11 @@ public class WxCpUserServiceImpl implements WxCpUserService {
JsonElement tmpJsonElement = new JsonParser().parse(responseContent);
return tmpJsonElement.getAsJsonObject().get("userid").getAsString();
}

@Override
public WxCpUserExternalContactInfo getExternalContact(String userId) throws WxErrorException {
String url = "https://qyapi.weixin.qq.com/cgi-bin/crm/get_external_contact?external_userid=" + userId;
String responseContent = this.mainService.get(url, null);
return WxCpUserExternalContactInfo.fromJson(responseContent);
}
}

+ 21
- 12
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpAgent.java Visa fil

@@ -1,7 +1,10 @@
package me.chanjar.weixin.cp.bean;

import com.google.gson.annotations.SerializedName;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder;

import java.io.Serializable;
@@ -16,16 +19,20 @@ import java.util.List;
* @author <a href="https://github.com/huansinho">huansinho</a>
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class WxCpAgent implements Serializable {
private static final long serialVersionUID = 5002894979081127234L;

@SerializedName("errcode")
private Integer errcode;
private Integer errCode;

@SerializedName("errmsg")
private String errmsg;
private String errMsg;

@SerializedName("agentid")
private Integer agentid;
private Integer agentId;

@SerializedName("name")
private String name;
@@ -33,14 +40,17 @@ public class WxCpAgent implements Serializable {
@SerializedName("square_logo_url")
private String squareLogoUrl;

@SerializedName("logo_mediaid")
private String logoMediaId;

@SerializedName("description")
private String description;

@SerializedName("allow_userinfos")
private Users allowUserinfos;
private Users allowUserInfos;

@SerializedName("allow_partys")
private Partys allowPartys;
private Parties allowParties;

@SerializedName("allow_tags")
private Tags allowTags;
@@ -55,7 +65,7 @@ public class WxCpAgent implements Serializable {
private Integer reportLocationFlag;

@SerializedName("isreportenter")
private Integer isreportenter;
private Integer isReportEnter;

@SerializedName("home_url")
private String homeUrl;
@@ -71,26 +81,25 @@ public class WxCpAgent implements Serializable {
@Data
public static class Users implements Serializable {
@SerializedName("user")
private List<User> user;
private List<User> users;
}


@Data
public class User implements Serializable {
@SerializedName("userid")
private String userid;
private String userId;
}

@Data
public class Partys {
public class Parties {
@SerializedName("partyid")
private List<Integer> partyids = null;
private List<Integer> partyIds = null;
}

@Data
public class Tags {
@SerializedName("tagid")
private List<Integer> tagids = null;
private List<Integer> tagIds = null;
}

}

+ 9
- 7
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpInviteResult.java Visa fil

@@ -1,15 +1,17 @@
package me.chanjar.weixin.cp.bean;

import java.io.Serializable;
import java.util.Collections;
import java.util.List;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;

import com.google.common.base.Splitter;
import com.google.gson.annotations.SerializedName;
import lombok.Data;
import me.chanjar.weixin.common.util.ToStringUtils;
import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder;
import org.apache.commons.lang3.StringUtils;

import java.io.Serializable;
import java.util.Collections;
import java.util.List;

/**
* 邀请成员的结果对象类.
@@ -23,7 +25,7 @@ public class WxCpInviteResult implements Serializable {

@Override
public String toString() {
return ToStringUtils.toSimpleString(this);
return ToStringBuilder.reflectionToString(this, ToStringStyle.JSON_STYLE);
}

public static WxCpInviteResult fromJson(String json) {


+ 9
- 7
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpMessageSendResult.java Visa fil

@@ -1,15 +1,17 @@
package me.chanjar.weixin.cp.bean;

import java.io.Serializable;
import java.util.Collections;
import java.util.List;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;

import com.google.common.base.Splitter;
import com.google.gson.annotations.SerializedName;
import lombok.Data;
import me.chanjar.weixin.common.util.ToStringUtils;
import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder;
import org.apache.commons.lang3.StringUtils;

import java.io.Serializable;
import java.util.Collections;
import java.util.List;

/**
* 消息发送结果对象类.
@@ -23,7 +25,7 @@ public class WxCpMessageSendResult implements Serializable {

@Override
public String toString() {
return ToStringUtils.toSimpleString(this);
return ToStringBuilder.reflectionToString(this, ToStringStyle.JSON_STYLE);
}

public static WxCpMessageSendResult fromJson(String json) {


+ 9
- 7
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTagAddOrRemoveUsersResult.java Visa fil

@@ -1,15 +1,17 @@
package me.chanjar.weixin.cp.bean;

import java.io.Serializable;
import java.util.Collections;
import java.util.List;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;

import com.google.common.base.Splitter;
import com.google.gson.annotations.SerializedName;
import lombok.Data;
import me.chanjar.weixin.common.util.ToStringUtils;
import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder;
import org.apache.commons.lang3.StringUtils;

import java.io.Serializable;
import java.util.Collections;
import java.util.List;

/**
* 为标签添加或移除用户结果对象类.
@@ -23,7 +25,7 @@ public class WxCpTagAddOrRemoveUsersResult implements Serializable {

@Override
public String toString() {
return ToStringUtils.toSimpleString(this);
return ToStringBuilder.reflectionToString(this, ToStringStyle.JSON_STYLE);
}

public static WxCpTagAddOrRemoveUsersResult fromJson(String json) {


+ 50
- 4
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpUser.java Visa fil

@@ -1,13 +1,15 @@
package me.chanjar.weixin.cp.bean;

import lombok.AllArgsConstructor;
import lombok.Data;
import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder;

/**
* 微信用户信息.
*
@@ -24,6 +26,7 @@ public class WxCpUser implements Serializable {
private Gender gender;
private String email;
private String avatar;
private String avatarMediaId;
private Integer status;
private Integer enable;
private Integer isLeader;
@@ -33,6 +36,14 @@ public class WxCpUser implements Serializable {
private String telephone;
private String qrCode;
private Boolean toInvite;
/**
* 成员对外信息.
*/
private List<ExternalAttribute> externalAttrs = new ArrayList<>();

public void addExternalAttr(ExternalAttribute externalAttr) {
this.externalAttrs.add(externalAttr);
}

public void addExtAttr(String name, String value) {
this.extAttrs.add(new Attr(name, value));
@@ -53,4 +64,39 @@ public class WxCpUser implements Serializable {
private String value;
}

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public static class ExternalAttribute {
/**
* 属性类型: 0-本文 1-网页 2-小程序.
*/
private int type;
/**
* 属性名称: 需要先确保在管理端有创建改属性,否则会忽略.
*/
private String name;
/**
* 文本属性内容,长度限制12个UTF8字符.
*/
private String value;
/**
* 网页的url,必须包含http或者https头.
*/
private String url;
/**
* 小程序的展示标题,长度限制12个UTF8字符.
* 或者 网页的展示标题,长度限制12个UTF8字符
*/
private String title;
/**
* 小程序appid,必须是有在本企业安装授权的小程序,否则会被忽略.
*/
private String appid;
/**
* 小程序的页面路径.
*/
private String pagePath;
}
}

+ 31
- 1
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpUserDetail.java Visa fil

@@ -13,12 +13,42 @@ import lombok.Data;
*/
@Data
public class WxCpUserDetail {

/**
* 成员UserID
*/
@SerializedName("userid")
private String userId;

/**
* 成员姓名
*/
private String name;

/**
* 成员手机号,仅在用户同意snsapi_privateinfo授权时返回
*/
private String mobile;

/**
* 性别。0表示未定义,1表示男性,2表示女性
*/
private String gender;

/**
* 成员邮箱,仅在用户同意snsapi_privateinfo授权时返回
*/
private String email;
@SerializedName("qrCode")

/**
* 头像url。注:如果要获取小图将url最后的”/0”改成”/100”即可。仅在用户同意snsapi_privateinfo授权时返回
*/
private String avatar;

/**
* 员工个人二维码(扫描可添加为外部联系人),仅在用户同意snsapi_privateinfo授权时返回
*/
@SerializedName("qr_code")
private String qrCode;

}

+ 126
- 0
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpUserExternalContactInfo.java Visa fil

@@ -0,0 +1,126 @@
package me.chanjar.weixin.cp.bean;

import java.util.List;

import com.google.gson.annotations.SerializedName;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder;

/**
* <pre>
* 外部联系人详情
* Created by Binary Wang on 2018/9/16.
* 参考文档:https://work.weixin.qq.com/api/doc#13878
* </pre>
*
* @author <a href="https://github.com/binarywang">Binary Wang</a>
*/
@Getter
@Setter
public class WxCpUserExternalContactInfo {
@SerializedName("external_contact")
private ExternalContact externalContact;

@SerializedName("follow_user")
private List<FollowedUser> followedUsers;

@Getter
@Setter
public static class ExternalContact {
@SerializedName("external_userid")
private String externalUserId;

@SerializedName("position")
private String position;

@SerializedName("name")
private String name;

@SerializedName("avatar")
private String avatar;

@SerializedName("corp_name")
private String corpName;

@SerializedName("corp_full_name")
private String corpFullName;

@SerializedName("type")
private Integer type;

@SerializedName("gender")
private Integer gender;

@SerializedName("unionid")
private String unionId;

@SerializedName("external_profile")
private ExternalProfile externalProfile;
}

@Setter
@Getter
public static class ExternalProfile {
@SerializedName("external_attr")
private List<ExternalAttribute> externalAttrs;
}

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public static class ExternalAttribute {
@Setter
@Getter
public static class Text {
private String value;
}

@Setter
@Getter
public static class Web {
private String title;
private String url;
}

@Setter
@Getter
public static class MiniProgram {
@SerializedName("pagepath")
private String pagePath;
private String appid;
private String title;
}

private int type;

private String name;

private Text text;

private Web web;

@SerializedName("miniprogram")
private MiniProgram miniProgram;
}

@Setter
@Getter
public static class FollowedUser {
@SerializedName("userid")
private String userId;
private String remark;
private String description;
@SerializedName("createtime")
private Long createTime;
}

public static WxCpUserExternalContactInfo fromJson(String json) {
return WxCpGsonBuilder.create().fromJson(json, WxCpUserExternalContactInfo.class);
}
}

+ 214
- 52
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpXmlMessage.java Visa fil

@@ -1,35 +1,38 @@
package me.chanjar.weixin.cp.bean;

import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;

import com.thoughtworks.xstream.annotations.XStreamAlias;
import com.thoughtworks.xstream.annotations.XStreamConverter;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.common.api.WxConsts;
import me.chanjar.weixin.common.util.ToStringUtils;
import me.chanjar.weixin.common.util.xml.XStreamCDataConverter;
import me.chanjar.weixin.cp.config.WxCpConfigStorage;
import me.chanjar.weixin.cp.util.crypto.WxCpCryptUtil;
import me.chanjar.weixin.cp.util.xml.XStreamTransformer;
import org.apache.commons.io.IOUtils;

import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

/**
* <pre>
* 微信推送过来的消息,也是同步回复给用户的消息,xml格式
* 相关字段的解释看微信开发者文档:
* http://mp.weixin.qq.com/wiki/index.php?title=接收普通消息
* http://mp.weixin.qq.com/wiki/index.php?title=接收事件推送
* http://mp.weixin.qq.com/wiki/index.php?title=接收语音识别结果
* https://work.weixin.qq.com/api/doc#12973
* https://work.weixin.qq.com/api/doc#12974
* </pre>
*
* @author Daniel Qian
*/
@XStreamAlias("xml")
@Data
@Slf4j
@XStreamAlias("xml")
public class WxCpXmlMessage implements Serializable {
private static final long serialVersionUID = -1042994982179476410L;

@@ -51,6 +54,24 @@ public class WxCpXmlMessage implements Serializable {
@XStreamAlias("CreateTime")
private Long createTime;

/**
* <pre>
* 当接受用户消息时,可能会获得以下值:
* {@link WxConsts.XmlMsgType#TEXT}
* {@link WxConsts.XmlMsgType#IMAGE}
* {@link WxConsts.XmlMsgType#VOICE}
* {@link WxConsts.XmlMsgType#VIDEO}
* {@link WxConsts.XmlMsgType#LOCATION}
* {@link WxConsts.XmlMsgType#LINK}
* {@link WxConsts.XmlMsgType#EVENT}
* 当发送消息的时候使用:
* {@link WxConsts.XmlMsgType#TEXT}
* {@link WxConsts.XmlMsgType#IMAGE}
* {@link WxConsts.XmlMsgType#VOICE}
* {@link WxConsts.XmlMsgType#VIDEO}
* {@link WxConsts.XmlMsgType#NEWS}
* </pre>
*/
@XStreamAlias("MsgType")
@XStreamConverter(value = XStreamCDataConverter.class)
private String msgType;
@@ -128,15 +149,172 @@ public class WxCpXmlMessage implements Serializable {
@XStreamConverter(value = XStreamCDataConverter.class)
private String recognition;

/**
* 通讯录变更事件.
* 请参考常量 me.chanjar.weixin.cp.WxCpConsts.ContactChangeType
*/
@XStreamAlias("ChangeType")
@XStreamConverter(value = XStreamCDataConverter.class)
private String changeType;

/**
* 变更信息的成员UserID.
*/
@XStreamAlias("UserID")
@XStreamConverter(value = XStreamCDataConverter.class)
private String userId;

/**
* 新的UserID,变更时推送(userid由系统生成时可更改一次).
*/
@XStreamAlias("NewUserID")
@XStreamConverter(value = XStreamCDataConverter.class)
private String newUserId;

/**
* 成员名称.
* 或者部门名称
*/
@XStreamAlias("Name")
@XStreamConverter(value = XStreamCDataConverter.class)
private String name;

/**
* 成员部门列表.
*/
@XStreamAlias("Department")
@XStreamConverter(value = XStreamCDataConverter.class)
private String department;

/**
* 手机号码.
*/
@XStreamAlias("Mobile")
@XStreamConverter(value = XStreamCDataConverter.class)
private String mobile;

/**
* 职位信息。长度为0~64个字节.
*/
@XStreamAlias("Position")
@XStreamConverter(value = XStreamCDataConverter.class)
private String position;

/**
* 性别,1表示男性,2表示女性.
*/
@XStreamAlias("Gender")
private Integer gender;

/**
* 邮箱.
*/
@XStreamAlias("Email")
@XStreamConverter(value = XStreamCDataConverter.class)
private String email;

/**
* 头像url。注:如果要获取小图将url最后的”/0”改成”/100”即可.
*/
@XStreamAlias("Avatar")
@XStreamConverter(value = XStreamCDataConverter.class)
private String avatar;

/**
* 英文名.
*/
@XStreamAlias("EnglishName")
@XStreamConverter(value = XStreamCDataConverter.class)
private String englishName;

/**
* 上级字段,标识是否为上级。0表示普通成员,1表示上级.
*/
@XStreamAlias("IsLeader")
private Integer isLeader;

/**
* 座机.
*/
@XStreamAlias("Telephone")
@XStreamConverter(value = XStreamCDataConverter.class)
private String telephone;

/**
* 扩展属性.
*/
@XStreamAlias("ExtAttr")
private ExtAttr extAttrs = new ExtAttr();

/**
* 部门Id.
*/
@XStreamAlias("Id")
private Integer id;

/**
* 父部门id.
*/
@XStreamAlias("ParentId")
@XStreamConverter(value = XStreamCDataConverter.class)
private String parentId;

/**
* 部门排序.
*/
@XStreamAlias("Order")
@XStreamConverter(value = XStreamCDataConverter.class)
private String order;

/**
* 标签Id.
*/
@XStreamAlias("TagId")
@XStreamConverter(value = XStreamCDataConverter.class)
private String tagId;

/**
* 标签中新增的成员userid列表,用逗号分隔.
*/
@XStreamAlias("AddUserItems")
@XStreamConverter(value = XStreamCDataConverter.class)
private String addUserItems;

/**
* 标签中删除的成员userid列表,用逗号分隔.
*/
@XStreamAlias("DelUserItems")
@XStreamConverter(value = XStreamCDataConverter.class)
private String delUserItems;

/**
* 标签中新增的部门id列表,用逗号分隔.
*/
@XStreamAlias("AddPartyItems")
@XStreamConverter(value = XStreamCDataConverter.class)
private String addPartyItems;

/**
* 标签中删除的部门id列表,用逗号分隔.
*/
@XStreamAlias("DelPartyItems")
@XStreamConverter(value = XStreamCDataConverter.class)
private String delPartyItems;


///////////////////////////////////////
// 群发消息返回的结果
///////////////////////////////////////
/**
* 群发的结果.
* 多个时间共用字段.
* 1. 群发的结果.
* 2. 通讯录变更事件
* 激活状态:1=已激活 2=已禁用 4=未激活 已激活代表已激活企业微信或已关注微工作台(原企业号).
*/
@XStreamAlias("Status")
@XStreamConverter(value = XStreamCDataConverter.class)
private String status;

/**
* group_id下粉丝数;或者openid_list中的粉丝数.
*/
@@ -181,19 +359,16 @@ public class WxCpXmlMessage implements Serializable {
/**
* 从加密字符串转换.
*/
public static WxCpXmlMessage fromEncryptedXml(
String encryptedXml,
WxCpConfigStorage wxCpConfigStorage,
String timestamp, String nonce, String msgSignature) {
public static WxCpXmlMessage fromEncryptedXml(String encryptedXml, WxCpConfigStorage wxCpConfigStorage,
String timestamp, String nonce, String msgSignature) {
WxCpCryptUtil cryptUtil = new WxCpCryptUtil(wxCpConfigStorage);
String plainText = cryptUtil.decrypt(msgSignature, timestamp, nonce, encryptedXml);
log.debug("解密后的原始xml消息内容:{}", plainText);
return fromXml(plainText);
}

public static WxCpXmlMessage fromEncryptedXml(
InputStream is,
WxCpConfigStorage wxCpConfigStorage,
String timestamp, String nonce, String msgSignature) {
public static WxCpXmlMessage fromEncryptedXml(InputStream is, WxCpConfigStorage wxCpConfigStorage,
String timestamp, String nonce, String msgSignature) {
try {
return fromEncryptedXml(IOUtils.toString(is, "UTF-8"), wxCpConfigStorage, timestamp, nonce, msgSignature);
} catch (IOException e) {
@@ -201,39 +376,9 @@ public class WxCpXmlMessage implements Serializable {
}
}

/**
* <pre>
* 当接受用户消息时,可能会获得以下值:
* {@link WxConsts.XmlMsgType#TEXT}
* {@link WxConsts.XmlMsgType#IMAGE}
* {@link WxConsts.XmlMsgType#VOICE}
* {@link WxConsts.XmlMsgType#VIDEO}
* {@link WxConsts.XmlMsgType#LOCATION}
* {@link WxConsts.XmlMsgType#LINK}
* {@link WxConsts.XmlMsgType#EVENT}
* </pre>
*/
public String getMsgType() {
return this.msgType;
}

/**
* <pre>
* 当发送消息的时候使用:
* {@link WxConsts.XmlMsgType#TEXT}
* {@link WxConsts.XmlMsgType#IMAGE}
* {@link WxConsts.XmlMsgType#VOICE}
* {@link WxConsts.XmlMsgType#VIDEO}
* {@link WxConsts.XmlMsgType#NEWS}
* </pre>
*/
public void setMsgType(String msgType) {
this.msgType = msgType;
}

@Override
public String toString() {
return ToStringUtils.toSimpleString(this);
return ToStringBuilder.reflectionToString(this, ToStringStyle.JSON_STYLE);
}

@Data
@@ -255,6 +400,23 @@ public class WxCpXmlMessage implements Serializable {
private String scanResult;
}

@Data
public static class ExtAttr {
@XStreamAlias("Item")
protected final List<Item> items = new ArrayList<>();

@Data
public static class Item {
@XStreamAlias("Name")
@XStreamConverter(value = XStreamCDataConverter.class)
private String name;

@XStreamAlias("Value")
@XStreamConverter(value = XStreamCDataConverter.class)
private String value;
}
}

@Data
@XStreamAlias("SendPicsInfo")
public static class SendPicsInfo {


+ 13
- 3
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpXmlOutMessage.java Visa fil

@@ -1,16 +1,26 @@
package me.chanjar.weixin.cp.bean;

import java.io.Serializable;

import com.thoughtworks.xstream.annotations.XStreamAlias;
import com.thoughtworks.xstream.annotations.XStreamConverter;
import lombok.Data;
import me.chanjar.weixin.common.util.xml.XStreamCDataConverter;
import me.chanjar.weixin.cp.bean.outxmlbuilder.ImageBuilder;
import me.chanjar.weixin.cp.bean.outxmlbuilder.NewsBuilder;
import me.chanjar.weixin.cp.bean.outxmlbuilder.TextBuilder;
import me.chanjar.weixin.cp.bean.outxmlbuilder.VideoBuilder;
import me.chanjar.weixin.cp.bean.outxmlbuilder.VoiceBuilder;
import me.chanjar.weixin.cp.config.WxCpConfigStorage;
import me.chanjar.weixin.cp.bean.outxmlbuilder.*;
import me.chanjar.weixin.cp.util.crypto.WxCpCryptUtil;
import me.chanjar.weixin.cp.util.xml.XStreamTransformer;

import java.io.Serializable;

/**
* 被动回复消息.
* https://work.weixin.qq.com/api/doc#12975
*
* @author Daniel Qian
*/
@XStreamAlias("xml")
@Data
public abstract class WxCpXmlOutMessage implements Serializable {


+ 6
- 4
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/WxCpInMemoryConfigStorage.java Visa fil

@@ -1,11 +1,13 @@
package me.chanjar.weixin.cp.config;

import java.io.File;

import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;

import me.chanjar.weixin.common.bean.WxAccessToken;
import me.chanjar.weixin.common.util.ToStringUtils;
import me.chanjar.weixin.common.util.http.apache.ApacheHttpClientBuilder;

import java.io.File;

/**
* 基于内存的微信配置provider,在实际生产环境中应该将这些配置持久化
*
@@ -201,7 +203,7 @@ public class WxCpInMemoryConfigStorage implements WxCpConfigStorage {

@Override
public String toString() {
return ToStringUtils.toSimpleString(this);
return ToStringBuilder.reflectionToString(this, ToStringStyle.JSON_STYLE);
}

@Override


+ 12
- 5
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/WxCpJedisConfigStorage.java Visa fil

@@ -43,17 +43,24 @@ public class WxCpJedisConfigStorage implements WxCpConfigStorage {
private volatile File tmpDirFile;
private volatile ApacheHttpClientBuilder apacheHttpClientBuilder;

public WxCpJedisConfigStorage(JedisPool jedisPool) {
this.jedisPool = jedisPool;
}
public WxCpJedisConfigStorage(String host, int port) {
this.jedisPool = new JedisPool(host, port);
jedisPool = new JedisPool(host, port);
}


public WxCpJedisConfigStorage(JedisPoolConfig poolConfig, String host, int port) {
this.jedisPool = new JedisPool(poolConfig, host, port);
jedisPool = new JedisPool(poolConfig, host, port);
}

public WxCpJedisConfigStorage(JedisPoolConfig poolConfig, String host, int port, int timeout, String password) {
jedisPool = new JedisPool(poolConfig, host, port, timeout, password);
}

public WxCpJedisConfigStorage(JedisPoolConfig poolConfig, String host, int port, int timeout, final String password) {
this.jedisPool = new JedisPool(poolConfig, host, port, timeout, password);
public WxCpJedisConfigStorage(JedisPoolConfig poolConfig, String host, int port, int timeout, String password, int database) {
jedisPool = new JedisPool(poolConfig, host, port, timeout, password, database);
}

/**


+ 3
- 0
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/util/json/WxCpGsonBuilder.java Visa fil

@@ -10,6 +10,9 @@ import me.chanjar.weixin.cp.bean.WxCpMessage;
import me.chanjar.weixin.cp.bean.WxCpTag;
import me.chanjar.weixin.cp.bean.WxCpUser;

/**
* @author Daniel Qian
*/
public class WxCpGsonBuilder {

public static final GsonBuilder INSTANCE = new GsonBuilder();


+ 104
- 5
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/util/json/WxCpUserGsonAdapter.java Visa fil

@@ -8,21 +8,30 @@
*/
package me.chanjar.weixin.cp.util.json;

import com.google.gson.*;
import java.lang.reflect.Type;

import com.google.gson.JsonArray;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import com.google.gson.JsonPrimitive;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;
import me.chanjar.weixin.common.util.json.GsonHelper;
import me.chanjar.weixin.cp.bean.Gender;
import me.chanjar.weixin.cp.bean.WxCpUser;

import java.lang.reflect.Type;

/**
* @author Daniel Qian
*/
public class WxCpUserGsonAdapter implements JsonDeserializer<WxCpUser>, JsonSerializer<WxCpUser> {
private static final String EXTERNAL_PROFILE = "external_profile";
private static final String EXTERNAL_ATTR = "external_attr";

@Override
public WxCpUser deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
throws JsonParseException {
public WxCpUser deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
JsonObject o = json.getAsJsonObject();
WxCpUser user = new WxCpUser();

@@ -43,6 +52,7 @@ public class WxCpUserGsonAdapter implements JsonDeserializer<WxCpUser>, JsonSeri
user.setGender(Gender.fromCode(GsonHelper.getString(o, "gender")));
user.setEmail(GsonHelper.getString(o, "email"));
user.setAvatar(GsonHelper.getString(o, "avatar"));
user.setAvatarMediaId(GsonHelper.getString(o, "avatar_mediaid"));
user.setStatus(GsonHelper.getInteger(o, "status"));
user.setEnable(GsonHelper.getInteger(o, "enable"));
user.setIsLeader(GsonHelper.getInteger(o, "isleader"));
@@ -62,6 +72,53 @@ public class WxCpUserGsonAdapter implements JsonDeserializer<WxCpUser>, JsonSeri
user.getExtAttrs().add(attr);
}
}

if (GsonHelper.isNotNull(o.get(EXTERNAL_PROFILE))) {
JsonArray attrJsonElements = o.get(EXTERNAL_PROFILE).getAsJsonObject().get(EXTERNAL_ATTR).getAsJsonArray();
for (JsonElement element : attrJsonElements) {
final Integer type = GsonHelper.getInteger(element.getAsJsonObject(), "type");
final String name = GsonHelper.getString(element.getAsJsonObject(), "name");

switch (type) {
case 0: {
user.getExternalAttrs()
.add(WxCpUser.ExternalAttribute.builder()
.type(type)
.name(name)
.value(GsonHelper.getString(element.getAsJsonObject().get("text").getAsJsonObject(), "value"))
.build()
);
break;
}
case 1: {
final JsonObject web = element.getAsJsonObject().get("web").getAsJsonObject();
user.getExternalAttrs()
.add(WxCpUser.ExternalAttribute.builder()
.type(type)
.name(name)
.url(GsonHelper.getString(web, "url"))
.title(GsonHelper.getString(web, "title"))
.build()
);
break;
}
case 2: {
final JsonObject miniprogram = element.getAsJsonObject().get("miniprogram").getAsJsonObject();
user.getExternalAttrs()
.add(WxCpUser.ExternalAttribute.builder()
.type(type)
.name(name)
.appid(GsonHelper.getString(miniprogram, "appid"))
.pagePath(GsonHelper.getString(miniprogram, "pagepath"))
.title(GsonHelper.getString(miniprogram, "title"))
.build()
);
break;
}
default://ignored
}
}
}
return user;
}

@@ -96,6 +153,9 @@ public class WxCpUserGsonAdapter implements JsonDeserializer<WxCpUser>, JsonSeri
if (user.getAvatar() != null) {
o.addProperty("avatar", user.getAvatar());
}
if (user.getAvatarMediaId() != null) {
o.addProperty("avatar_mediaid", user.getAvatarMediaId());
}
if (user.getStatus() != null) {
o.addProperty("status", user.getStatus());
}
@@ -133,6 +193,45 @@ public class WxCpUserGsonAdapter implements JsonDeserializer<WxCpUser>, JsonSeri
attrsJson.add("attrs", attrsJsonArray);
o.add("extattr", attrsJson);
}

if (user.getExternalAttrs().size() > 0) {
JsonArray attrsJsonArray = new JsonArray();
for (WxCpUser.ExternalAttribute attr : user.getExternalAttrs()) {
JsonObject attrJson = new JsonObject();
attrJson.addProperty("type",attr.getType());
attrJson.addProperty("name", attr.getName());
switch (attr.getType()) {
case 0: {
JsonObject text = new JsonObject();
text.addProperty("value", attr.getValue());
attrJson.add("text", text);
break;
}
case 1: {
JsonObject web = new JsonObject();
web.addProperty("url", attr.getUrl());
web.addProperty("title", attr.getTitle());
attrJson.add("web", web);
break;
}
case 2: {
JsonObject miniprogram = new JsonObject();
miniprogram.addProperty("appid", attr.getAppid());
miniprogram.addProperty("pagepath", attr.getPagePath());
miniprogram.addProperty("title", attr.getTitle());
attrJson.add("miniprogram", miniprogram);
break;
}
default://忽略
}
attrsJsonArray.add(attrJson);
}

JsonObject attrsJson = new JsonObject();
attrsJson.add(EXTERNAL_ATTR, attrsJsonArray);
o.add(EXTERNAL_PROFILE, attrsJson);
}

return o;
}



+ 56
- 18
weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/impl/WxCpAgentServiceImplTest.java Visa fil

@@ -1,23 +1,21 @@
package me.chanjar.weixin.cp.api.impl;

import com.google.inject.Inject;
import me.chanjar.weixin.common.api.WxConsts;
import me.chanjar.weixin.common.bean.menu.WxMenu;
import me.chanjar.weixin.common.bean.menu.WxMenuButton;
import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.cp.api.ApiTestModule;
import me.chanjar.weixin.cp.api.WxCpAgentService;
import me.chanjar.weixin.cp.api.WxCpService;
import me.chanjar.weixin.cp.bean.WxCpAgent;
import me.chanjar.weixin.cp.config.WxCpInMemoryConfigStorage;
import org.mockito.Mock;
import org.testng.Assert;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Guice;
import org.testng.annotations.Test;

import java.util.List;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertNotNull;
import static org.mockito.Mockito.*;


/**
@@ -28,24 +26,64 @@ import static org.mockito.Mockito.*;
*
* @author <a href="https://github.com/huansinho">huansinho</a>
*/
@Guice(modules = ApiTestModule.class)
public class WxCpAgentServiceImplTest {
protected WxCpService wxService = mock(WxCpService.class);
@Inject
private WxCpService wxCpService;

@Test
public void testGet() throws Exception {
String returnJson = "{\"errcode\": 0,\"errmsg\": \"ok\",\"agentid\": 9,\"name\": \"测试应用\",\"square_logo_url\": \"http://wx.qlogo.cn/mmhead/alksjf;lasdjf;lasjfuodiuj3rj2o34j/0\",\"description\": \"这是一个企业号应用\",\"allow_userinfos\": {\"user\": [{\"userid\": \"0009854\"}, {\"userid\": \"1723\"}, {\"userid\": \"5625\"}]},\"allow_partys\": {\"partyid\": [42762742]},\"allow_tags\": {\"tagid\": [23, 22, 35, 19, 32, 125, 133, 46, 150, 38, 183, 9, 7]},\"close\": 0,\"redirect_domain\": \"weixin.com.cn\",\"report_location_flag\": 0,\"isreportenter\": 0,\"home_url\": \"\"}";
when(wxService.get("https://qyapi.weixin.qq.com/cgi-bin/agent/get?agentid=9", null)).thenReturn(returnJson);
when(wxService.getAgentService()).thenReturn(new WxCpAgentServiceImpl(wxService));
final Integer agentId = this.wxCpService.getWxCpConfigStorage().getAgentId();
WxCpAgent wxCpAgent = this.wxCpService.getAgentService().get(agentId);

assertThat(wxCpAgent.getAgentId()).isEqualTo(agentId);

assertThat(wxCpAgent.getAllowUserInfos().getUsers().toArray()).isNotEmpty();
assertThat(wxCpAgent.getAllowParties().getPartyIds().toArray()).isNotEmpty();
assertThat(wxCpAgent.getAllowTags().getTagIds().toArray()).isNotEmpty();
}

@Test
public void testSet() throws WxErrorException {
final Integer agentId = this.wxCpService.getWxCpConfigStorage().getAgentId();

this.wxCpService.getAgentService().set(WxCpAgent.builder()
.description("abcddd")
.logoMediaId("aaaaaaaaaaaaaa")
.agentId(agentId)
.build());
}

@Test
public void testList() throws WxErrorException {
List<WxCpAgent> list = this.wxCpService.getAgentService().list();

assertThat(list).isNotEmpty();

assertThat(list.get(0).getAgentId()).isNotNull();
assertThat(list.get(0).getName()).isNotEmpty();
assertThat(list.get(0).getSquareLogoUrl()).isNotEmpty();
}

public static class MockTest {
private WxCpService wxService = mock(WxCpService.class);

@Test
public void testGet() throws Exception {
String returnJson = "{\"errcode\": 0,\"errmsg\": \"ok\",\"agentid\": 9,\"name\": \"测试应用\",\"square_logo_url\": \"http://wx.qlogo.cn/mmhead/alksjf;lasdjf;lasjfuodiuj3rj2o34j/0\",\"description\": \"这是一个企业号应用\",\"allow_userinfos\": {\"user\": [{\"userid\": \"0009854\"}, {\"userid\": \"1723\"}, {\"userid\": \"5625\"}]},\"allow_partys\": {\"partyid\": [42762742]},\"allow_tags\": {\"tagid\": [23, 22, 35, 19, 32, 125, 133, 46, 150, 38, 183, 9, 7]},\"close\": 0,\"redirect_domain\": \"weixin.com.cn\",\"report_location_flag\": 0,\"isreportenter\": 0,\"home_url\": \"\"}";
when(wxService.get("https://qyapi.weixin.qq.com/cgi-bin/agent/get?agentid=9", null)).thenReturn(returnJson);
when(wxService.getAgentService()).thenReturn(new WxCpAgentServiceImpl(wxService));

WxCpAgentService wxAgentService = this.wxService.getAgentService();
WxCpAgent wxCpAgent = wxAgentService.get(9);

WxCpAgentService wxAgentService = this.wxService.getAgentService();
WxCpAgent wxCpAgent = wxAgentService.get(9);
assertEquals(9, wxCpAgent.getAgentId().intValue());

Assert.assertEquals(9, wxCpAgent.getAgentid().intValue());
assertEquals(new Integer[]{42762742}, wxCpAgent.getAllowParties().getPartyIds().toArray());

Assert.assertEquals(new Integer[]{42762742}, wxCpAgent.getAllowPartys().getPartyids().toArray());
assertEquals(new Integer[]{23, 22, 35, 19, 32, 125, 133, 46, 150, 38, 183, 9, 7}, wxCpAgent.getAllowTags().getTagIds().toArray());

Assert.assertEquals(new Integer[]{23, 22, 35, 19, 32, 125, 133, 46, 150, 38, 183, 9, 7}, wxCpAgent.getAllowTags().getTagids().toArray());
}

}



+ 19
- 12
weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/impl/WxCpMediaServiceImplTest.java Visa fil

@@ -1,5 +1,14 @@
package me.chanjar.weixin.cp.api.impl;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;

import org.testng.annotations.*;

import com.google.inject.Inject;
import me.chanjar.weixin.common.api.WxConsts;
import me.chanjar.weixin.common.bean.result.WxMediaUploadResult;
@@ -7,22 +16,14 @@ import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.cp.api.ApiTestModule;
import me.chanjar.weixin.cp.api.TestConstants;
import me.chanjar.weixin.cp.api.WxCpService;
import org.testng.annotations.*;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;

import static org.assertj.core.api.Assertions.assertThat;
import static org.testng.Assert.*;

/**
* <pre>
*
* Created by Binary Wang on 2017-6-25.
*
* @author <a href="https://github.com/binarywang">Binary Wang</a>
* </pre>
*/
@Guice(modules = ApiTestModule.class)
public class WxCpMediaServiceImplTest {
@@ -35,8 +36,8 @@ public class WxCpMediaServiceImplTest {
public Object[][] mediaData() {
return new Object[][]{
new Object[]{WxConsts.MediaFileType.IMAGE, TestConstants.FILE_JPG, "mm.jpeg"},
new Object[]{WxConsts.MediaFileType.VOICE, TestConstants.FILE_MP3, "mm.mp3"},
new Object[]{WxConsts.MediaFileType.VOICE, TestConstants.FILE_AMR, "mm.amr"},//{"errcode":301017,"errmsg":"voice file only support amr like myvoice.amr"}
new Object[]{WxConsts.MediaFileType.VOICE, TestConstants.FILE_MP3, "mm.mp3"},//{"errcode":301017,"errmsg":"voice file only support amr like myvoice.amr"}
new Object[]{WxConsts.MediaFileType.VOICE, TestConstants.FILE_AMR, "mm.amr"},
new Object[]{WxConsts.MediaFileType.VIDEO, TestConstants.FILE_MP4, "mm.mp4"},
new Object[]{WxConsts.MediaFileType.FILE, TestConstants.FILE_JPG, "mm.jpeg"}
};
@@ -75,4 +76,10 @@ public class WxCpMediaServiceImplTest {
System.out.println(file);
}

@Test
public void testUploadImg() throws WxErrorException {
URL url = ClassLoader.getSystemResource("mm.jpeg");
String res = this.wxService.getMediaService().uploadImg(new File(url.getFile()));
assertThat(res).isNotEmpty();
}
}

+ 17
- 9
weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/impl/WxCpUserServiceImplTest.java Visa fil

@@ -1,22 +1,23 @@
package me.chanjar.weixin.cp.api.impl;

import java.util.List;
import java.util.Map;

import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import org.testng.annotations.*;

import com.google.common.collect.Lists;
import com.google.inject.Inject;
import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.cp.api.ApiTestModule;
import me.chanjar.weixin.cp.api.WxCpService;
import me.chanjar.weixin.cp.bean.Gender;
import me.chanjar.weixin.cp.bean.WxCpInviteResult;
import me.chanjar.weixin.cp.bean.WxCpUser;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import org.testng.annotations.Guice;
import org.testng.annotations.Test;
import me.chanjar.weixin.cp.bean.WxCpUserExternalContactInfo;

import java.util.List;
import java.util.Map;

import static org.testng.Assert.assertNotEquals;
import static org.testng.Assert.assertNotNull;
import static org.testng.Assert.*;

/**
* <pre>
@@ -109,4 +110,11 @@ public class WxCpUserServiceImplTest {
System.out.println(result);
assertNotNull(result);
}

@Test
public void testGetExternalContact() throws WxErrorException {
WxCpUserExternalContactInfo result = this.wxCpService.getUserService().getExternalContact(userId);
System.out.println(result);
assertNotNull(result);
}
}

+ 3
- 3
weixin-java-cp/src/test/java/me/chanjar/weixin/cp/bean/WxCpAgentTest.java Visa fil

@@ -14,11 +14,11 @@ public class WxCpAgentTest {

WxCpAgent wxCpAgent = WxCpAgent.fromJson(json);

Assert.assertEquals(9, wxCpAgent.getAgentid().intValue());
Assert.assertEquals(9, wxCpAgent.getAgentId().intValue());

Assert.assertEquals(new Integer[]{42762742}, wxCpAgent.getAllowPartys().getPartyids().toArray());
Assert.assertEquals(new Integer[]{42762742}, wxCpAgent.getAllowParties().getPartyIds().toArray());

Assert.assertEquals(new Integer[]{23, 22, 35, 19, 32, 125, 133, 46, 150, 38, 183, 9, 7}, wxCpAgent.getAllowTags().getTagids().toArray());
Assert.assertEquals(new Integer[]{23, 22, 35, 19, 32, 125, 133, 46, 150, 38, 183, 9, 7}, wxCpAgent.getAllowTags().getTagIds().toArray());

}



+ 129
- 0
weixin-java-cp/src/test/java/me/chanjar/weixin/cp/bean/WxCpUserExternalContactInfoTest.java Visa fil

@@ -0,0 +1,129 @@
package me.chanjar.weixin.cp.bean;

import java.util.List;

import org.testng.annotations.*;

import static org.assertj.core.api.Assertions.assertThat;

/**
* <pre>
*
* Created by Binary Wang on 2018/9/16.
* </pre>
*
* @author <a href="https://github.com/binarywang">Binary Wang</a>
*/
public class WxCpUserExternalContactInfoTest {

@Test
public void testFromJson() {
final String json = "{\n" +
" \"errcode\": 0,\n" +
" \"errmsg\": \"ok\",\n" +
" \"external_contact\": {\n" +
" \"external_userid\": \"woAJ2GCAAAXtWyujaWJHDDGi0mACH71w\",\n" +
" \"name\": \"李四\",\n" +
" \"position\": \"Mangaer\",\n" +
" \"avatar\": \"http://p.qlogo.cn/bizmail/IcsdgagqefergqerhewSdage/0\",\n" +
" \"corp_name\": \"腾讯\",\n" +
" \"corp_full_name\": \"腾讯科技有限公司\",\n" +
" \"type\": 2,\n" +
" \"gender\": 1,\n" +
" \"unionid\": \"ozynqsulJFCZ2z1aYeS8h-nuasdfR1\",\n" +
" \"external_profile\": {\n" +
" \"external_attr\": [\n" +
" {\n" +
" \"type\": 0,\n" +
" \"name\": \"文本名称\",\n" +
" \"text\": {\n" +
" \"value\": \"文本\"\n" +
" }\n" +
" },\n" +
" {\n" +
" \"type\": 1,\n" +
" \"name\": \"网页名称\",\n" +
" \"web\": {\n" +
" \"url\": \"http://www.test.com\",\n" +
" \"title\": \"标题\"\n" +
" }\n" +
" },\n" +
" {\n" +
" \"type\": 2,\n" +
" \"name\": \"测试app\",\n" +
" \"miniprogram\": {\n" +
" \"appid\": \"wx8bd80126147df384\",\n" +
" \"pagepath\": \"/index\",\n" +
" \"title\": \"my miniprogram\"\n" +
" }\n" +
" }\n" +
" ]\n" +
" }\n" +
" },\n" +
" \"follow_user\": [\n" +
" {\n" +
" \"userid\": \"rocky\",\n" +
" \"remark\": \"李部长\",\n" +
" \"description\": \"对接采购事物\",\n" +
" \"createtime\": 1525779812\n" +
" },\n" +
" {\n" +
" \"userid\": \"tommy\",\n" +
" \"remark\": \"李总\",\n" +
" \"description\": \"采购问题咨询\",\n" +
" \"createtime\": 1525881637\n" +
" }\n" +
" ]\n" +
"}";

final WxCpUserExternalContactInfo contactInfo = WxCpUserExternalContactInfo.fromJson(json);
assertThat(contactInfo).isNotNull();
assertThat(contactInfo.getExternalContact()).isNotNull();

assertThat(contactInfo.getExternalContact().getExternalUserId()).isEqualTo("woAJ2GCAAAXtWyujaWJHDDGi0mACH71w");
assertThat(contactInfo.getExternalContact().getPosition()).isEqualTo("Mangaer");
assertThat(contactInfo.getExternalContact().getAvatar()).isEqualTo("http://p.qlogo.cn/bizmail/IcsdgagqefergqerhewSdage/0");
assertThat(contactInfo.getExternalContact().getCorpName()).isEqualTo("腾讯");
assertThat(contactInfo.getExternalContact().getCorpFullName()).isEqualTo("腾讯科技有限公司");
assertThat(contactInfo.getExternalContact().getType()).isEqualTo(2);
assertThat(contactInfo.getExternalContact().getGender()).isEqualTo(1);
assertThat(contactInfo.getExternalContact().getUnionId()).isEqualTo("ozynqsulJFCZ2z1aYeS8h-nuasdfR1");
assertThat(contactInfo.getExternalContact().getName()).isEqualTo("李四");

assertThat(contactInfo.getExternalContact().getExternalProfile()).isNotNull();

final List<WxCpUserExternalContactInfo.ExternalAttribute> externalAttrs = contactInfo.getExternalContact().getExternalProfile().getExternalAttrs();
assertThat(externalAttrs).isNotEmpty();

final WxCpUserExternalContactInfo.ExternalAttribute externalAttr1 = externalAttrs.get(0);
assertThat(externalAttr1.getType()).isEqualTo(0);
assertThat(externalAttr1.getName()).isEqualTo("文本名称");
assertThat(externalAttr1.getText().getValue()).isEqualTo("文本");

final WxCpUserExternalContactInfo.ExternalAttribute externalAttr2 = externalAttrs.get(1);
assertThat(externalAttr2.getType()).isEqualTo(1);
assertThat(externalAttr2.getName()).isEqualTo("网页名称");
assertThat(externalAttr2.getWeb().getUrl()).isEqualTo("http://www.test.com");
assertThat(externalAttr2.getWeb().getTitle()).isEqualTo("标题");

final WxCpUserExternalContactInfo.ExternalAttribute externalAttr3 = externalAttrs.get(2);
assertThat(externalAttr3.getType()).isEqualTo(2);
assertThat(externalAttr3.getName()).isEqualTo("测试app");
assertThat(externalAttr3.getMiniProgram().getAppid()).isEqualTo("wx8bd80126147df384");
assertThat(externalAttr3.getMiniProgram().getPagePath()).isEqualTo("/index");
assertThat(externalAttr3.getMiniProgram().getTitle()).isEqualTo("my miniprogram");


List<WxCpUserExternalContactInfo.FollowedUser> followedUsers = contactInfo.getFollowedUsers();
assertThat(followedUsers).isNotEmpty();
assertThat(followedUsers.get(0).getUserId()).isEqualTo("rocky");
assertThat(followedUsers.get(0).getRemark()).isEqualTo("李部长");
assertThat(followedUsers.get(0).getDescription()).isEqualTo("对接采购事物");
assertThat(followedUsers.get(0).getCreateTime()).isEqualTo(1525779812);

assertThat(followedUsers.get(1).getUserId()).isEqualTo("tommy");
assertThat(followedUsers.get(1).getRemark()).isEqualTo("李总");
assertThat(followedUsers.get(1).getDescription()).isEqualTo("采购问题咨询");
assertThat(followedUsers.get(1).getCreateTime()).isEqualTo(1525881637);
}
}

+ 30
- 10
weixin-java-cp/src/test/java/me/chanjar/weixin/cp/demo/WxCpDemoServer.java Visa fil

@@ -1,6 +1,16 @@
package me.chanjar.weixin.cp.demo;

import java.io.IOException;
import java.io.InputStream;
import java.util.Map;

import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletHandler;
import org.eclipse.jetty.servlet.ServletHolder;

import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.common.session.WxSessionManager;
import me.chanjar.weixin.cp.WxCpConsts;
import me.chanjar.weixin.cp.api.WxCpService;
import me.chanjar.weixin.cp.api.impl.WxCpServiceImpl;
import me.chanjar.weixin.cp.bean.WxCpXmlMessage;
@@ -9,13 +19,6 @@ import me.chanjar.weixin.cp.bean.WxCpXmlOutTextMessage;
import me.chanjar.weixin.cp.config.WxCpConfigStorage;
import me.chanjar.weixin.cp.message.WxCpMessageHandler;
import me.chanjar.weixin.cp.message.WxCpMessageRouter;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletHandler;
import org.eclipse.jetty.servlet.ServletHolder;

import java.io.IOException;
import java.io.InputStream;
import java.util.Map;

public class WxCpDemoServer {

@@ -78,9 +81,26 @@ public class WxCpDemoServer {
};

wxCpMessageRouter = new WxCpMessageRouter(wxCpService);
wxCpMessageRouter.rule().async(false).content("哈哈") // 拦截内容为“哈哈”的消息
.handler(handler).end().rule().async(false).content("oauth")
.handler(oauth2handler).end();
wxCpMessageRouter.rule()
.async(false)
.content("哈哈") // 拦截内容为“哈哈”的消息
.handler(handler)
.end()
.rule()
.async(false)
.content("oauth")
.handler(oauth2handler)
.end()
.rule()
.event(WxCpConsts.EventType.CHANGE_CONTACT)
.handler(new WxCpMessageHandler() {
@Override
public WxCpXmlOutMessage handle(WxCpXmlMessage wxMessage, Map<String, Object> context, WxCpService wxCpService, WxSessionManager sessionManager) throws WxErrorException {
System.out.println("通讯录发生变更");
return null;
}
})
.end();

}
}


+ 124
- 0
weixin-java-cp/src/test/java/me/chanjar/weixin/cp/util/json/WxCpUserGsonAdapterTest.java Visa fil

@@ -0,0 +1,124 @@
package me.chanjar.weixin.cp.util.json;

import org.testng.annotations.*;

import me.chanjar.weixin.cp.bean.WxCpUser;

import static org.assertj.core.api.Assertions.assertThat;

/**
* <pre>
*
* Created by Binary Wang on 2018/9/16.
* </pre>
*
* @author BinaryWang
*/
public class WxCpUserGsonAdapterTest {

@Test
public void testDeserialize() {
final String userJson = "{\n" +
" \"errcode\": 0,\n" +
" \"errmsg\": \"ok\",\n" +
" \"userid\": \"zhangsan\",\n" +
" \"name\": \"李四\",\n" +
" \"department\": [1, 2],\n" +
" \"order\": [1, 2],\n" +
" \"position\": \"后台工程师\",\n" +
" \"mobile\": \"15913215421\",\n" +
" \"gender\": \"1\",\n" +
" \"email\": \"zhangsan@gzdev.com\",\n" +
" \"isleader\": 1,\n" +
" \"avatar\": \"http://wx.qlogo.cn/mmopen/ajNVdqHZLLA3WJ6DSZUfiakYe37PKnQhBIeOQBO4czqrnZDS79FH5Wm5m4X69TBicnHFlhiafvDwklOpZeXYQQ2icg/0\",\n" +
" \"telephone\": \"020-123456\",\n" +
" \"enable\": 1,\n" +
" \"alias\": \"jackzhang\",\n" +
" \"extattr\": {\n" +
" \"attrs\": [{\n" +
" \"name\": \"爱好\",\n" +
" \"value\": \"旅游\"\n" +
" }, {\n" +
" \"name\": \"卡号\",\n" +
" \"value\": \"1234567234\"\n" +
" }]\n" +
" },\n" +
" \"status\": 1,\n" +
" \"qr_code\": \"https://open.work.weixin.qq.com/wwopen/userQRCode?vcode=xxx\",\n" +
" \"external_profile\": {\n" +
" \"external_attr\": [{\n" +
" \"type\": 0,\n" +
" \"name\": \"文本名称\",\n" +
" \"text\": {\n" +
" \"value\": \"文本\"\n" +
" }\n" +
" },\n" +
" {\n" +
" \"type\": 1,\n" +
" \"name\": \"网页名称\",\n" +
" \"web\": {\n" +
" \"url\": \"http://www.test.com\",\n" +
" \"title\": \"标题\"\n" +
" }\n" +
" },\n" +
" {\n" +
" \"type\": 2,\n" +
" \"name\": \"测试app\",\n" +
" \"miniprogram\": {\n" +
" \"appid\": \"wx8bd80126147df384\",\n" +
" \"pagepath\": \"/index\",\n" +
" \"title\": \"my miniprogram\"\n" +
" }\n" +
" }\n" +
" ]\n" +
" }\n" +
"}";

final WxCpUser user = WxCpUser.fromJson(userJson);
assertThat(user).isNotNull();
assertThat(user.getExternalAttrs()).isNotEmpty();

final WxCpUser.ExternalAttribute externalAttr1 = user.getExternalAttrs().get(0);
assertThat(externalAttr1.getType()).isEqualTo(0);
assertThat(externalAttr1.getName()).isEqualTo("文本名称");
assertThat(externalAttr1.getValue()).isEqualTo("文本");

final WxCpUser.ExternalAttribute externalAttr2 = user.getExternalAttrs().get(1);
assertThat(externalAttr2.getType()).isEqualTo(1);
assertThat(externalAttr2.getName()).isEqualTo("网页名称");
assertThat(externalAttr2.getUrl()).isEqualTo("http://www.test.com");
assertThat(externalAttr2.getTitle()).isEqualTo("标题");

final WxCpUser.ExternalAttribute externalAttr3 = user.getExternalAttrs().get(2);
assertThat(externalAttr3.getType()).isEqualTo(2);
assertThat(externalAttr3.getName()).isEqualTo("测试app");
assertThat(externalAttr3.getAppid()).isEqualTo("wx8bd80126147df384");
assertThat(externalAttr3.getPagePath()).isEqualTo("/index");
assertThat(externalAttr3.getTitle()).isEqualTo("my miniprogram");
}

@Test
public void testSerialize() {
WxCpUser user = new WxCpUser();
user.addExternalAttr(WxCpUser.ExternalAttribute.builder()
.type(0)
.name("文本名称")
.value("文本")
.build());
user.addExternalAttr(WxCpUser.ExternalAttribute.builder()
.type(1)
.name("网页名称")
.url("http://www.test.com")
.title("标题")
.build());
user.addExternalAttr(WxCpUser.ExternalAttribute.builder()
.type(2)
.name("测试app")
.appid("wx8bd80126147df384")
.pagePath("/index")
.title("my miniprogram")
.build());

assertThat(user.toJson()).isEqualTo("{\"external_profile\":{\"external_attr\":[{\"type\":0,\"name\":\"文本名称\",\"text\":{\"value\":\"文本\"}},{\"type\":1,\"name\":\"网页名称\",\"web\":{\"url\":\"http://www.test.com\",\"title\":\"标题\"}},{\"type\":2,\"name\":\"测试app\",\"miniprogram\":{\"appid\":\"wx8bd80126147df384\",\"pagepath\":\"/index\",\"title\":\"my miniprogram\"}}]}}");
}
}

+ 1
- 1
weixin-java-miniapp/pom.xml Visa fil

@@ -7,7 +7,7 @@
<parent>
<groupId>com.github.binarywang</groupId>
<artifactId>weixin-java-parent</artifactId>
<version>3.1.0</version>
<version>3.2.0</version>
</parent>
<artifactId>weixin-java-miniapp</artifactId>
<name>Weixin Java Tools - MiniApp</name>


+ 7
- 3
weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaCodeService.java Visa fil

@@ -1,5 +1,7 @@
package cn.binarywang.wx.miniapp.api;

import java.util.List;

import cn.binarywang.wx.miniapp.bean.code.WxMaCategory;
import cn.binarywang.wx.miniapp.bean.code.WxMaCodeAuditStatus;
import cn.binarywang.wx.miniapp.bean.code.WxMaCodeCommitRequest;
@@ -7,8 +9,6 @@ import cn.binarywang.wx.miniapp.bean.code.WxMaCodeSubmitAuditRequest;
import cn.binarywang.wx.miniapp.bean.code.WxMaCodeVersionDistribution;
import me.chanjar.weixin.common.error.WxErrorException;

import java.util.List;

/**
* 小程序代码管理相关 API(大部分只能是第三方平台调用)
* 文档:https://open.weixin.qq.com/cgi-bin/showdocument?action=dir_list&t=resource/res_list&verify=1&id=open1489140610_Uavc4&token=&lang=zh_CN
@@ -44,11 +44,15 @@ public interface WxMaCodeService {

/**
* 获取体验小程序的体验二维码
* 文档地址:
* https://open.weixin.qq.com/cgi-bin/showdocument?action=dir_list&t=resource/res_list&verify=1&id=open1489140610_Uavc4&token=&lang=zh_CN
*
* @param path 指定体验版二维码跳转到某个具体页面(如果不需要的话,则不需要填path参数,可在路径后以“?参数”方式传入参数)
* 具体的路径加参数需要urlencode(方法内部处理),比如page/index?action=1编码后得到page%2Findex%3Faction%3D1
* @return 二维码 bytes
* @throws WxErrorException 上传失败时抛出,具体错误码请看类注释文档
*/
byte[] getQrCode() throws WxErrorException;
byte[] getQrCode(String path) throws WxErrorException;

/**
* 获取授权小程序帐号的可选类目


+ 48
- 0
weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaJsapiService.java Visa fil

@@ -0,0 +1,48 @@
package cn.binarywang.wx.miniapp.api;

import me.chanjar.weixin.common.bean.WxJsapiSignature;
import me.chanjar.weixin.common.error.WxErrorException;

/**
* <pre>
* jsapi相关接口
* Created by BinaryWang on 2018/8/5.
* </pre>
*
* @author <a href="https://github.com/binarywang">Binary Wang</a>
*/
public interface WxMaJsapiService {
/**
* 获得jsapi_ticket的url
*/
String GET_JSAPI_TICKET_URL = "https://api.weixin.qq.com/cgi-bin/ticket/getticket?type=jsapi";

/**
* 获得jsapi_ticket,不强制刷新jsapi_ticket
*
* @see #getJsapiTicket(boolean)
*/
String getJsapiTicket() throws WxErrorException;

/**
* <pre>
* 获得jsapi_ticket
* 获得时会检查jsapiToken是否过期,如果过期了,那么就刷新一下,否则就什么都不干
*
* 详情请见:http://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421141115&token=&lang=zh_CN
* </pre>
*
* @param forceRefresh 强制刷新
*/
String getJsapiTicket(boolean forceRefresh) throws WxErrorException;

/**
* <pre>
* 创建调用jsapi时所需要的签名
*
* 详情请见:http://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421141115&token=&lang=zh_CN
* </pre>
*/
WxJsapiSignature createJsapiSignature(String url) throws WxErrorException;

}

+ 12
- 0
weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaMsgService.java Visa fil

@@ -2,6 +2,7 @@ package cn.binarywang.wx.miniapp.api;

import cn.binarywang.wx.miniapp.bean.WxMaKefuMessage;
import cn.binarywang.wx.miniapp.bean.WxMaTemplateMessage;
import cn.binarywang.wx.miniapp.bean.WxMaUniformMessage;
import me.chanjar.weixin.common.error.WxErrorException;

/**
@@ -14,6 +15,7 @@ import me.chanjar.weixin.common.error.WxErrorException;
public interface WxMaMsgService {
String KEFU_MESSAGE_SEND_URL = "https://api.weixin.qq.com/cgi-bin/message/custom/send";
String TEMPLATE_MSG_SEND_URL = "https://api.weixin.qq.com/cgi-bin/message/wxopen/template/send";
String UNIFORM_MSG_SEND_URL = "https://api.weixin.qq.com/cgi-bin/message/wxopen/template/uniform_send";

/**
* <pre>
@@ -32,4 +34,14 @@ public interface WxMaMsgService {
* </pre>
*/
void sendTemplateMsg(WxMaTemplateMessage templateMessage) throws WxErrorException;


/**
* <pre>
* 下发小程序和公众号统一的服务消息
* 详情请见: <a href="https://developers.weixin.qq.com/miniprogram/dev/api/open-api/uniform-message/sendUniformMessage.html">下发小程序和公众号统一的服务消息</a>
* 接口url格式:https://api.weixin.qq.com/cgi-bin/message/wxopen/template/uniform_send?access_token=ACCESS_TOKEN
* </pre>
*/
void sendUniformMsg(WxMaUniformMessage uniformMessage) throws WxErrorException;
}

+ 24
- 1
weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaService.java Visa fil

@@ -1,5 +1,7 @@
package cn.binarywang.wx.miniapp.api;

import java.io.File;

import cn.binarywang.wx.miniapp.bean.WxMaJscode2SessionResult;
import cn.binarywang.wx.miniapp.config.WxMaConfig;
import me.chanjar.weixin.common.error.WxErrorException;
@@ -17,12 +19,25 @@ public interface WxMaService {
String GET_ACCESS_TOKEN_URL = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s";

String JSCODE_TO_SESSION_URL = "https://api.weixin.qq.com/sns/jscode2session";

String IMG_SEC_CHECK_URL = "https://api.weixin.qq.com/wxa/img_sec_check";

/**
* <pre>
* 校验一张图片是否含有违法违规内容.
* 应用场景举例:1)图片智能鉴黄:涉及拍照的工具类应用(如美拍,识图类应用)用户拍照上传检测;电商类商品上架图片检测;媒体类用户文章里的图片检测等;2)敏感人脸识别:用户头像;媒体类用户文章里的图片检测;社交类用户上传的图片检测等。频率限制:单个 appId 调用上限为 1000 次/分钟,100,000 次/天
* 详情请见: https://developers.weixin.qq.com/miniprogram/dev/api/imgSecCheck.html
* </pre>
*/
boolean imgSecCheck(File file) throws WxErrorException;

/**
* 获取登录后的session信息
* 获取登录后的session信息.
*
* @param jsCode 登录时获取的 code
*/
WxMaJscode2SessionResult jsCode2SessionInfo(String jsCode) throws WxErrorException;

/**
* <pre>
* 验证消息的确来自微信服务器.
@@ -131,6 +146,7 @@ public interface WxMaService {

/**
* 返回模板配置相关接口方法的实现类对象, 以方便调用其各个接口.
*
* @return WxMaTemplateService
*/
WxMaTemplateService getTemplateService();
@@ -149,6 +165,13 @@ public interface WxMaService {
*/
WxMaCodeService getCodeService();

/**
* 返回jsapi操作相关的 API服务类对象
*
* @return WxMaJsapiService
*/
WxMaJsapiService getJsapiService();

/**
* 小程序修改服务器地址、成员管理 API
*


+ 12
- 0
weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaUserService.java Visa fil

@@ -5,6 +5,8 @@ import cn.binarywang.wx.miniapp.bean.WxMaPhoneNumberInfo;
import cn.binarywang.wx.miniapp.bean.WxMaUserInfo;
import me.chanjar.weixin.common.error.WxErrorException;

import java.util.Map;

/**
* 用户信息相关操作接口.
*
@@ -28,6 +30,16 @@ public interface WxMaUserService {
*/
WxMaUserInfo getUserInfo(String sessionKey, String encryptedData, String ivStr);

/**
* 上报用户数据后台接口.
* <p>小游戏可以通过本接口上报key-value数据到用户的CloudStorage。</p>
* 文档参考https://developers.weixin.qq.com/minigame/dev/document/open-api/data/setUserStorage.html
* @param kvMap 要上报的数据
* @param sessionKey 通过wx.login 获得的登录态
* @param openid
*/
void setUserStorage(Map<String, String> kvMap, String sessionKey, String openid) throws WxErrorException;

/**
* 解密用户手机号信息.
*


+ 18
- 8
weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaCodeServiceImpl.java Visa fil

@@ -1,5 +1,15 @@
package cn.binarywang.wx.miniapp.api.impl;

import java.io.File;
import java.io.IOException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;

import org.apache.commons.lang3.StringUtils;

import cn.binarywang.wx.miniapp.api.WxMaCodeService;
import cn.binarywang.wx.miniapp.api.WxMaService;
import cn.binarywang.wx.miniapp.bean.code.WxMaCategory;
@@ -17,12 +27,6 @@ import me.chanjar.weixin.common.util.http.BaseMediaDownloadRequestExecutor;
import me.chanjar.weixin.common.util.http.RequestExecutor;
import me.chanjar.weixin.common.util.json.GsonHelper;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;

/**
* @author <a href="https://github.com/charmingoh">Charming</a>
* @since 2018-04-26 20:00
@@ -41,13 +45,19 @@ public class WxMaCodeServiceImpl implements WxMaCodeService {
}

@Override
public byte[] getQrCode() throws WxErrorException {
public byte[] getQrCode(String path) throws WxErrorException {
String appId = this.wxMaService.getWxMaConfig().getAppid();
Path qrCodeFilePath = null;
try {
RequestExecutor<File, String> executor = BaseMediaDownloadRequestExecutor
.create(this.wxMaService.getRequestHttp(), Files.createTempDirectory("weixin-java-tools-ma-" + appId).toFile());
qrCodeFilePath = this.wxMaService.execute(executor, GET_QRCODE_URL, null).toPath();

final StringBuilder url = new StringBuilder(GET_QRCODE_URL);
if (StringUtils.isNotBlank(path)) {
url.append("?path=").append(URLEncoder.encode(path, StandardCharsets.UTF_8.name()));
}

qrCodeFilePath = this.wxMaService.execute(executor, url.toString(), null).toPath();
return Files.readAllBytes(qrCodeFilePath);
} catch (IOException e) {
throw new WxErrorException(WxError.builder().errorMsg(e.getMessage()).build(), e);


+ 75
- 0
weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaJsapiServiceImpl.java Visa fil

@@ -0,0 +1,75 @@
package cn.binarywang.wx.miniapp.api.impl;

import cn.binarywang.wx.miniapp.api.WxMaJsapiService;
import cn.binarywang.wx.miniapp.api.WxMaService;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import me.chanjar.weixin.common.bean.WxJsapiSignature;
import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.common.util.RandomUtils;
import me.chanjar.weixin.common.util.crypto.SHA1;

import java.util.concurrent.locks.Lock;

/**
* <pre>
* Created by BinaryWang on 2018/8/5.
* </pre>
*
* @author <a href="https://github.com/binarywang">Binary Wang</a>
*/
public class WxMaJsapiServiceImpl implements WxMaJsapiService {
private static final JsonParser JSON_PARSER = new JsonParser();

private WxMaService wxMaService;

public WxMaJsapiServiceImpl(WxMaService wxMaService) {
this.wxMaService = wxMaService;
}

@Override
public String getJsapiTicket() throws WxErrorException {
return getJsapiTicket(false);
}

@Override
public String getJsapiTicket(boolean forceRefresh) throws WxErrorException {
Lock lock = this.wxMaService.getWxMaConfig().getJsapiTicketLock();
try {
lock.lock();
if (forceRefresh) {
this.wxMaService.getWxMaConfig().expireJsapiTicket();
}

if (this.wxMaService.getWxMaConfig().isJsapiTicketExpired()) {
String responseContent = this.wxMaService.get(GET_JSAPI_TICKET_URL, null);
JsonElement tmpJsonElement = JSON_PARSER.parse(responseContent);
JsonObject tmpJsonObject = tmpJsonElement.getAsJsonObject();
String jsapiTicket = tmpJsonObject.get("ticket").getAsString();
int expiresInSeconds = tmpJsonObject.get("expires_in").getAsInt();
this.wxMaService.getWxMaConfig().updateJsapiTicket(jsapiTicket, expiresInSeconds);
}
} finally {
lock.unlock();
}
return this.wxMaService.getWxMaConfig().getJsapiTicket();
}

@Override
public WxJsapiSignature createJsapiSignature(String url) throws WxErrorException {
long timestamp = System.currentTimeMillis() / 1000;
String randomStr = RandomUtils.getRandomStr();
String jsapiTicket = getJsapiTicket(false);
String signature = SHA1.genWithAmple("jsapi_ticket=" + jsapiTicket,
"noncestr=" + randomStr, "timestamp=" + timestamp, "url=" + url);
return WxJsapiSignature
.builder()
.appId(this.wxMaService.getWxMaConfig().getAppid())
.timestamp(timestamp)
.nonceStr(randomStr)
.url(url)
.signature(signature)
.build();
}
}

+ 10
- 0
weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaMsgServiceImpl.java Visa fil

@@ -4,6 +4,7 @@ import cn.binarywang.wx.miniapp.api.WxMaMsgService;
import cn.binarywang.wx.miniapp.api.WxMaService;
import cn.binarywang.wx.miniapp.bean.WxMaKefuMessage;
import cn.binarywang.wx.miniapp.bean.WxMaTemplateMessage;
import cn.binarywang.wx.miniapp.bean.WxMaUniformMessage;
import cn.binarywang.wx.miniapp.constant.WxMaConstants;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
@@ -36,4 +37,13 @@ public class WxMaMsgServiceImpl implements WxMaMsgService {
}
}

@Override
public void sendUniformMsg(WxMaUniformMessage uniformMessage) throws WxErrorException {
String responseContent = this.wxMaService.post(UNIFORM_MSG_SEND_URL, uniformMessage.toJson());
JsonObject jsonObject = JSON_PARSER.parse(responseContent).getAsJsonObject();
if (jsonObject.get(WxMaConstants.ERRCODE).getAsInt() != 0) {
throw new WxErrorException(WxError.fromJson(responseContent));
}
}

}

+ 34
- 13
weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaServiceImpl.java Visa fil

@@ -1,7 +1,23 @@
package cn.binarywang.wx.miniapp.api.impl;

import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.Lock;

import org.apache.http.HttpHost;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.BasicResponseHandler;
import org.apache.http.impl.client.CloseableHttpClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import cn.binarywang.wx.miniapp.api.WxMaAnalysisService;
import cn.binarywang.wx.miniapp.api.WxMaCodeService;
import cn.binarywang.wx.miniapp.api.WxMaJsapiService;
import cn.binarywang.wx.miniapp.api.WxMaMediaService;
import cn.binarywang.wx.miniapp.api.WxMaMsgService;
import cn.binarywang.wx.miniapp.api.WxMaQrcodeService;
@@ -12,31 +28,21 @@ import cn.binarywang.wx.miniapp.api.WxMaUserService;
import cn.binarywang.wx.miniapp.bean.WxMaJscode2SessionResult;
import cn.binarywang.wx.miniapp.config.WxMaConfig;
import com.google.common.base.Joiner;
import com.google.gson.Gson;
import me.chanjar.weixin.common.bean.WxAccessToken;
import me.chanjar.weixin.common.bean.result.WxMediaUploadResult;
import me.chanjar.weixin.common.error.WxError;
import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.common.util.DataUtils;
import me.chanjar.weixin.common.util.crypto.SHA1;
import me.chanjar.weixin.common.util.http.HttpType;
import me.chanjar.weixin.common.util.http.MediaUploadRequestExecutor;
import me.chanjar.weixin.common.util.http.RequestExecutor;
import me.chanjar.weixin.common.util.http.RequestHttp;
import me.chanjar.weixin.common.util.http.SimpleGetRequestExecutor;
import me.chanjar.weixin.common.util.http.SimplePostRequestExecutor;
import me.chanjar.weixin.common.util.http.apache.ApacheHttpClientBuilder;
import me.chanjar.weixin.common.util.http.apache.DefaultApacheHttpClientBuilder;
import org.apache.http.HttpHost;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.BasicResponseHandler;
import org.apache.http.impl.client.CloseableHttpClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.Lock;

import static cn.binarywang.wx.miniapp.constant.WxMaConstants.ErrorCode.*;

@@ -58,10 +64,13 @@ public class WxMaServiceImpl implements WxMaService, RequestHttp<CloseableHttpCl
private WxMaAnalysisService analysisService = new WxMaAnalysisServiceImpl(this);
private WxMaCodeService codeService = new WxMaCodeServiceImpl(this);
private WxMaSettingService settingService = new WxMaSettingServiceImpl(this);
private WxMaJsapiService jsapiService = new WxMaJsapiServiceImpl(this);

private int retrySleepMillis = 1000;
private int maxRetryTimes = 5;

protected static final Gson GSON = new Gson();

@Override
public CloseableHttpClient getRequestHttpClient() {
return httpClient;
@@ -140,6 +149,13 @@ public class WxMaServiceImpl implements WxMaService, RequestHttp<CloseableHttpCl
return this.getWxMaConfig().getAccessToken();
}

@Override
public boolean imgSecCheck(File file) throws WxErrorException {
//这里只是借用MediaUploadRequestExecutor,并不使用其返回值WxMediaUploadResult
WxMediaUploadResult result = this.execute(MediaUploadRequestExecutor.create(this.getRequestHttp()), IMG_SEC_CHECK_URL, file);
return result != null;
}

@Override
public WxMaJscode2SessionResult jsCode2SessionInfo(String jsCode) throws WxErrorException {
final WxMaConfig config = getWxMaConfig();
@@ -310,6 +326,11 @@ public class WxMaServiceImpl implements WxMaService, RequestHttp<CloseableHttpCl
return this.codeService;
}

@Override
public WxMaJsapiService getJsapiService() {
return this.jsapiService;
}

@Override
public WxMaSettingService getSettingService() {
return this.settingService;


+ 37
- 2
weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaUserServiceImpl.java Visa fil

@@ -1,14 +1,25 @@
package cn.binarywang.wx.miniapp.api.impl;

import org.apache.commons.codec.digest.DigestUtils;

import cn.binarywang.wx.miniapp.api.WxMaService;
import cn.binarywang.wx.miniapp.api.WxMaUserService;
import cn.binarywang.wx.miniapp.bean.WxMaJscode2SessionResult;
import cn.binarywang.wx.miniapp.bean.WxMaPhoneNumberInfo;
import cn.binarywang.wx.miniapp.bean.WxMaUserInfo;
import cn.binarywang.wx.miniapp.config.WxMaConfig;
import cn.binarywang.wx.miniapp.util.crypt.WxMaCryptUtils;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import me.chanjar.weixin.common.error.WxError;
import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.common.util.SignUtils;
import org.apache.commons.codec.binary.Hex;
import org.apache.commons.codec.digest.DigestUtils;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Map;

/**
* @author <a href="https://github.com/binarywang">Binary Wang</a>
@@ -30,6 +41,30 @@ public class WxMaUserServiceImpl implements WxMaUserService {
return WxMaUserInfo.fromJson(WxMaCryptUtils.decrypt(sessionKey, encryptedData, ivStr));
}

@Override
public void setUserStorage(Map<String, String> kvMap, String sessionKey, String openid) throws WxErrorException {
final WxMaConfig config = this.service.getWxMaConfig();
JsonObject param = new JsonObject();
JsonArray array = new JsonArray();
for (Map.Entry<String, String> e : kvMap.entrySet()) {
JsonObject jsonObject = new JsonObject();
jsonObject.addProperty("key", e.getKey());
jsonObject.addProperty("value", e.getValue());
array.add(jsonObject);
}
param.add("kv_list", array);
String params = param.toString();
String signature = SignUtils.createHmacSha256Sign(params, sessionKey);
String url = String.format("https://api.weixin.qq.com/wxa/set_user_storage" +
"?appid=%s&signature=%s&openid=%s&sig_method=%s",
config.getAppid(), signature, openid, "hmac_sha256");
String result = this.service.post(url, params);
WxError error = WxError.fromJson(result);
if (error.getErrorCode() != 0) {
throw new WxErrorException(error);
}
}

@Override
public WxMaPhoneNumberInfo getPhoneNoInfo(String sessionKey, String encryptedData, String ivStr) {
return WxMaPhoneNumberInfo.fromJson(WxMaCryptUtils.decrypt(sessionKey, encryptedData, ivStr));


+ 10
- 8
weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/WxMaMessage.java Visa fil

@@ -1,5 +1,14 @@
package cn.binarywang.wx.miniapp.bean;

import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
import java.nio.charset.StandardCharsets;

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;

import cn.binarywang.wx.miniapp.config.WxMaConfig;
import cn.binarywang.wx.miniapp.util.crypt.WxMaCryptUtils;
import cn.binarywang.wx.miniapp.util.json.WxMaGsonBuilder;
@@ -8,14 +17,7 @@ import com.google.gson.annotations.SerializedName;
import com.thoughtworks.xstream.annotations.XStreamAlias;
import com.thoughtworks.xstream.annotations.XStreamConverter;
import lombok.Data;
import me.chanjar.weixin.common.util.ToStringUtils;
import me.chanjar.weixin.common.util.xml.XStreamCDataConverter;
import org.apache.commons.io.IOUtils;

import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
import java.nio.charset.StandardCharsets;

/**
* @author <a href="https://github.com/binarywang">Binary Wang</a>
@@ -166,7 +168,7 @@ public class WxMaMessage implements Serializable {

@Override
public String toString() {
return ToStringUtils.toSimpleString(this);
return ToStringBuilder.reflectionToString(this, ToStringStyle.JSON_STYLE);
}

public String toJson() {


+ 32
- 0
weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/WxMaTemplateData.java Visa fil

@@ -0,0 +1,32 @@
package cn.binarywang.wx.miniapp.bean;

import lombok.Data;
import lombok.NoArgsConstructor;

/**
* <pre>
*
* Created by Binary Wang on 2018/9/23.
* </pre>
*
* @author <a href="https://github.com/binarywang">Binary Wang</a>
*/
@Data
@NoArgsConstructor
public class WxMaTemplateData {
private String name;
private String value;
private String color;

public WxMaTemplateData(String name, String value) {
this.name = name;
this.value = value;
}

public WxMaTemplateData(String name, String value, String color) {
this.name = name;
this.value = value;
this.color = color;
}

}

+ 9
- 24
weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/WxMaTemplateMessage.java Visa fil

@@ -1,12 +1,16 @@
package cn.binarywang.wx.miniapp.bean;

import cn.binarywang.wx.miniapp.util.json.WxMaGsonBuilder;
import lombok.*;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

import cn.binarywang.wx.miniapp.util.json.WxMaGsonBuilder;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

/**
* 模板消息.
* 参考 https://mp.weixin.qq.com/debug/wxadoc/dev/api/notice.html#接口说明 模板消息部分
@@ -69,7 +73,7 @@ public class WxMaTemplateMessage implements Serializable {
* 描述: 模板内容,不填则下发空模板
* </pre>
*/
private List<Data> data;
private List<WxMaTemplateData> data;

/**
* 模板内容字体的颜色,不填默认黑色.
@@ -91,7 +95,7 @@ public class WxMaTemplateMessage implements Serializable {
*/
private String emphasisKeyword;

public WxMaTemplateMessage addData(Data datum) {
public WxMaTemplateMessage addData(WxMaTemplateData datum) {
if (this.data == null) {
this.data = new ArrayList<>();
}
@@ -104,23 +108,4 @@ public class WxMaTemplateMessage implements Serializable {
return WxMaGsonBuilder.create().toJson(this);
}

@lombok.Data
public static class Data {
private String name;
private String value;
private String color;

public Data(String name, String value) {
this.name = name;
this.value = value;
}

public Data(String name, String value, String color) {
this.name = name;
this.value = value;
this.color = color;
}

}

}

+ 108
- 0
weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/WxMaUniformMessage.java Visa fil

@@ -0,0 +1,108 @@
package cn.binarywang.wx.miniapp.bean;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

import cn.binarywang.wx.miniapp.util.json.WxMaGsonBuilder;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

/**
* 模板消息.
* 参考 https://mp.weixin.qq.com/debug/wxadoc/dev/api/notice.html#接口说明 模板消息部分
*
* @author <a href="https://github.com/binarywang">Binary Wang</a>
*/
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class WxMaUniformMessage implements Serializable {
private static final long serialVersionUID = 5063374783759519418L;

/**
* 是否发送公众号模版消息,否则发送小程序模版消息.
*/
private boolean isMpTemplateMsg;

/**
* 用户openid.
* 可以是小程序的openid,也可以是mp_template_msg.appid对应的公众号的openid
*/
private String toUser;

/**
* 公众号appid,要求与小程序有绑定且同主体.
*/
private String appid;

/**
* 公众号或小程序模板ID.
*/
private String templateId;

/**
* 公众号模板消息所要跳转的url.
*/
private String url;

/**
* 小程序页面路径.
*/
private String page;

/**
* 小程序模板消息formid.
*/
private String formId;

/**
* 公众号模板消息所要跳转的小程序,小程序的必须与公众号具有绑定关系.
*/
private MiniProgram miniProgram;

/**
* 小程序模板数据.
*/
private List<WxMaTemplateData> data;

/**
* 模板需要放大的关键词,不填则默认无放大.
*/
private String emphasisKeyword;

public WxMaUniformMessage addData(WxMaTemplateData datum) {
if (this.data == null) {
this.data = new ArrayList<>();
}
this.data.add(datum);

return this;
}

public String toJson() {
return WxMaGsonBuilder.create().toJson(this);
}

@Data
@NoArgsConstructor
@AllArgsConstructor
public static class MiniProgram implements Serializable {
private static final long serialVersionUID = -7945254706501974849L;

private String appid;
private String pagePath;

/**
* 是否使用path,否则使用pagepath.
* 加入此字段是基于微信官方接口变化多端的考虑
*/
private boolean usePath = false;
}
}

+ 40
- 34
weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/code/WxMaCodeExtConfig.java Visa fil

@@ -1,14 +1,15 @@
package cn.binarywang.wx.miniapp.bean.code;

import lombok.Builder;
import lombok.Data;

import java.io.Serializable;
import java.util.List;
import java.util.Map;

import lombok.Builder;
import lombok.Data;

/**
* 上传代码需要用到的第三方自定义的配置
* 详细文档,参考:https://developers.weixin.qq.com/miniprogram/dev/framework/config.html
*
* @author <a href="https://github.com/charmingoh">Charming</a>
* @since 2018-04-26 19:44
@@ -18,34 +19,34 @@ import java.util.Map;
public class WxMaCodeExtConfig implements Serializable {
private static final long serialVersionUID = -7666911367458178753L;
/**
* 配置 ext.json 是否生效
* 配置 ext.json 是否生效.
* 必填:是
*/
private boolean extEnable;
/**
* 配置 extAppid
* 配置 extAppid.
* 必填:是
*/
private String extAppid;
/**
* 开发自定义的数据字段
* 开发自定义的数据字段.
* 必填:否
*/
private Object ext;
/**
* 单独设置每个页面的 json
* 单独设置每个页面的 json.
* 必填:否
* key: page 名称,如 pages/logs/logs
* value: page 配置
*/
private Map<String, PageConfig> extPages;
/**
* 是否直接提交到待审核列表
* 是否直接提交到待审核列表.
* 必填:否
*/
private Boolean directCommit;
/**
* 设置页面路径(同 app.json 相同的字段,填写会覆盖 app.json)
* 设置页面路径(同 app.json 相同的字段,填写会覆盖 app.json).
* 必填:否
*/
private List<String> pages;
@@ -64,6 +65,11 @@ public class WxMaCodeExtConfig implements Serializable {
* 必填:否
*/
private Boolean debug;
/**
* 底部 tab 栏的表现.
* 必填:否
*/
private TabBar tabBar;

/**
* page.json 配置,页面配置
@@ -73,125 +79,125 @@ public class WxMaCodeExtConfig implements Serializable {
@Builder
public static class PageConfig {
/**
* 导航栏背景颜色,如"#000000" HexColor
* 导航栏背景颜色,如"#000000" HexColor.
* 默认:#000000
*/
private String navigationBarBackgroundColor;
/**
* 导航栏标题颜色,仅支持 black/white
* 导航栏标题颜色,仅支持 black/white.
* 默认:white
*/
private String navigationBarTextStyle;
/**
* 导航栏标题文字内容
* 导航栏标题文字内容.
*/
private String navigationBarTitleText;
/**
* 窗口的背景色 HexColor
* 窗口的背景色 HexColor.
* 默认:#ffffff
*/
private String backgroundColor;
/**
* 下拉背景字体、loading 图的样式,仅支持 dark/light
* 下拉背景字体、loading 图的样式,仅支持 dark/light.
* 默认:dark
*/
private String backgroundTextStyle;
/**
* 是否开启下拉刷新,详见页面相关事件处理函数
* 是否开启下拉刷新,详见页面相关事件处理函数.
* 默认:false
*/
private String enablePullDownRefresh;
/**
* 设置为 true 则页面整体不能上下滚动;只在 page.json 中有效,无法在 app.json 中设置该项
* 设置为 true 则页面整体不能上下滚动;只在 page.json 中有效,无法在 app.json 中设置该项.
* 默认:false
*/
private Boolean disableScroll;
/**
* 页面上拉触底事件触发时距页面底部距离,单位为px
* 页面上拉触底事件触发时距页面底部距离,单位为px.
* 默认:50
*/
private Integer onReachBottomDistance;
}

/**
* tabBar 配置
* tabBar 配置.
*/
@Data
@Builder
public static class TabBar {
/**
* HexColor, tab 上的文字默认颜色
* HexColor, tab 上的文字默认颜色.
*/
private String color;
/**
* HexColor, tab 上的文字选中时的颜色
* HexColor, tab 上的文字选中时的颜色.
*/
private String selectedColor;
/**
* HexColor, tab 的背景色
* HexColor, tab 的背景色.
*/
private String backgroundColor;
/**
* tabbar 上边框的颜色,仅支持 black/white
* tabbar 上边框的颜色,仅支持 black/white.
*/
private String borderStyle;
/**
* tab 的列表,最少2个、最多5个 tab
* tab 的列表,最少2个、最多5个 tab.
*/
private List<Item> list;
/**
* 可选值 bottom、top
* 可选值 bottom、top.
*/
private String position;

/**
* list item
* list item.
*/
@Data
@Builder
public static class Item {
/**
* 页面路径,必须在 pages 中先定义
* 页面路径,必须在 pages 中先定义.
*/
private String pagePath;
/**
* tab 上按钮文字
* tab 上按钮文字.
*/
private String text;
/**
* 图片路径,icon 大小限制为40kb,建议尺寸为 81px * 81px,当 postion 为 top 时,此参数无效,不支持网络图片
* 图片路径,icon 大小限制为40kb,建议尺寸为 81px * 81px,当 postion 为 top 时,此参数无效,不支持网络图片.
*/
private String iconPath;
/**
* 选中时的图片路径,icon 大小限制为40kb,建议尺寸为 81px * 81px ,当 postion 为 top 时,此参数无效
* 选中时的图片路径,icon 大小限制为40kb,建议尺寸为 81px * 81px ,当 postion 为 top 时,此参数无效.
*/
private String selectedIconPath;
}
}

/**
* 各种网络请求的超时时间
* 各种网络请求的超时时间.
*/
@Data
@Builder
public static class NetworkTimeout {
/**
* wx.request的超时时间,单位毫秒,默认为:60000
* wx.request的超时时间,单位毫秒,默认为:60000.
* 必填:否
*/
private Integer request;
/**
* wx.connectSocket的超时时间,单位毫秒,默认为:60000
* wx.connectSocket的超时时间,单位毫秒,默认为:60000.
* 必填:否
*/
private Integer connectSocket;
/**
* wx.uploadFile的超时时间,单位毫秒,默认为:60000
* wx.uploadFile的超时时间,单位毫秒,默认为:60000.
* 必填:否
*/
private Integer uploadFile;
/**
* wx.downloadFile的超时时间,单位毫秒,默认为:60000
* wx.downloadFile的超时时间,单位毫秒,默认为:60000.
* 必填:否
*/
private Integer downloadFile;


+ 19
- 0
weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/config/WxMaConfig.java Visa fil

@@ -38,6 +38,25 @@ public interface WxMaConfig {
*/
void updateAccessToken(String accessToken, int expiresInSeconds);

String getJsapiTicket();

Lock getJsapiTicketLock();

boolean isJsapiTicketExpired();

/**
* 强制将jsapi ticket过期掉
*/
void expireJsapiTicket();

/**
* 应该是线程安全的
*
* @param jsapiTicket 新的jsapi ticket值
* @param expiresInSeconds 过期时间,以秒为单位
*/
void updateJsapiTicket(String jsapiTicket, int expiresInSeconds);

String getAppid();

String getSecret();


+ 38
- 5
weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/config/WxMaInMemoryConfig.java Visa fil

@@ -1,13 +1,15 @@
package cn.binarywang.wx.miniapp.config;

import me.chanjar.weixin.common.bean.WxAccessToken;
import me.chanjar.weixin.common.util.ToStringUtils;
import me.chanjar.weixin.common.util.http.apache.ApacheHttpClientBuilder;

import java.io.File;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;

import me.chanjar.weixin.common.bean.WxAccessToken;
import me.chanjar.weixin.common.util.http.apache.ApacheHttpClientBuilder;

/**
* 基于内存的微信配置provider,在实际生产环境中应该将这些配置持久化
*
@@ -27,7 +29,11 @@ public class WxMaInMemoryConfig implements WxMaConfig {
protected volatile String httpProxyUsername;
protected volatile String httpProxyPassword;

protected volatile String jsapiTicket;
protected volatile long jsapiTicketExpiresTime;

protected Lock accessTokenLock = new ReentrantLock();
protected Lock jsapiTicketLock = new ReentrantLock();

/**
* 临时文件目录
@@ -70,6 +76,33 @@ public class WxMaInMemoryConfig implements WxMaConfig {
this.expiresTime = System.currentTimeMillis() + (expiresInSeconds - 200) * 1000L;
}

@Override
public String getJsapiTicket() {
return this.jsapiTicket;
}

@Override
public Lock getJsapiTicketLock() {
return this.jsapiTicketLock;
}

@Override
public boolean isJsapiTicketExpired() {
return System.currentTimeMillis() > this.jsapiTicketExpiresTime;
}

@Override
public void expireJsapiTicket() {
this.jsapiTicketExpiresTime = 0;
}

@Override
public void updateJsapiTicket(String jsapiTicket, int expiresInSeconds) {
this.jsapiTicket = jsapiTicket;
// 预留200秒的时间
this.jsapiTicketExpiresTime = System.currentTimeMillis() + (expiresInSeconds - 200) * 1000L;
}

@Override
public void expireAccessToken() {
this.expiresTime = 0;
@@ -158,7 +191,7 @@ public class WxMaInMemoryConfig implements WxMaConfig {

@Override
public String toString() {
return ToStringUtils.toSimpleString(this);
return ToStringBuilder.reflectionToString(this, ToStringStyle.JSON_STYLE);
}

@Override


+ 2
- 0
weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/util/json/WxMaGsonBuilder.java Visa fil

@@ -1,6 +1,7 @@
package cn.binarywang.wx.miniapp.util.json;

import cn.binarywang.wx.miniapp.bean.WxMaTemplateMessage;
import cn.binarywang.wx.miniapp.bean.WxMaUniformMessage;
import cn.binarywang.wx.miniapp.bean.analysis.WxMaRetainInfo;
import cn.binarywang.wx.miniapp.bean.analysis.WxMaUserPortrait;
import cn.binarywang.wx.miniapp.bean.analysis.WxMaVisitDistribution;
@@ -18,6 +19,7 @@ public class WxMaGsonBuilder {
static {
INSTANCE.disableHtmlEscaping();
INSTANCE.registerTypeAdapter(WxMaTemplateMessage.class, new WxMaTemplateMessageGsonAdapter());
INSTANCE.registerTypeAdapter(WxMaUniformMessage.class, new WxMaUniformMessageGsonAdapter());
INSTANCE.registerTypeAdapter(WxMaCodeCommitRequest.class, new WxMaCodeCommitRequestGsonAdapter());
INSTANCE.registerTypeAdapter(WxMaCodeVersionDistribution.class, new WxMaCodeVersionDistributionGsonAdapter());
INSTANCE.registerTypeAdapter(WxMaVisitDistribution.class, new WxMaVisitDistributionGsonAdapter());


+ 4
- 7
weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/util/json/WxMaTemplateMessageGsonAdapter.java Visa fil

@@ -1,13 +1,14 @@
package cn.binarywang.wx.miniapp.util.json;

import java.lang.reflect.Type;

import cn.binarywang.wx.miniapp.bean.WxMaTemplateData;
import cn.binarywang.wx.miniapp.bean.WxMaTemplateMessage;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;

import java.lang.reflect.Type;

/**
* @author <a href="https://github.com/binarywang">Binary Wang</a>
*/
@@ -26,10 +27,6 @@ public class WxMaTemplateMessageGsonAdapter implements JsonSerializer<WxMaTempla
messageJson.addProperty("form_id", message.getFormId());
}

if (message.getPage() != null) {
messageJson.addProperty("page", message.getPage());
}

if (message.getColor() != null) {
messageJson.addProperty("color", message.getColor());
}
@@ -45,7 +42,7 @@ public class WxMaTemplateMessageGsonAdapter implements JsonSerializer<WxMaTempla
return messageJson;
}

for (WxMaTemplateMessage.Data datum : message.getData()) {
for (WxMaTemplateData datum : message.getData()) {
JsonObject dataJson = new JsonObject();
dataJson.addProperty("value", datum.getValue());
if (datum.getColor() != null) {


+ 98
- 0
weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/util/json/WxMaUniformMessageGsonAdapter.java Visa fil

@@ -0,0 +1,98 @@
package cn.binarywang.wx.miniapp.util.json;

import java.lang.reflect.Type;

import cn.binarywang.wx.miniapp.bean.WxMaTemplateData;
import cn.binarywang.wx.miniapp.bean.WxMaUniformMessage;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;

/**
* @author <a href="https://github.com/binarywang">Binary Wang</a>
*/
public class WxMaUniformMessageGsonAdapter implements JsonSerializer<WxMaUniformMessage> {

@Override
public JsonElement serialize(WxMaUniformMessage message, Type typeOfSrc, JsonSerializationContext context) {
JsonObject messageJson = new JsonObject();
messageJson.addProperty("touser", message.getToUser());
if (message.isMpTemplateMsg()) {
JsonObject msg = new JsonObject();
if (message.getAppid() != null) {
msg.addProperty("appid", message.getAppid());
}

msg.addProperty("template_id", message.getTemplateId());

if (message.getUrl() != null) {
msg.addProperty("url", message.getUrl());
}

final WxMaUniformMessage.MiniProgram miniProgram = message.getMiniProgram();
if (miniProgram != null) {
JsonObject miniProgramJson = new JsonObject();
miniProgramJson.addProperty("appid", miniProgram.getAppid());
if (miniProgram.isUsePath()) {
miniProgramJson.addProperty("path", miniProgram.getPagePath());
} else {
miniProgramJson.addProperty("pagepath", miniProgram.getPagePath());
}
msg.add("miniprogram", miniProgramJson);
}

if (message.getData() != null) {
JsonObject data = new JsonObject();
for (WxMaTemplateData templateData : message.getData()) {
JsonObject dataJson = new JsonObject();
dataJson.addProperty("value", templateData.getValue());
if (templateData.getColor() != null) {
dataJson.addProperty("color", templateData.getColor());
}
data.add(templateData.getName(), dataJson);
}
msg.add("data", data);
}


messageJson.add("mp_template_msg", msg);
return messageJson;
}

//小程序模版消息
JsonObject msg = new JsonObject();
msg.addProperty("template_id", message.getTemplateId());

if (message.getPage() != null) {
msg.addProperty("page", message.getPage());
}

if (message.getFormId() != null) {
msg.addProperty("form_id", message.getFormId());
}

JsonObject data = new JsonObject();
msg.add("data", data);

if (message.getData() != null) {
for (WxMaTemplateData templateData : message.getData()) {
JsonObject dataJson = new JsonObject();
dataJson.addProperty("value", templateData.getValue());
if (templateData.getColor() != null) {
dataJson.addProperty("color", templateData.getColor());
}
data.add(templateData.getName(), dataJson);
}
}

if (message.getEmphasisKeyword() != null) {
msg.addProperty("emphasis_keyword", message.getEmphasisKeyword());
}

messageJson.add("weapp_template_msg", msg);

return messageJson;
}

}

+ 9
- 10
weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/api/impl/WxMaCodeServiceImplTest.java Visa fil

@@ -1,5 +1,12 @@
package cn.binarywang.wx.miniapp.api.impl;

import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.testng.annotations.*;

import cn.binarywang.wx.miniapp.api.WxMaCodeService;
import cn.binarywang.wx.miniapp.api.WxMaService;
import cn.binarywang.wx.miniapp.bean.code.WxMaCategory;
@@ -11,16 +18,8 @@ import cn.binarywang.wx.miniapp.bean.code.WxMaCodeVersionDistribution;
import cn.binarywang.wx.miniapp.config.WxMaConfig;
import cn.binarywang.wx.miniapp.test.ApiTestModule;
import com.google.inject.Inject;
import org.testng.annotations.Guice;
import org.testng.annotations.Test;

import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import static org.testng.Assert.assertNotNull;
import static org.testng.Assert.assertTrue;
import static org.testng.Assert.*;

/**
* @author <a href="https://github.com/charmingoh">Charming</a>
@@ -77,7 +76,7 @@ public class WxMaCodeServiceImplTest {

@Test
public void testGetQrCode() throws Exception {
byte[] qrCode = wxService.getCodeService().getQrCode();
byte[] qrCode = wxService.getCodeService().getQrCode(null);
assertTrue(qrCode.length > 0);
}



+ 45
- 0
weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/api/impl/WxMaJsapiServiceImplTest.java Visa fil

@@ -0,0 +1,45 @@
package cn.binarywang.wx.miniapp.api.impl;

import cn.binarywang.wx.miniapp.api.WxMaService;
import cn.binarywang.wx.miniapp.config.WxMaConfig;
import cn.binarywang.wx.miniapp.test.ApiTestModule;
import com.google.inject.Inject;
import me.chanjar.weixin.common.bean.WxJsapiSignature;
import me.chanjar.weixin.common.error.WxErrorException;
import org.testng.annotations.Guice;
import org.testng.annotations.Test;

import static org.assertj.core.api.Assertions.assertThat;

/**
* <pre>
* Created by BinaryWang on 2018/8/5.
* </pre>
*
* @author <a href="https://github.com/binarywang">Binary Wang</a>
*/
@Test
@Guice(modules = ApiTestModule.class)
public class WxMaJsapiServiceImplTest {
@Inject
private WxMaService wxService;
@Inject
private WxMaConfig wxMaConfig;

@Test
public void testGetJsapiTicket() throws WxErrorException {
assertThat(this.wxService.getJsapiService().getJsapiTicket()).isNotBlank();
}

@Test
public void testGetJsapiTicket1() throws WxErrorException {
assertThat(this.wxService.getJsapiService().getJsapiTicket(true)).isNotBlank();
}

@Test
public void testCreateJsapiSignature() throws WxErrorException {
final WxJsapiSignature jsapiSignature = this.wxService.getJsapiService().createJsapiSignature("http://www.qq.com");
System.out.println(jsapiSignature);
assertThat(jsapiSignature).isNotNull();
}
}

+ 30
- 9
weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/api/impl/WxMaMsgServiceImplTest.java Visa fil

@@ -1,17 +1,20 @@
package cn.binarywang.wx.miniapp.api.impl;

import java.text.SimpleDateFormat;
import java.util.Date;

import org.testng.annotations.*;

import cn.binarywang.wx.miniapp.api.WxMaService;
import cn.binarywang.wx.miniapp.bean.WxMaKefuMessage;
import cn.binarywang.wx.miniapp.bean.WxMaTemplateData;
import cn.binarywang.wx.miniapp.bean.WxMaTemplateMessage;
import cn.binarywang.wx.miniapp.bean.WxMaUniformMessage;
import cn.binarywang.wx.miniapp.test.ApiTestModule;
import cn.binarywang.wx.miniapp.test.TestConfig;
import com.google.common.collect.Lists;
import com.google.inject.Inject;
import me.chanjar.weixin.common.error.WxErrorException;
import org.testng.annotations.*;

import java.text.SimpleDateFormat;
import java.util.Date;

/**
* 测试消息相关接口
@@ -45,15 +48,33 @@ public class WxMaMsgServiceImplTest {
.formId("FORMID")
.page("index")
.data(Lists.newArrayList(
new WxMaTemplateMessage.Data("keyword1", "339208499", "#173177"),
new WxMaTemplateMessage.Data("keyword2", dateFormat.format(new Date()), "#173177"),
new WxMaTemplateMessage.Data("keyword3", "粤海喜来登酒店", "#173177"),
new WxMaTemplateMessage.Data("keyword4", "广州市天河区天河路208号", "#173177")))
new WxMaTemplateData("keyword1", "339208499", "#173177"),
new WxMaTemplateData("keyword2", dateFormat.format(new Date()), "#173177"),
new WxMaTemplateData("keyword3", "粤海喜来登酒店", "#173177"),
new WxMaTemplateData("keyword4", "广州市天河区天河路208号", "#173177")))
.templateId(config.getTemplateId())
.emphasisKeyword("keyword1.DATA")
.build();
//templateMessage.addData( new WxMaTemplateMessage.Data("keyword1", "339208499", "#173177"));
//templateMessage.addData( new WxMaTemplateData("keyword1", "339208499", "#173177"));
this.wxService.getMsgService().sendTemplateMsg(templateMessage);
}

@Test
public void testSendUniformMsg() throws WxErrorException {
TestConfig config = (TestConfig) this.wxService.getWxMaConfig();
WxMaUniformMessage message = WxMaUniformMessage.builder()
.isMpTemplateMsg(false)
.toUser(config.getOpenid())
.page("page/page/index")
.templateId("TEMPLATE_ID")
.formId("FORMID")
.emphasisKeyword("keyword1.DATA")
.build();
message.addData(new WxMaTemplateData("keyword1", "339208499"))
.addData(new WxMaTemplateData("keyword2", "2015年01月05日 12:30"))
.addData(new WxMaTemplateData("keyword3", "腾讯微信总部"))
.addData(new WxMaTemplateData("keyword4", "广州市海珠区新港中路397号"));

this.wxService.getMsgService().sendUniformMsg(message);
}
}

+ 11
- 5
weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/api/impl/WxMaServiceImplTest.java Visa fil

@@ -1,16 +1,17 @@
package cn.binarywang.wx.miniapp.api.impl;

import java.io.File;

import org.apache.commons.lang3.StringUtils;
import org.testng.annotations.*;

import cn.binarywang.wx.miniapp.api.WxMaService;
import cn.binarywang.wx.miniapp.config.WxMaConfig;
import cn.binarywang.wx.miniapp.test.ApiTestModule;
import com.google.inject.Inject;
import me.chanjar.weixin.common.error.WxErrorException;
import org.apache.commons.lang3.StringUtils;
import org.testng.annotations.Guice;
import org.testng.annotations.Test;

import static org.testng.Assert.assertNotEquals;
import static org.testng.Assert.assertTrue;
import static org.testng.Assert.*;

/**
* @author <a href="https://github.com/binarywang">Binary Wang</a>
@@ -32,4 +33,9 @@ public class WxMaServiceImplTest {
assertTrue(StringUtils.isNotBlank(after));
}

@Test
public void testImgSecCheck() throws WxErrorException {
boolean result = this.wxService.imgSecCheck(new File(ClassLoader.getSystemResource("tmp.png").getFile()));
assertTrue(result);
}
}

+ 19
- 0
weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/api/impl/WxMaUserServiceImplTest.java Visa fil

@@ -1,5 +1,9 @@
package cn.binarywang.wx.miniapp.api.impl;

import cn.binarywang.wx.miniapp.test.TestConfig;
import com.google.common.collect.ImmutableMap;
import jdk.nashorn.internal.ir.annotations.Immutable;
import me.chanjar.weixin.common.error.WxErrorException;
import org.testng.annotations.*;

import cn.binarywang.wx.miniapp.api.WxMaService;
@@ -8,6 +12,8 @@ import cn.binarywang.wx.miniapp.bean.WxMaUserInfo;
import cn.binarywang.wx.miniapp.test.ApiTestModule;
import com.google.inject.Inject;

import javax.management.ImmutableDescriptor;

import static org.testng.Assert.*;

/**
@@ -54,4 +60,17 @@ public class WxMaUserServiceImplTest {
assertNotNull(phoneNoInfo);
System.out.println(phoneNoInfo.toString());
}

@Test
public void testGetSessionInfo() {
}

/**
* TODO 测试数据有问题,需要替换为正确的数据
*/
@Test
public void testSetUserStorage() throws WxErrorException {
this.wxService.getUserService().setUserStorage(ImmutableMap.of("1","2"),
"r7BXXKkLb8qrSNn05n0qiA",((TestConfig)this.wxService.getWxMaConfig()).getOpenid());
}
}

+ 7
- 6
weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/bean/WxMaTemplateMessageTest.java Visa fil

@@ -1,9 +1,10 @@
package cn.binarywang.wx.miniapp.bean;

import org.testng.annotations.*;

import com.google.common.collect.Lists;
import org.testng.annotations.Test;

import static org.testng.AssertJUnit.assertEquals;
import static org.testng.AssertJUnit.*;

/**
* @author <a href="https://github.com/binarywang">Binary Wang</a>
@@ -17,10 +18,10 @@ public class WxMaTemplateMessageTest {
.formId("FORMID")
.page("index")
.data(Lists.newArrayList(
new WxMaTemplateMessage.Data("keyword1", "339208499", "#173177"),
new WxMaTemplateMessage.Data("keyword2", "2015年01月05日12:30", "#173177"),
new WxMaTemplateMessage.Data("keyword3", "粤海喜来登酒店", "#173177"),
new WxMaTemplateMessage.Data("keyword4", "广州市天河区天河路208号", "#173177")))
new WxMaTemplateData("keyword1", "339208499", "#173177"),
new WxMaTemplateData("keyword2", "2015年01月05日12:30", "#173177"),
new WxMaTemplateData("keyword3", "粤海喜来登酒店", "#173177"),
new WxMaTemplateData("keyword4", "广州市天河区天河路208号", "#173177")))
.templateId("TEMPLATE_ID")
.emphasisKeyword("keyword1.DATA")
.build();


+ 12
- 10
weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/demo/WxMaDemoServer.java Visa fil

@@ -1,9 +1,20 @@
package cn.binarywang.wx.miniapp.demo;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.Map;
import java.util.concurrent.locks.ReentrantLock;

import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletHandler;
import org.eclipse.jetty.servlet.ServletHolder;

import cn.binarywang.wx.miniapp.api.WxMaService;
import cn.binarywang.wx.miniapp.api.impl.WxMaServiceImpl;
import cn.binarywang.wx.miniapp.bean.WxMaKefuMessage;
import cn.binarywang.wx.miniapp.bean.WxMaMessage;
import cn.binarywang.wx.miniapp.bean.WxMaTemplateData;
import cn.binarywang.wx.miniapp.bean.WxMaTemplateMessage;
import cn.binarywang.wx.miniapp.config.WxMaConfig;
import cn.binarywang.wx.miniapp.constant.WxMaConstants;
@@ -14,15 +25,6 @@ import com.google.common.collect.Lists;
import me.chanjar.weixin.common.bean.result.WxMediaUploadResult;
import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.common.session.WxSessionManager;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletHandler;
import org.eclipse.jetty.servlet.ServletHolder;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.Map;
import java.util.concurrent.locks.ReentrantLock;

/**
* @author <a href="https://github.com/binarywang">Binary Wang</a>
@@ -96,7 +98,7 @@ public class WxMaDemoServer {
throws WxErrorException {
service.getMsgService().sendTemplateMsg(WxMaTemplateMessage.builder()
.templateId(templateId).data(Lists.newArrayList(
new WxMaTemplateMessage.Data("keyword1", "339208499", "#173177")))
new WxMaTemplateData("keyword1", "339208499", "#173177")))
.toUser(wxMessage.getFromUser())
.formId("自己替换可用的formid")
.build());


+ 1
- 0
weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/test/TestConfig.java Visa fil

@@ -54,6 +54,7 @@ public class TestConfig extends WxMaInMemoryConfig {
this.templateId = templateId;
}

@Override
public void setAccessTokenLock(Lock lock) {
super.accessTokenLock = lock;
}


+ 112
- 0
weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/util/json/WxMaUniformMessageGsonAdapterTest.java Visa fil

@@ -0,0 +1,112 @@
package cn.binarywang.wx.miniapp.util.json;

import org.testng.annotations.*;

import cn.binarywang.wx.miniapp.bean.WxMaTemplateData;
import cn.binarywang.wx.miniapp.bean.WxMaUniformMessage;
import com.google.gson.JsonParser;

import static org.assertj.core.api.Assertions.assertThat;

/**
* <pre>
*
* Created by Binary Wang on 2018/9/23.
* </pre>
*
* @author <a href="https://github.com/binarywang">Binary Wang</a>
*/
public class WxMaUniformMessageGsonAdapterTest {

@Test
public void testSerialize_mp() {
WxMaUniformMessage message = WxMaUniformMessage.builder()
.isMpTemplateMsg(true)
.toUser("OPENID")
.appid("APPID")
.templateId("TEMPLATE_ID")
.url("http://weixin.qq.com/download")
.miniProgram(new WxMaUniformMessage.MiniProgram("xiaochengxuappid12345", "index?foo=bar", false))
.build();
message.addData(new WxMaTemplateData("first", "恭喜你购买成功!", "#173177"))
.addData(new WxMaTemplateData("keyword1", "巧克力", "#173177"))
.addData(new WxMaTemplateData("keyword2", "39.8元", "#173177"))
.addData(new WxMaTemplateData("keyword3", "2014年9月22日", "#173177"))
.addData(new WxMaTemplateData("remark", "欢迎再次购买!", "#173177"));

assertThat(message.toJson()).isEqualTo(new JsonParser().parse("{\n" +
" \"touser\":\"OPENID\",\n" +
" \"mp_template_msg\":{\n" +
" \"appid\":\"APPID\",\n" +
" \"template_id\":\"TEMPLATE_ID\",\n" +
" \"url\":\"http://weixin.qq.com/download\",\n" +
" \"miniprogram\":{\n" +
" \"appid\":\"xiaochengxuappid12345\",\n" +
" \"pagepath\":\"index?foo=bar\"\n" +
" },\n" +
" \"data\":{\n" +
" \"first\":{\n" +
" \"value\":\"恭喜你购买成功!\",\n" +
" \"color\":\"#173177\"\n" +
" },\n" +
" \"keyword1\":{\n" +
" \"value\":\"巧克力\",\n" +
" \"color\":\"#173177\"\n" +
" },\n" +
" \"keyword2\":{\n" +
" \"value\":\"39.8元\",\n" +
" \"color\":\"#173177\"\n" +
" },\n" +
" \"keyword3\":{\n" +
" \"value\":\"2014年9月22日\",\n" +
" \"color\":\"#173177\"\n" +
" },\n" +
" \"remark\":{\n" +
" \"value\":\"欢迎再次购买!\",\n" +
" \"color\":\"#173177\"\n" +
" }\n" +
" }\n" +
" }\n" +
"}").getAsJsonObject().toString());
}

@Test
public void testSerialize_ma() {
WxMaUniformMessage message = WxMaUniformMessage.builder()
.isMpTemplateMsg(false)
.toUser("OPENID")
.page("page/page/index")
.templateId("TEMPLATE_ID")
.formId("FORMID")
.emphasisKeyword("keyword1.DATA")
.build();
message.addData(new WxMaTemplateData("keyword1", "339208499"))
.addData(new WxMaTemplateData("keyword2", "2015年01月05日 12:30"))
.addData(new WxMaTemplateData("keyword3", "腾讯微信总部"))
.addData(new WxMaTemplateData("keyword4", "广州市海珠区新港中路397号"));

assertThat(message.toJson()).isEqualTo(new JsonParser().parse("{\n" +
" \"touser\":\"OPENID\",\n" +
" \"weapp_template_msg\":{\n" +
" \"template_id\":\"TEMPLATE_ID\",\n" +
" \"page\":\"page/page/index\",\n" +
" \"form_id\":\"FORMID\",\n" +
" \"data\":{\n" +
" \"keyword1\":{\n" +
" \"value\":\"339208499\"\n" +
" },\n" +
" \"keyword2\":{\n" +
" \"value\":\"2015年01月05日 12:30\"\n" +
" },\n" +
" \"keyword3\":{\n" +
" \"value\":\"腾讯微信总部\"\n" +
" },\n" +
" \"keyword4\":{\n" +
" \"value\":\"广州市海珠区新港中路397号\"\n" +
" }\n" +
" },\n" +
" \"emphasis_keyword\":\"keyword1.DATA\"\n" +
" }\n" +
"}").getAsJsonObject().toString());
}
}

+ 1
- 1
weixin-java-mp/pom.xml Visa fil

@@ -7,7 +7,7 @@
<parent>
<groupId>com.github.binarywang</groupId>
<artifactId>weixin-java-parent</artifactId>
<version>3.1.0</version>
<version>3.2.0</version>
</parent>
<artifactId>weixin-java-mp</artifactId>
<name>Weixin Java Tools - MP</name>


+ 61
- 1
weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpCardService.java Visa fil

@@ -2,12 +2,16 @@ package me.chanjar.weixin.mp.api;

import me.chanjar.weixin.common.bean.WxCardApiSignature;
import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.mp.bean.card.WxMpCardLandingPageCreateRequest;
import me.chanjar.weixin.mp.bean.card.WxMpCardLandingPageCreateResult;
import me.chanjar.weixin.mp.bean.card.WxMpCardQrcodeCreateResult;
import me.chanjar.weixin.mp.bean.result.WxMpCardResult;

/**
* 卡券相关接口
*
* @author YuJian(mgcnrx11@hotmail.com) on 01/11/2016
* @author YuJian(mgcnrx11 @ hotmail.com) on 01/11/2016
* @author yuanqixun 2018-08-29
*/
public interface WxMpCardService {
String CARD_GET = "https://api.weixin.qq.com/card/get";
@@ -16,6 +20,13 @@ public interface WxMpCardService {
String CARD_CODE_GET = "https://api.weixin.qq.com/card/code/get";
String CARD_CODE_CONSUME = "https://api.weixin.qq.com/card/code/consume";
String CARD_CODE_MARK = "https://api.weixin.qq.com/card/code/mark";
String CARD_TEST_WHITELIST = "https://api.weixin.qq.com/card/testwhitelist/set";
String CARD_QRCODE_CREAET = "https://api.weixin.qq.com/card/qrcode/create";
String CARD_LANDING_PAGE_CREAET = "https://api.weixin.qq.com/card/landingpage/create";
/**
* 将用户的卡券设置为失效状态
*/
String CARD_CODE_UNAVAILABLE = "https://api.weixin.qq.com/card/code/unavailable";

/**
* 得到WxMpService
@@ -122,4 +133,53 @@ public interface WxMpCardService {
* <br> 可由 com.google.gson.JsonParser#parse 等方法直接取JSON串中的某个字段。
*/
String getCardDetail(String cardId) throws WxErrorException;

/**
* 添加测试白名单
*
* @param openid 用户的openid
* @return
*/
String addTestWhiteList(String openid) throws WxErrorException;

/**
* 创建卡券二维码
*
* @param cardId 卡券编号
* @param outerStr 二维码标识
* @return WxMpCardQrcodeCreateResult
*/
WxMpCardQrcodeCreateResult createQrcodeCard(String cardId, String outerStr) throws WxErrorException;

/**
* 创建卡券二维码
*
* @param cardId 卡券编号
* @param outerStr 二维码标识
* @param expiresIn 失效时间,单位秒,不填默认365天
* @return WxMpCardQrcodeCreateResult
*/
WxMpCardQrcodeCreateResult createQrcodeCard(String cardId, String outerStr, int expiresIn) throws WxErrorException;

/**
* 创建卡券货架
*
* @param createRequest 货架创建参数
* @return
* @throws WxErrorException
*/
WxMpCardLandingPageCreateResult createLandingPage(WxMpCardLandingPageCreateRequest createRequest) throws WxErrorException;

/**
* 将用户的卡券设置为失效状态
* 详见:https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1451025272&anchor=9
*
* @param cardId 卡券编号
* @param code 用户会员卡号
* @param reason 设置为失效的原因
* @return
* @throws WxErrorException
*/
String unavailableCardCode(String cardId, String code, String reason) throws WxErrorException;

}

+ 7
- 5
weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpInMemoryConfigStorage.java Visa fil

@@ -1,13 +1,15 @@
package me.chanjar.weixin.mp.api;

import me.chanjar.weixin.common.bean.WxAccessToken;
import me.chanjar.weixin.common.util.ToStringUtils;
import me.chanjar.weixin.common.util.http.apache.ApacheHttpClientBuilder;

import java.io.File;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;

import me.chanjar.weixin.common.bean.WxAccessToken;
import me.chanjar.weixin.common.util.http.apache.ApacheHttpClientBuilder;

/**
* 基于内存的微信配置provider,在实际生产环境中应该将这些配置持久化
*
@@ -248,7 +250,7 @@ public class WxMpInMemoryConfigStorage implements WxMpConfigStorage {

@Override
public String toString() {
return ToStringUtils.toSimpleString(this);
return ToStringBuilder.reflectionToString(this, ToStringStyle.JSON_STYLE);
}

@Override


+ 58
- 9
weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpKefuService.java Visa fil

@@ -1,16 +1,21 @@
package me.chanjar.weixin.mp.api;

import java.io.File;
import java.util.Date;

import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.mp.bean.kefu.WxMpKefuMessage;
import me.chanjar.weixin.mp.bean.kefu.request.WxMpKfAccountRequest;
import me.chanjar.weixin.mp.bean.kefu.result.*;

import java.io.File;
import java.util.Date;
import me.chanjar.weixin.mp.bean.kefu.result.WxMpKfList;
import me.chanjar.weixin.mp.bean.kefu.result.WxMpKfMsgList;
import me.chanjar.weixin.mp.bean.kefu.result.WxMpKfOnlineList;
import me.chanjar.weixin.mp.bean.kefu.result.WxMpKfSessionGetResult;
import me.chanjar.weixin.mp.bean.kefu.result.WxMpKfSessionList;
import me.chanjar.weixin.mp.bean.kefu.result.WxMpKfSessionWaitCaseList;

/**
* <pre>
* 客服接口
* 客服接口.
* 注意:命名采用kefu拼音的原因是:其英文CustomerService如果再加上Service后缀显得有点啰嗦,如果不加又显得表意不完整。
* </pre>
*
@@ -30,7 +35,8 @@ public interface WxMpKefuService {
String KFSESSION_GET_SESSION = "https://api.weixin.qq.com/customservice/kfsession/getsession?openid=%s";
String KFSESSION_GET_SESSION_LIST = "https://api.weixin.qq.com/customservice/kfsession/getsessionlist?kf_account=%s";
String KFSESSION_GET_WAIT_CASE = "https://api.weixin.qq.com/customservice/kfsession/getwaitcase";
String MSGRECORD_GET_MSG_LIST = "https://api.weixin.qq.com/customservice/msgrecord/getmsglist";
String MSG_RECORD_LIST = "https://api.weixin.qq.com/customservice/msgrecord/getmsglist";
String CUSTOM_TYPING = "https://api.weixin.qq.com/cgi-bin/message/custom/typing";

/**
* <pre>
@@ -38,6 +44,8 @@ public interface WxMpKefuService {
* 详情请见: <a href="http://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140547&token=&lang=zh_CN">发送客服消息</a>
* 接口url格式:https://api.weixin.qq.com/cgi-bin/message/custom/send?access_token=ACCESS_TOKEN
* </pre>
*
* @throws WxErrorException 异常
*/
boolean sendKefuMessage(WxMpKefuMessage message) throws WxErrorException;

@@ -49,6 +57,8 @@ public interface WxMpKefuService {
* 详情请见:<a href="http://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1458044813&token=&lang=zh_CN">客服管理</a>
* 接口url格式:https://api.weixin.qq.com/cgi-bin/customservice/getkflist?access_token=ACCESS_TOKEN
* </pre>
*
* @throws WxErrorException 异常
*/
WxMpKfList kfList() throws WxErrorException;

@@ -58,6 +68,8 @@ public interface WxMpKefuService {
* 详情请见:<a href="http://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1458044813&token=&lang=zh_CN">客服管理</a>
* 接口url格式:https://api.weixin.qq.com/cgi-bin/customservice/getonlinekflist?access_token=ACCESS_TOKEN
* </pre>
*
* @throws WxErrorException 异常
*/
WxMpKfOnlineList kfOnlineList() throws WxErrorException;

@@ -67,6 +79,8 @@ public interface WxMpKefuService {
* 详情请见:<a href="http://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1458044813&token=&lang=zh_CN">客服管理</a>
* 接口url格式:https://api.weixin.qq.com/customservice/kfaccount/add?access_token=ACCESS_TOKEN
* </pre>
*
* @throws WxErrorException 异常
*/
boolean kfAccountAdd(WxMpKfAccountRequest request) throws WxErrorException;

@@ -85,6 +99,8 @@ public interface WxMpKefuService {
* 详情请见:<a href="http://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1458044813&token=&lang=zh_CN">客服管理</a>
* 接口url格式:https://api.weixin.qq.com/customservice/kfaccount/inviteworker?access_token=ACCESS_TOKEN
* </pre>
*
* @throws WxErrorException 异常
*/
boolean kfAccountInviteWorker(WxMpKfAccountRequest request) throws WxErrorException;

@@ -94,8 +110,10 @@ public interface WxMpKefuService {
* 详情请见:<a href="http://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1458044813&token=&lang=zh_CN">客服管理</a>
* 接口url格式:https://api.weixin.qq.com/customservice/kfaccount/uploadheadimg?access_token=ACCESS_TOKEN&kf_account=KFACCOUNT
* </pre>
*
* @throws WxErrorException 异常
*/
boolean kfAccountUploadHeadImg(String kfAccount, File imgFile) throws WxErrorException;
boolean kfAccountUploadHeadImg(String kfAccount, File imgFile) throws WxErrorException;

/**
* <pre>
@@ -103,6 +121,8 @@ public interface WxMpKefuService {
* 详情请见:<a href="http://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1458044813&token=&lang=zh_CN">客服管理</a>
* 接口url格式:https://api.weixin.qq.com/customservice/kfaccount/del?access_token=ACCESS_TOKEN&kf_account=KFACCOUNT
* </pre>
*
* @throws WxErrorException 异常
*/
boolean kfAccountDel(String kfAccount) throws WxErrorException;

@@ -115,6 +135,8 @@ public interface WxMpKefuService {
* 详情请见:<a href="http://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1458044820&token=&lang=zh_CN">客服会话控制接口</a>
* 接口url格式: https://api.weixin.qq.com/customservice/kfsession/create?access_token=ACCESS_TOKEN
* </pre>
*
* @throws WxErrorException 异常
*/
boolean kfSessionCreate(String openid, String kfAccount) throws WxErrorException;

@@ -125,6 +147,8 @@ public interface WxMpKefuService {
* 详情请见:<a href="http://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1458044820&token=&lang=zh_CN">客服会话控制接口</a>
* 接口url格式: https://api.weixin.qq.com/customservice/kfsession/close?access_token=ACCESS_TOKEN
* </pre>
*
* @throws WxErrorException 异常
*/
boolean kfSessionClose(String openid, String kfAccount) throws WxErrorException;

@@ -135,6 +159,8 @@ public interface WxMpKefuService {
* 详情请见:<a href="http://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1458044820&token=&lang=zh_CN">客服会话控制接口</a>
* 接口url格式: https://api.weixin.qq.com/customservice/kfsession/getsession?access_token=ACCESS_TOKEN&openid=OPENID
* </pre>
*
* @throws WxErrorException 异常
*/
WxMpKfSessionGetResult kfSessionGet(String openid) throws WxErrorException;

@@ -145,6 +171,8 @@ public interface WxMpKefuService {
* 详情请见:<a href="http://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1458044820&token=&lang=zh_CN">客服会话控制</a>
* 接口url格式: https://api.weixin.qq.com/customservice/kfsession/getsessionlist?access_token=ACCESS_TOKEN&kf_account=KFACCOUNT
* </pre>
*
* @throws WxErrorException 异常
*/
WxMpKfSessionList kfSessionList(String kfAccount) throws WxErrorException;

@@ -155,6 +183,8 @@ public interface WxMpKefuService {
* 详情请见:<a href="http://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1458044820&token=&lang=zh_CN">客服会话控制</a>
* 接口url格式: https://api.weixin.qq.com/customservice/kfsession/getwaitcase?access_token=ACCESS_TOKEN
* </pre>
*
* @throws WxErrorException 异常
*/
WxMpKfSessionWaitCaseList kfSessionGetWaitCase() throws WxErrorException;

@@ -173,7 +203,7 @@ public interface WxMpKefuService {
* @param msgId 消息id顺序从小到大,从1开始
* @param number 每次获取条数,最多10000条
* @return 聊天记录对象
* @throws WxErrorException
* @throws WxErrorException 异常
*/
WxMpKfMsgList kfMsgList(Date startTime, Date endTime, Long msgId, Integer number) throws WxErrorException;

@@ -188,8 +218,27 @@ public interface WxMpKefuService {
* @param startTime 起始时间
* @param endTime 结束时间
* @return 聊天记录对象
* @throws WxErrorException
* @throws WxErrorException 异常
*/
WxMpKfMsgList kfMsgList(Date startTime, Date endTime) throws WxErrorException;

/**
* <pre>
* 客服输入状态
* 开发者可通过调用“客服输入状态”接口,返回客服当前输入状态给用户。
* 此接口需要客服消息接口权限。
* 如果不满足发送客服消息的触发条件,则无法下发输入状态。
* 下发输入状态,需要客服之前30秒内跟用户有过消息交互。
* 在输入状态中(持续15s),不可重复下发输入态。
* 在输入状态中,如果向用户下发消息,会同时取消输入状态。
*
* 详情请见:<a href="https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140547">客服输入状态</a>
* 接口url格式:https://api.weixin.qq.com/cgi-bin/message/custom/typing?access_token=ACCESS_TOKEN
* </pre>
*
* @param openid 用户id
* @param command "Typing":对用户下发“正在输入"状态 "CancelTyping":取消对用户的”正在输入"状态
* @throws WxErrorException 异常
*/
boolean sendKfTypingState(String openid, String command) throws WxErrorException;
}

+ 12
- 4
weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpMaterialService.java Visa fil

@@ -1,12 +1,20 @@
package me.chanjar.weixin.mp.api;

import me.chanjar.weixin.common.bean.result.WxMediaUploadResult;
import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.mp.bean.material.*;

import java.io.File;
import java.io.InputStream;

import me.chanjar.weixin.common.bean.result.WxMediaUploadResult;
import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.mp.bean.material.WxMediaImgUploadResult;
import me.chanjar.weixin.mp.bean.material.WxMpMaterial;
import me.chanjar.weixin.mp.bean.material.WxMpMaterialArticleUpdate;
import me.chanjar.weixin.mp.bean.material.WxMpMaterialCountResult;
import me.chanjar.weixin.mp.bean.material.WxMpMaterialFileBatchGetResult;
import me.chanjar.weixin.mp.bean.material.WxMpMaterialNews;
import me.chanjar.weixin.mp.bean.material.WxMpMaterialNewsBatchGetResult;
import me.chanjar.weixin.mp.bean.material.WxMpMaterialUploadResult;
import me.chanjar.weixin.mp.bean.material.WxMpMaterialVideoInfoResult;

/**
* <pre>
* Created by Binary Wang on 2016/7/21.


+ 46
- 8
weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpMemberCardService.java Visa fil

@@ -1,29 +1,57 @@
package me.chanjar.weixin.mp.api;

import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.mp.bean.membercard.WxMpMemberCardActivatedMessage;
import me.chanjar.weixin.mp.bean.membercard.WxMpMemberCardUpdateMessage;
import me.chanjar.weixin.mp.bean.membercard.WxMpMemberCardUpdateResult;
import me.chanjar.weixin.mp.bean.membercard.WxMpMemberCardUserInfoResult;
import me.chanjar.weixin.mp.bean.card.MemberCardActivateUserFormRequest;
import me.chanjar.weixin.mp.bean.card.MemberCardActivateUserFormResult;
import me.chanjar.weixin.mp.bean.card.WxMpCardCreateResult;
import me.chanjar.weixin.mp.bean.membercard.*;

/**
* 会员卡相关接口
*
* @author YuJian(mgcnrx11@gmail.com)
* @author YuJian(mgcnrx11 @ gmail.com)
* @author yuanqixun
* @version 2017/7/8
* @date 2018-08-30
*/
public interface WxMpMemberCardService {
String MEMBER_CARD_CREAET = "https://api.weixin.qq.com/card/create";
String MEMBER_CARD_ACTIVATE = "https://api.weixin.qq.com/card/membercard/activate";
String MEMBER_CARD_USER_INFO_GET = "https://api.weixin.qq.com/card/membercard/userinfo/get";
String MEMBER_CARD_UPDATE_USER = "https://api.weixin.qq.com/card/membercard/updateuser";
/**
* 会员卡激活之微信开卡接口(wx_activate=true情况调用)
*/
String MEMBER_CARD_ACTIVATEUSERFORM = "https://api.weixin.qq.com/card/membercard/activateuserform/set";

/**
* 得到WxMpService
*/
WxMpService getWxMpService();

/**
* 会员卡创建接口
*
* @param createJson
* @return
* @throws WxErrorException
*/
WxMpCardCreateResult createMemberCard(String createJson) throws WxErrorException;

/**
* 会员卡创建接口
*
* @param createMessageMessage
* @return WxMpCardCreateResult
* @throws WxErrorException
*/
WxMpCardCreateResult createMemberCard(WxMpMemberCardCreateMessage createMessageMessage) throws WxErrorException;

/**
* 会员卡激活接口
*
* @param activatedMessage 激活所需参数
* @return 调用返回的JSON字符串。
* @return 返回json字符串
* @throws WxErrorException 接口调用失败抛出的异常
*/
String activateMemberCard(WxMpMemberCardActivatedMessage activatedMessage) throws WxErrorException;
@@ -32,7 +60,7 @@ public interface WxMpMemberCardService {
* 拉取会员信息接口
*
* @param cardId 会员卡的CardId,微信分配
* @param code 领取会员的会员卡Code
* @param code 领取会员的会员卡Code
* @return 会员信息的结果对象
* @throws WxErrorException 接口调用失败抛出的异常
*/
@@ -40,7 +68,7 @@ public interface WxMpMemberCardService {

/**
* 当会员持卡消费后,支持开发者调用该接口更新会员信息。会员卡交易后的每次信息变更需通过该接口通知微信,便于后续消息通知及其他扩展功能。
*
* <p>
* 1.开发者可以同时传入add_bonus和bonus解决由于同步失败带来的幂等性问题。同时传入add_bonus和bonus时
* add_bonus作为积分变动消息中的变量值,而bonus作为卡面上的总积分额度显示。余额变动同理。
* 2.开发者可以传入is_notify_bonus控制特殊的积分对账变动不发送消息,余额变动同理。
@@ -50,4 +78,14 @@ public interface WxMpMemberCardService {
* @throws WxErrorException 接口调用失败抛出的异常
*/
WxMpMemberCardUpdateResult updateUserMemberCard(WxMpMemberCardUpdateMessage updateUserMessage) throws WxErrorException;

/**
* 设置会员卡激活的字段(会员卡设置:wx_activate=true 时需要)
*
* @param userFormRequest
* @return
* @throws WxErrorException
*/
MemberCardActivateUserFormResult setActivateUserForm(MemberCardActivateUserFormRequest userFormRequest) throws WxErrorException;

}

+ 93
- 4
weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpCardServiceImpl.java Visa fil

@@ -1,9 +1,6 @@
package me.chanjar.weixin.mp.api.impl;

import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.google.gson.JsonPrimitive;
import com.google.gson.*;
import com.google.gson.reflect.TypeToken;
import me.chanjar.weixin.common.bean.WxCardApiSignature;
import me.chanjar.weixin.common.error.WxError;
@@ -13,8 +10,12 @@ import me.chanjar.weixin.common.util.crypto.SHA1;
import me.chanjar.weixin.common.util.http.SimpleGetRequestExecutor;
import me.chanjar.weixin.mp.api.WxMpCardService;
import me.chanjar.weixin.mp.api.WxMpService;
import me.chanjar.weixin.mp.bean.card.WxMpCardLandingPageCreateRequest;
import me.chanjar.weixin.mp.bean.card.WxMpCardLandingPageCreateResult;
import me.chanjar.weixin.mp.bean.card.WxMpCardQrcodeCreateResult;
import me.chanjar.weixin.mp.bean.result.WxMpCardResult;
import me.chanjar.weixin.mp.util.json.WxMpGsonBuilder;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@@ -30,6 +31,8 @@ public class WxMpCardServiceImpl implements WxMpCardService {

private WxMpService wxMpService;

private static final Gson GSON = new Gson();

public WxMpCardServiceImpl(WxMpService wxMpService) {
this.wxMpService = wxMpService;
}
@@ -236,4 +239,90 @@ public class WxMpCardServiceImpl implements WxMpCardService {

return responseContent;
}

/**
* 添加测试白名单
*
* @param openid 用户的openid
* @return
*/
public String addTestWhiteList(String openid) throws WxErrorException {
JsonArray array = new JsonArray();
array.add(openid);
JsonObject jsonObject = new JsonObject();
jsonObject.add("openid", array);
String respone = this.wxMpService.post(CARD_TEST_WHITELIST, GSON.toJson(jsonObject));
return respone;
}

/**
* 创建卡券二维码
*
* @param cardId
* @param outerStr
* @return
*/
public WxMpCardQrcodeCreateResult createQrcodeCard(String cardId, String outerStr) throws WxErrorException {
return createQrcodeCard(cardId, outerStr, 0);
}

/**
* 创建卡券二维码
*
* @param cardId 卡券编号
* @param outerStr 二维码标识
* @param expiresIn 失效时间,单位秒,不填默认365天
* @return
* @throws WxErrorException
*/
public WxMpCardQrcodeCreateResult createQrcodeCard(String cardId, String outerStr, int expiresIn) throws WxErrorException {
JsonObject jsonObject = new JsonObject();
jsonObject.addProperty("action_name", "QR_CARD");
if (expiresIn > 0) {
jsonObject.addProperty("expire_seconds", expiresIn);
}
JsonObject actionInfoJson = new JsonObject();
JsonObject cardJson = new JsonObject();
cardJson.addProperty("card_id", cardId);
cardJson.addProperty("outer_str", outerStr);
actionInfoJson.add("card", cardJson);
jsonObject.add("action_info", actionInfoJson);
String response = this.wxMpService.post(CARD_QRCODE_CREAET, GSON.toJson(jsonObject));
return WxMpCardQrcodeCreateResult.fromJson(response);
}

/**
* 创建卡券货架接口
*
* @param request
* @return
* @throws WxErrorException
*/
@Override
public WxMpCardLandingPageCreateResult createLandingPage(WxMpCardLandingPageCreateRequest request) throws WxErrorException {
String response = this.wxMpService.post(CARD_LANDING_PAGE_CREAET, GSON.toJson(request));
return WxMpCardLandingPageCreateResult.fromJson(response);
}

/**
* 将用户的卡券设置为失效状态
* 详见:https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1451025272&anchor=9
*
* @param cardId 卡券编号
* @param code 用户会员卡号
* @param reason 设置为失效的原因
* @return
* @throws WxErrorException
*/
@Override
public String unavailableCardCode(String cardId, String code, String reason) throws WxErrorException {
if (StringUtils.isAnyBlank(cardId, code, reason))
throw new WxErrorException(WxError.builder().errorCode(41012).errorMsg("参数不完整").build());
JsonObject jsonRequest = new JsonObject();
jsonRequest.addProperty("card_id", cardId);
jsonRequest.addProperty("code", code);
jsonRequest.addProperty("reason", reason);
String response = this.wxMpService.post(CARD_CODE_UNAVAILABLE, GSON.toJson(jsonRequest));
return response;
}
}

+ 27
- 12
weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpKefuServiceImpl.java Visa fil

@@ -1,8 +1,14 @@
package me.chanjar.weixin.mp.api.impl;

import java.io.File;
import java.util.Date;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.gson.JsonObject;
import me.chanjar.weixin.common.error.WxError;
import me.chanjar.weixin.common.bean.result.WxMediaUploadResult;
import me.chanjar.weixin.common.error.WxError;
import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.common.util.http.MediaUploadRequestExecutor;
import me.chanjar.weixin.mp.api.WxMpKefuService;
@@ -10,12 +16,12 @@ import me.chanjar.weixin.mp.api.WxMpService;
import me.chanjar.weixin.mp.bean.kefu.WxMpKefuMessage;
import me.chanjar.weixin.mp.bean.kefu.request.WxMpKfAccountRequest;
import me.chanjar.weixin.mp.bean.kefu.request.WxMpKfSessionRequest;
import me.chanjar.weixin.mp.bean.kefu.result.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.util.Date;
import me.chanjar.weixin.mp.bean.kefu.result.WxMpKfList;
import me.chanjar.weixin.mp.bean.kefu.result.WxMpKfMsgList;
import me.chanjar.weixin.mp.bean.kefu.result.WxMpKfOnlineList;
import me.chanjar.weixin.mp.bean.kefu.result.WxMpKfSessionGetResult;
import me.chanjar.weixin.mp.bean.kefu.result.WxMpKfSessionList;
import me.chanjar.weixin.mp.bean.kefu.result.WxMpKfSessionWaitCaseList;

/**
* @author Binary Wang
@@ -120,12 +126,12 @@ public class WxMpKefuServiceImpl implements WxMpKefuService {
}

JsonObject param = new JsonObject();
param.addProperty("starttime", startTime.getTime() / 1000); //starttime 起始时间,unix时间戳
param.addProperty("endtime", endTime.getTime() / 1000); //endtime 结束时间,unix时间戳,每次查询时段不能超过24小时
param.addProperty("msgid", msgId); //msgid 消息id顺序从小到大,从1开始
param.addProperty("number", number); //number 每次获取条数,最多10000条
param.addProperty("starttime", startTime.getTime() / 1000);
param.addProperty("endtime", endTime.getTime() / 1000);
param.addProperty("msgid", msgId);
param.addProperty("number", number);

String responseContent = this.wxMpService.post(MSGRECORD_GET_MSG_LIST, param.toString());
String responseContent = this.wxMpService.post(MSG_RECORD_LIST, param.toString());

return WxMpKfMsgList.fromJson(responseContent);
}
@@ -149,4 +155,13 @@ public class WxMpKefuServiceImpl implements WxMpKefuService {
return result;
}

@Override
public boolean sendKfTypingState(String openid, String command) throws WxErrorException {
JsonObject params = new JsonObject();
params.addProperty("touser", openid);
params.addProperty("command", command);
String responseContent = this.wxMpService.post(CUSTOM_TYPING, params.toString());
return responseContent != null;
}

}

+ 23
- 11
weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpMaterialServiceImpl.java Visa fil

@@ -1,9 +1,16 @@
package me.chanjar.weixin.mp.api.impl;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

import me.chanjar.weixin.common.WxType;
import me.chanjar.weixin.common.api.WxConsts;
import me.chanjar.weixin.common.error.WxError;
import me.chanjar.weixin.common.bean.result.WxMediaUploadResult;
import me.chanjar.weixin.common.error.WxError;
import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.common.util.fs.FileUtils;
import me.chanjar.weixin.common.util.http.BaseMediaDownloadRequestExecutor;
@@ -11,17 +18,22 @@ import me.chanjar.weixin.common.util.http.MediaUploadRequestExecutor;
import me.chanjar.weixin.common.util.json.WxGsonBuilder;
import me.chanjar.weixin.mp.api.WxMpMaterialService;
import me.chanjar.weixin.mp.api.WxMpService;
import me.chanjar.weixin.mp.bean.material.*;
import me.chanjar.weixin.mp.util.requestexecuter.material.*;
import me.chanjar.weixin.mp.util.requestexecuter.media.MediaImgUploadRequestExecutor;
import me.chanjar.weixin.mp.bean.material.WxMediaImgUploadResult;
import me.chanjar.weixin.mp.bean.material.WxMpMaterial;
import me.chanjar.weixin.mp.bean.material.WxMpMaterialArticleUpdate;
import me.chanjar.weixin.mp.bean.material.WxMpMaterialCountResult;
import me.chanjar.weixin.mp.bean.material.WxMpMaterialFileBatchGetResult;
import me.chanjar.weixin.mp.bean.material.WxMpMaterialNews;
import me.chanjar.weixin.mp.bean.material.WxMpMaterialNewsBatchGetResult;
import me.chanjar.weixin.mp.bean.material.WxMpMaterialUploadResult;
import me.chanjar.weixin.mp.bean.material.WxMpMaterialVideoInfoResult;
import me.chanjar.weixin.mp.util.json.WxMpGsonBuilder;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import me.chanjar.weixin.mp.util.requestexecuter.material.MaterialDeleteRequestExecutor;
import me.chanjar.weixin.mp.util.requestexecuter.material.MaterialNewsInfoRequestExecutor;
import me.chanjar.weixin.mp.util.requestexecuter.material.MaterialUploadRequestExecutor;
import me.chanjar.weixin.mp.util.requestexecuter.material.MaterialVideoInfoRequestExecutor;
import me.chanjar.weixin.mp.util.requestexecuter.material.MaterialVoiceAndImageDownloadRequestExecutor;
import me.chanjar.weixin.mp.util.requestexecuter.media.MediaImgUploadRequestExecutor;

/**
* Created by Binary Wang on 2016/7/21.


+ 191
- 12
weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpMemberCardServiceImpl.java Visa fil

@@ -6,30 +6,29 @@ import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.google.gson.reflect.TypeToken;
import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.common.util.json.WxGsonBuilder;
import me.chanjar.weixin.mp.api.WxMpMemberCardService;
import me.chanjar.weixin.mp.api.WxMpService;
import me.chanjar.weixin.mp.bean.membercard.WxMpMemberCardActivatedMessage;
import me.chanjar.weixin.mp.bean.membercard.WxMpMemberCardUpdateMessage;
import me.chanjar.weixin.mp.bean.membercard.WxMpMemberCardUpdateResult;
import me.chanjar.weixin.mp.bean.membercard.WxMpMemberCardUserInfoResult;
import me.chanjar.weixin.mp.bean.card.*;
import me.chanjar.weixin.mp.bean.card.enums.BusinessServiceType;
import me.chanjar.weixin.mp.bean.card.enums.CardColor;
import me.chanjar.weixin.mp.bean.card.enums.DateInfoType;
import me.chanjar.weixin.mp.bean.membercard.*;
import me.chanjar.weixin.mp.util.json.WxMpGsonBuilder;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* 会员卡相关接口的实现类
*
* @author YuJian(mgcnrx11@gmail.com)
* @author YuJian(mgcnrx11 @ gmail.com)
* @version 2017/7/8
*/
public class WxMpMemberCardServiceImpl implements WxMpMemberCardService {

private final Logger log = LoggerFactory.getLogger(WxMpMemberCardServiceImpl.class);

private static final String MEMBER_CARD_ACTIVATE = "https://api.weixin.qq.com/card/membercard/activate";
private static final String MEMBER_CARD_USER_INFO_GET = "https://api.weixin.qq.com/card/membercard/userinfo/get";
private static final String MEMBER_CARD_UPDATE_USER = "https://api.weixin.qq.com/card/membercard/updateuser";

private WxMpService wxMpService;

private static final Gson GSON = new Gson();
@@ -46,11 +45,176 @@ public class WxMpMemberCardServiceImpl implements WxMpMemberCardService {
return this.wxMpService;
}

/**
* 会员卡创建接口
*
* @param createJson 创建json
* @return 调用返回的JSON字符串。
* @throws WxErrorException 接口调用失败抛出的异常
*/
@Override
public WxMpCardCreateResult createMemberCard(String createJson) throws WxErrorException {
WxMpMemberCardCreateMessage createMessage = WxGsonBuilder.create().fromJson(createJson, WxMpMemberCardCreateMessage.class);
return createMemberCard(createMessage);
}

/**
* 会员卡创建接口
*
* @param createMessageMessage 创建所需参数
* @return WxMpCardCreateResult。
* @throws WxErrorException 接口调用失败抛出的异常
*/
@Override
public WxMpCardCreateResult createMemberCard(WxMpMemberCardCreateMessage createMessageMessage) throws WxErrorException {
//校验请求对象合法性
WxMpCardCreateResult validResult = validCheck(createMessageMessage);
if (!validResult.isSuccess())
return validResult;
String response = this.wxMpService.post(MEMBER_CARD_CREAET, GSON.toJson(createMessageMessage));
return WxMpCardCreateResult.fromJson(response);
}

private WxMpCardCreateResult validCheck(WxMpMemberCardCreateMessage createMessageMessage) throws WxErrorException {
if (createMessageMessage == null) {
return WxMpCardCreateResult.failure("对象不能为空");
}
MemberCardCreateRequest cardCreateRequest = createMessageMessage.getCardCreateRequest();
if (createMessageMessage == null) {
return WxMpCardCreateResult.failure("会员卡对象不能为空");
}
String cardType = cardCreateRequest.getCardType();
if (!StringUtils.equals(cardType, "MEMBER_CARD")) {
return WxMpCardCreateResult.failure("卡券类型必须等于MEMBER_CARD");
}
MemberCard memberCard = cardCreateRequest.getMemberCard();

if (StringUtils.isEmpty(memberCard.getPrerogative())) {
return WxMpCardCreateResult.failure("会员卡特权说明不能为空:prerogative");
}
//卡片激活规则
if (!memberCard.isAutoActivate() && !memberCard.isWxActivate() && StringUtils.isEmpty(memberCard.getActivateUrl())) {
return WxMpCardCreateResult.failure("会员卡激活方式为接口激活,activate_url不能为空");
}

//积分支持
// if(memberCard.isSupplyBonus() && StringUtils.isEmpty(memberCard.getBonusUrl())){
// return WxMpCardCreateResult.failure("会员卡支持积分,bonus_url不能为空");
// }
// if(memberCard.isSupplyBonus() && memberCard.getBonusRule() == null){
// return WxMpCardCreateResult.failure("会员卡支持积分,bonus_rule不能为空");
// }
BaseInfo baseInfo = memberCard.getBaseInfo();
if (baseInfo == null) {
return WxMpCardCreateResult.failure("会员卡基本信息对象base_info不能为空");
}

if (StringUtils.isBlank(baseInfo.getLogoUrl())) {
return WxMpCardCreateResult.failure("会员卡基本信息的商户logo:logo_url不能为空");
}

if (StringUtils.isBlank(baseInfo.getCodeType())) {
return WxMpCardCreateResult.failure("会员卡基本信息的条码类型:code_type不能为空");
}

if (StringUtils.isBlank(baseInfo.getBrandName())) {
return WxMpCardCreateResult.failure("会员卡基本信息的商户名字:brand_name不能为空");
}

if (StringUtils.length(baseInfo.getBrandName()) > 12) {
return WxMpCardCreateResult.failure("会员卡基本信息的商户名字:brand_name长度不能大于12个汉字");
}

if (StringUtils.isBlank(baseInfo.getTitle())) {
return WxMpCardCreateResult.failure("会员卡基本信息的卡券名称:title不能为空");
}

if (StringUtils.length(baseInfo.getTitle()) > 9) {
return WxMpCardCreateResult.failure("会员卡基本信息的卡券名称:title长度不能大于9个汉字");
}

if (StringUtils.isBlank(baseInfo.getColor())) {
return WxMpCardCreateResult.failure("会员卡基本信息的卡颜色:color不能为空");
}

CardColor cardColor = null;
try {
cardColor = CardColor.valueOf(baseInfo.getColor());
} catch (IllegalArgumentException ex) {

}
if (cardColor == null) {
return WxMpCardCreateResult.failure("会员卡基本信息的卡颜色:" + baseInfo.getColor() + "不支持");
}

if (StringUtils.isBlank(baseInfo.getNotice())) {
return WxMpCardCreateResult.failure("会员卡基本信息的使用提醒:notice不能为空");
}

if (StringUtils.isBlank(baseInfo.getDescription())) {
return WxMpCardCreateResult.failure("会员卡基本信息的使用说明:description不能为空");
}

if (baseInfo.getSku() == null) {
return WxMpCardCreateResult.failure("会员卡基本信息的商品信息:sku不能为空");
}

DateInfo dateInfo = baseInfo.getDateInfo();
if (dateInfo == null) {
return WxMpCardCreateResult.failure("会员卡基本信息的使用日期:date_info不能为空");
}

DateInfoType dateInfoType = null;
try {
dateInfoType = DateInfoType.valueOf(dateInfo.getType());
} catch (IllegalArgumentException ex) {

}

if (dateInfoType == null) {
return WxMpCardCreateResult.failure("会员卡基本信息的使用日期类型:" + dateInfo.getType() + "不合法");
}

//固定时长
if (dateInfoType == DateInfoType.DATE_TYPE_FIX_TERM && (dateInfo.getFixedTerm() == null || dateInfo.getFixedBeginTerm() == null)) {
return WxMpCardCreateResult.failure("会员卡基本信息的使用日期为:" + dateInfoType.getDescription() + ",fixedTerm和fixedBeginTerm不能为空");
}

//固定期限
if (dateInfoType == DateInfoType.DATE_TYPE_FIX_TIME_RANGE && (dateInfo.getBeginTimestamp() == null || dateInfo.getEndTimestamp() == null)) {
return WxMpCardCreateResult.failure("会员卡基本信息的使用日期为:" + dateInfoType.getDescription() + ",beginTimestamp 和 endTimestamp 不能为空");
}
if (dateInfoType == DateInfoType.DATE_TYPE_FIX_TIME_RANGE && (dateInfo.getBeginTimestamp() < System.currentTimeMillis() || dateInfo.getEndTimestamp() < System.currentTimeMillis() || dateInfo.getBeginTimestamp() > dateInfo.getEndTimestamp())) {
return WxMpCardCreateResult.failure("会员卡基本信息的使用日期为:" + dateInfoType.getDescription() + ",beginTimestamp和endTimestamp的值不合法,请检查");
}

if (!baseInfo.isUseAllLocations() && StringUtils.isBlank(baseInfo.getLocationIdList())) {
return WxMpCardCreateResult.failure("会员卡基本信息的门店使用范围选择指定门店,门店列表:locationIdList不能为空");
}

//校验高级信息
AdvancedInfo advancedInfo = memberCard.getAdvancedInfo();
if (advancedInfo != null) {
if (advancedInfo.getBusinessServiceList() != null) {
for (String bs : advancedInfo.getBusinessServiceList()) {
BusinessServiceType businessServiceType = null;
try {
businessServiceType = BusinessServiceType.valueOf(bs);
} catch (IllegalArgumentException ex) {
return WxMpCardCreateResult.failure("会员卡高级信息的商户服务:" + bs + " 不合法");
}
}
}
}

return WxMpCardCreateResult.success();
}

/**
* 会员卡激活接口
*
* @param activatedMessage 激活所需参数
* @return 调用返回的JSON字符串。
* @return WxMpCardCreateResult
* @throws WxErrorException 接口调用失败抛出的异常
*/
@Override
@@ -70,9 +234,10 @@ public class WxMpMemberCardServiceImpl implements WxMpMemberCardService {
public WxMpMemberCardUserInfoResult getUserInfo(String cardId, String code) throws WxErrorException {
JsonObject jsonObject = new JsonObject();
jsonObject.addProperty("card_id", cardId);
jsonObject.addProperty("code",code);
jsonObject.addProperty("code", code);

String responseContent = this.getWxMpService().post(MEMBER_CARD_USER_INFO_GET, jsonObject.toString());
log.debug("{}",responseContent);
JsonElement tmpJsonElement = new JsonParser().parse(responseContent);
return WxMpGsonBuilder.INSTANCE.create().fromJson(tmpJsonElement,
new TypeToken<WxMpMemberCardUserInfoResult>() {
@@ -81,7 +246,7 @@ public class WxMpMemberCardServiceImpl implements WxMpMemberCardService {

/**
* 当会员持卡消费后,支持开发者调用该接口更新会员信息。会员卡交易后的每次信息变更需通过该接口通知微信,便于后续消息通知及其他扩展功能。
*
* <p>
* 1.开发者可以同时传入add_bonus和bonus解决由于同步失败带来的幂等性问题。同时传入add_bonus和bonus时
* add_bonus作为积分变动消息中的变量值,而bonus作为卡面上的总积分额度显示。余额变动同理。
* 2.开发者可以传入is_notify_bonus控制特殊的积分对账变动不发送消息,余额变动同理。
@@ -101,4 +266,18 @@ public class WxMpMemberCardServiceImpl implements WxMpMemberCardService {
new TypeToken<WxMpMemberCardUpdateResult>() {
}.getType());
}

/**
* 设置会员卡激活的字段(会员卡设置:wx_activate=true 时需要)
*
* @param userFormRequest
* @return
* @throws WxErrorException
*/
@Override
public MemberCardActivateUserFormResult setActivateUserForm(MemberCardActivateUserFormRequest userFormRequest) throws WxErrorException {
String responseContent = this.getWxMpService().post(MEMBER_CARD_ACTIVATEUSERFORM, GSON.toJson(userFormRequest));
return MemberCardActivateUserFormResult.fromJson(responseContent);
}

}

Vissa filer visades inte eftersom för många filer har ändrats

Laddar…
Avbryt
Spara