Переглянути джерело

合并开发分支,发布正式版

master
Binary Wang 6 роки тому
committed by GitHub
джерело
коміт
32f88b7cd4
Не вдалося знайти GPG ключ що відповідає даному підпису Ідентифікатор GPG ключа: 4AEE18F83AFDEB23
100 змінених файлів з 3514 додано та 735 видалено
  1. +1
    -0
      .gitignore
  2. +3
    -0
      .vscode/settings.json
  3. +3
    -1
      pom.xml
  4. +32
    -0
      starters/wx-java-mp-starter/README.md
  5. +72
    -0
      starters/wx-java-mp-starter/pom.xml
  6. +42
    -0
      starters/wx-java-mp-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/RedisProperties.java
  7. +11
    -0
      starters/wx-java-mp-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/WxMpAutoConfiguration.java
  8. +58
    -0
      starters/wx-java-mp-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/WxMpProperties.java
  9. +53
    -0
      starters/wx-java-mp-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/WxMpServiceAutoConfiguration.java
  10. +84
    -0
      starters/wx-java-mp-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/WxMpStorageAutoConfiguration.java
  11. +1
    -0
      starters/wx-java-mp-starter/src/main/resources/META-INF/spring.factories
  12. +27
    -0
      starters/wx-java-pay-starter/README.md
  13. +68
    -0
      starters/wx-java-pay-starter/pom.xml
  14. +57
    -0
      starters/wx-java-pay-starter/src/main/java/com/binarywang/spring/starter/wxjava/pay/config/WxPayAutoConfiguration.java
  15. +46
    -0
      starters/wx-java-pay-starter/src/main/java/com/binarywang/spring/starter/wxjava/pay/properties/WxPayProperties.java
  16. +1
    -0
      starters/wx-java-pay-starter/src/main/resources/META-INF/spring.factories
  17. +3
    -4
      weixin-java-common/pom.xml
  18. +10
    -0
      weixin-java-common/src/main/java/me/chanjar/weixin/common/api/WxConsts.java
  19. +1
    -0
      weixin-java-common/src/main/java/me/chanjar/weixin/common/util/crypto/WxCryptUtil.java
  20. +1
    -1
      weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/ApacheMediaDownloadRequestExecutor.java
  21. +2
    -1
      weixin-java-common/src/main/java/me/chanjar/weixin/common/util/res/StringManager.java
  22. +3
    -0
      weixin-java-common/src/test/java/me/chanjar/weixin/common/util/crypto/WxCryptUtilTest.java
  23. +2
    -2
      weixin-java-cp/pom.xml
  24. +47
    -0
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/WxCpConsts.java
  25. +45
    -16
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpChatService.java
  26. +1
    -1
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpDepartmentService.java
  27. +66
    -0
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpOAService.java
  28. +60
    -0
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpService.java
  29. +30
    -0
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpTaskCardService.java
  30. +86
    -38
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/BaseWxCpServiceImpl.java
  31. +43
    -22
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpChatServiceImpl.java
  32. +2
    -2
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpDepartmentServiceImpl.java
  33. +165
    -0
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpOAServiceImpl.java
  34. +32
    -31
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpServiceApacheHttpClientImpl.java
  35. +21
    -22
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpServiceJoddHttpImpl.java
  36. +26
    -27
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpServiceOkHttpImpl.java
  37. +39
    -0
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpTaskCardServiceImpl.java
  38. +163
    -0
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpAppChatMessage.java
  39. +69
    -0
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpApprovalDataResult.java
  40. +51
    -0
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpCheckinData.java
  41. +151
    -0
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpCheckinOption.java
  42. +73
    -0
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpDialRecord.java
  43. +1
    -13
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpInviteResult.java
  44. +33
    -0
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpMaJsCode2SessionResult.java
  45. +195
    -22
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpMessage.java
  46. +42
    -0
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTaskCardUpdateResult.java
  47. +2
    -1
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpUser.java
  48. +36
    -11
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpXmlMessage.java
  49. +8
    -2
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/article/NewArticle.java
  50. +32
    -0
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/messagebuilder/MarkdownMsgBuilder.java
  51. +68
    -0
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/messagebuilder/TaskCardBuilder.java
  52. +23
    -0
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/taskcard/TaskCardButton.java
  53. +14
    -2
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/WxCpConfigStorage.java
  54. +27
    -2
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/WxCpInMemoryConfigStorage.java
  55. +44
    -17
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/WxCpJedisConfigStorage.java
  56. +1
    -1
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/message/WxCpMessageRouter.java
  57. +0
    -2
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/util/json/WxCpGsonBuilder.java
  58. +0
    -127
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/util/json/WxCpMessageGsonAdapter.java
  59. +88
    -56
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/util/json/WxCpUserGsonAdapter.java
  60. +50
    -2
      weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/WxCpMessageAPITest.java
  61. +36
    -0
      weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/impl/BaseWxCpServiceImplTest.java
  62. +126
    -14
      weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/impl/WxCpChatServiceImplTest.java
  63. +1
    -1
      weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/impl/WxCpDepartmentServiceImplTest.java
  64. +49
    -0
      weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/impl/WxCpOAServiceImplTest.java
  65. +65
    -0
      weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/impl/WxCpTaskCardServiceImplTest.java
  66. +1
    -1
      weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/impl/WxCpUserServiceImplTest.java
  67. +11
    -4
      weixin-java-cp/src/test/java/me/chanjar/weixin/cp/bean/WxCpAgentTest.java
  68. +50
    -8
      weixin-java-cp/src/test/java/me/chanjar/weixin/cp/bean/WxCpMessageTest.java
  69. +58
    -2
      weixin-java-cp/src/test/java/me/chanjar/weixin/cp/bean/WxCpXmlMessageTest.java
  70. +5
    -10
      weixin-java-cp/src/test/java/me/chanjar/weixin/cp/demo/WxCpDemoInMemoryConfigStorage.java
  71. +13
    -1
      weixin-java-cp/src/test/java/me/chanjar/weixin/cp/util/json/WxCpUserGsonAdapterTest.java
  72. +9
    -3
      weixin-java-miniapp/pom.xml
  73. +18
    -22
      weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaCodeService.java
  74. +2
    -2
      weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaMsgService.java
  75. +23
    -0
      weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaService.java
  76. +72
    -57
      weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaServiceImpl.java
  77. +12
    -4
      weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/WxMaKefuMessage.java
  78. +2
    -1
      weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/WxMaTemplateData.java
  79. +1
    -11
      weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/WxMaTemplateMessage.java
  80. +6
    -0
      weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/WxMaUniformMessage.java
  81. +9
    -3
      weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/code/WxMaCodeAuditStatus.java
  82. +49
    -9
      weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/util/crypt/WxMaCryptUtils.java
  83. +0
    -7
      weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/util/json/WxMaTemplateMessageGsonAdapter.java
  84. +2
    -3
      weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/util/json/WxMaUniformMessageGsonAdapter.java
  85. +2
    -2
      weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/util/json/WxMaVisitDistributionGsonAdapter.java
  86. +4
    -4
      weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/api/impl/WxMaMsgServiceImplTest.java
  87. +6
    -2
      weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/api/impl/WxMaServiceImplTest.java
  88. +35
    -0
      weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/util/crypt/WxMaCryptUtilsTest.java
  89. +3
    -4
      weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/util/json/WxMaUniformMessageGsonAdapterTest.java
  90. +3
    -2
      weixin-java-mp/pom.xml
  91. +5
    -27
      weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpAiOpenService.java
  92. +26
    -6
      weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpCardService.java
  93. +71
    -0
      weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpMarketingService.java
  94. +44
    -10
      weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpMemberCardService.java
  95. +53
    -1
      weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpService.java
  96. +82
    -36
      weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/BaseWxMpServiceImpl.java
  97. +14
    -17
      weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpAiOpenServiceImpl.java
  98. +26
    -25
      weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpCardServiceImpl.java
  99. +92
    -0
      weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpMarketingServiceImpl.java
  100. +13
    -10
      weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpMemberCardServiceImpl.java

+ 1
- 0
.gitignore Переглянути файл

@@ -42,6 +42,7 @@ Icon
.Spotlight-V100
.TemporaryItems
.Trashes
.vscode
.VolumeIcon.icns
.AppleDB
.AppleDesktop


+ 3
- 0
.vscode/settings.json Переглянути файл

@@ -0,0 +1,3 @@
{
"java.configuration.updateBuildConfiguration": "interactive"
}

+ 3
- 1
pom.xml Переглянути файл

@@ -6,7 +6,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>com.github.binarywang</groupId>
<artifactId>wx-java</artifactId>
<version>3.3.0</version>
<version>3.4.0</version>
<packaging>pom</packaging>
<name>WxJava - Weixin/Wechat Java SDK</name>
<description>微信开发Java SDK</description>
@@ -105,6 +105,8 @@
<module>weixin-java-pay</module>
<module>weixin-java-miniapp</module>
<module>weixin-java-open</module>
<module>starters/wx-java-pay-starter</module>
<module>starters/wx-java-mp-starter</module>
<!--module>weixin-java-osgi</module-->
</modules>



+ 32
- 0
starters/wx-java-mp-starter/README.md Переглянути файл

@@ -0,0 +1,32 @@
# wx-java-mp-starter
## 快速开始
1. 引入依赖
```xml
<dependency>
<groupId>com.github.binarywang</groupId>
<artifactId>wx-java-mp-starter</artifactId>
<version>${version}</version>
</dependency>
```
2. 添加配置(application.properties)
```properties
# 公众号配置(必填)
wx.mp.appId = @appId
wx.mp.secret = @secret
wx.mp.token = @token
wx.mp.aesKey = @aesKey
# 存储配置redis(可选)
wx.mp.config-storage.type = redis
wx.mp.config-storage.redis.host = 127.0.0.1
wx.mp.config-storage.redis.port = 6379
```
3. 支持自动注入的类型

`WxMpService`以及相关的服务类, 比如: `wxMpService.getXxxService`。








+ 72
- 0
starters/wx-java-mp-starter/pom.xml Переглянути файл

@@ -0,0 +1,72 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.github.binarywang</groupId>
<artifactId>wx-java</artifactId>
<version>3.4.0</version>
<relativePath>../../</relativePath>
</parent>

<artifactId>wx-java-mp-starter</artifactId>
<name>WxJava - Spring Boot Starter for MP</name>
<description>微信公众号开发的Spring Boot Starter</description>

<properties>
<spring.boot.version>2.1.4.RELEASE</spring.boot.version>
</properties>

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
<version>${spring.boot.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<version>${spring.boot.version}</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.github.binarywang</groupId>
<artifactId>weixin-java-mp</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring.boot.version}</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>2.2.1</version>
<executions>
<execution>
<id>attach-sources</id>
<goals>
<goal>jar-no-fork</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>

</project>

+ 42
- 0
starters/wx-java-mp-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/RedisProperties.java Переглянути файл

@@ -0,0 +1,42 @@
package com.binarywang.spring.starter.wxjava.mp;

import lombok.Data;

import java.io.Serializable;

/**
* Redis配置
*/
@Data
public class RedisProperties implements Serializable {

/**
* 主机地址
*/
private String host = "127.0.0.1";

/**
* 端口号
*/
private int port = 6379;

/**
* 密码
*/
private String password;

/**
* 超时
*/
private int timeout = 2000;

/**
* 数据库
*/
private int database = 0;

private Integer maxActive;
private Integer maxIdle;
private Integer maxWaitMillis;
private Integer minIdle;
}

+ 11
- 0
starters/wx-java-mp-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/WxMpAutoConfiguration.java Переглянути файл

@@ -0,0 +1,11 @@
package com.binarywang.spring.starter.wxjava.mp;

import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;

@Configuration
@EnableConfigurationProperties(WxMpProperties.class)
@Import({WxMpStorageAutoConfiguration.class, WxMpServiceAutoConfiguration.class})
public class WxMpAutoConfiguration {
}

+ 58
- 0
starters/wx-java-mp-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/WxMpProperties.java Переглянути файл

@@ -0,0 +1,58 @@
package com.binarywang.spring.starter.wxjava.mp;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;

import java.io.Serializable;

import static com.binarywang.spring.starter.wxjava.mp.WxMpProperties.PREFIX;
import static com.binarywang.spring.starter.wxjava.mp.WxMpProperties.StorageType.memory;


/**
* 微信接入相关配置属性
*/
@Data
@ConfigurationProperties(PREFIX)
public class WxMpProperties {
public static final String PREFIX = "wx.mp";

/**
* 设置微信公众号的appid
*/
private String appId;

/**
* 设置微信公众号的app secret
*/
private String secret;

/**
* 设置微信公众号的token
*/
private String token;

/**
* 设置微信公众号的EncodingAESKey
*/
private String aesKey;

/**
* 存储策略, memory, redis
*/
private ConfigStorage configStorage = new ConfigStorage();


@Data
public static class ConfigStorage implements Serializable {

private StorageType type = memory;

private RedisProperties redis = new RedisProperties();

}

public enum StorageType {
memory, redis
}
}

+ 53
- 0
starters/wx-java-mp-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/WxMpServiceAutoConfiguration.java Переглянути файл

@@ -0,0 +1,53 @@
package com.binarywang.spring.starter.wxjava.mp;

import me.chanjar.weixin.mp.api.WxMpConfigStorage;
import me.chanjar.weixin.mp.api.WxMpService;
import me.chanjar.weixin.mp.api.impl.WxMpServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
* 微信公众号相关服务自动注册
*/
@Configuration
public class WxMpServiceAutoConfiguration {
@Autowired
private ApplicationContext ctx;

@Bean
@ConditionalOnMissingBean
public WxMpService wxMpService(WxMpConfigStorage configStorage) {
WxMpService wxMpService = new WxMpServiceImpl();
wxMpService.setWxMpConfigStorage(configStorage);
registerWxMpSubService(wxMpService);
return wxMpService;
}

@ConditionalOnBean(WxMpService.class)
public Object registerWxMpSubService(WxMpService wxMpService) {
ConfigurableListableBeanFactory factory = (ConfigurableListableBeanFactory) ctx.getAutowireCapableBeanFactory();
factory.registerSingleton("wxMpKefuService", wxMpService.getKefuService());
factory.registerSingleton("wxMpMaterialService", wxMpService.getMaterialService());
factory.registerSingleton("wxMpMenuService", wxMpService.getMenuService());
factory.registerSingleton("wxMpUserService", wxMpService.getUserService());
factory.registerSingleton("wxMpUserTagService", wxMpService.getUserTagService());
factory.registerSingleton("wxMpQrcodeService", wxMpService.getQrcodeService());
factory.registerSingleton("wxMpCardService", wxMpService.getCardService());
factory.registerSingleton("wxMpDataCubeService", wxMpService.getDataCubeService());
factory.registerSingleton("wxMpUserBlacklistService", wxMpService.getBlackListService());
factory.registerSingleton("wxMpStoreService", wxMpService.getStoreService());
factory.registerSingleton("wxMpTemplateMsgService", wxMpService.getTemplateMsgService());
factory.registerSingleton("wxMpSubscribeMsgService", wxMpService.getSubscribeMsgService());
factory.registerSingleton("wxMpDeviceService", wxMpService.getDeviceService());
factory.registerSingleton("wxMpShakeService", wxMpService.getShakeService());
factory.registerSingleton("wxMpMemberCardService", wxMpService.getMemberCardService());
factory.registerSingleton("wxMpMassMessageService", wxMpService.getMassMessageService());
return Boolean.TRUE;
}

}

+ 84
- 0
starters/wx-java-mp-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/WxMpStorageAutoConfiguration.java Переглянути файл

@@ -0,0 +1,84 @@
package com.binarywang.spring.starter.wxjava.mp;

import me.chanjar.weixin.mp.api.WxMpConfigStorage;
import me.chanjar.weixin.mp.api.WxMpInMemoryConfigStorage;
import me.chanjar.weixin.mp.api.WxMpInRedisConfigStorage;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;

/**
* 微信公众号存储策略自动配置
*/
@Configuration
public class WxMpStorageAutoConfiguration {

@Autowired
private WxMpProperties properties;

@Autowired(required = false)
private JedisPool jedisPool;

@Bean
@ConditionalOnMissingBean(WxMpConfigStorage.class)
public WxMpConfigStorage wxMpInMemoryConfigStorage() {
WxMpProperties.ConfigStorage storage = properties.getConfigStorage();
WxMpProperties.StorageType type = storage.getType();

if (type == WxMpProperties.StorageType.redis) {
return getWxMpInRedisConfigStorage();
}
return getWxMpInMemoryConfigStorage();
}

private WxMpInMemoryConfigStorage getWxMpInMemoryConfigStorage() {
WxMpInMemoryConfigStorage config = new WxMpInMemoryConfigStorage();
setWxMpInfo(config);
return config;
}

private WxMpInRedisConfigStorage getWxMpInRedisConfigStorage() {
JedisPool poolToUse = jedisPool;
if (poolToUse == null) {
poolToUse = getJedisPool();
}
WxMpInRedisConfigStorage config = new WxMpInRedisConfigStorage(poolToUse);
setWxMpInfo(config);
return config;
}

private void setWxMpInfo(WxMpInMemoryConfigStorage config) {
config.setAppId(properties.getAppId());
config.setSecret(properties.getSecret());
config.setToken(properties.getToken());
config.setAesKey(properties.getAesKey());
}

private JedisPool getJedisPool() {
WxMpProperties.ConfigStorage storage = properties.getConfigStorage();
RedisProperties redis = storage.getRedis();

JedisPoolConfig config = new JedisPoolConfig();
if (redis.getMaxActive() != null) {
config.setMaxTotal(redis.getMaxActive());
}
if (redis.getMaxIdle() != null) {
config.setMaxIdle(redis.getMaxIdle());
}
if (redis.getMaxWaitMillis() != null) {
config.setMaxWaitMillis(redis.getMaxWaitMillis());
}
if (redis.getMinIdle() != null) {
config.setMinIdle(redis.getMinIdle());
}
config.setTestOnBorrow(true);
config.setTestWhileIdle(true);

JedisPool pool = new JedisPool(config, redis.getHost(), redis.getPort(),
redis.getTimeout(), redis.getPassword(), redis.getDatabase());
return pool;
}
}

+ 1
- 0
starters/wx-java-mp-starter/src/main/resources/META-INF/spring.factories Переглянути файл

@@ -0,0 +1 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.binarywang.spring.starter.wxjava.mp.WxMpAutoConfiguration

+ 27
- 0
starters/wx-java-pay-starter/README.md Переглянути файл

@@ -0,0 +1,27 @@
# 使用说明
1. 在自己的Spring Boot项目里,引入maven依赖
```xml
<dependency>
<groupId>com.github.binarywang</groupId>
<artifactId>wx-java-pay-starter</artifactId>
<version>${version}</version>
</dependency>
```
2. 添加配置(application.yml)
```yml
wx:
pay:
appId: wx5b69c56ac01ed858
mchId: 1462547202
mchKey: OGL9fvig9y2HrXrQ86tM4jTwyv4ja6G5
subAppId:
subMchId:
keyPath:
```








+ 68
- 0
starters/wx-java-pay-starter/pom.xml Переглянути файл

@@ -0,0 +1,68 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>wx-java</artifactId>
<groupId>com.github.binarywang</groupId>
<version>3.4.0</version>
<relativePath>../../</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>

<artifactId>wx-java-pay-starter</artifactId>
<name>WxJava - Spring Boot Starter for WxPay</name>
<description>微信支付开发的Spring Boot Starter</description>

<properties>
<spring.boot.version>2.1.4.RELEASE</spring.boot.version>
</properties>

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
<version>${spring.boot.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<version>${spring.boot.version}</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.github.binarywang</groupId>
<artifactId>weixin-java-pay</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring.boot.version}</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>2.2.1</version>
<executions>
<execution>
<id>attach-sources</id>
<goals>
<goal>jar-no-fork</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>

</project>

+ 57
- 0
starters/wx-java-pay-starter/src/main/java/com/binarywang/spring/starter/wxjava/pay/config/WxPayAutoConfiguration.java Переглянути файл

@@ -0,0 +1,57 @@
package com.binarywang.spring.starter.wxjava.pay.config;

import com.binarywang.spring.starter.wxjava.pay.properties.WxPayProperties;
import com.github.binarywang.wxpay.config.WxPayConfig;
import com.github.binarywang.wxpay.service.WxPayService;
import com.github.binarywang.wxpay.service.impl.WxPayServiceImpl;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
* <pre>
* 微信支付自动配置
* Created by BinaryWang on 2019/4/17.
* </pre>
*
* @author <a href="https://github.com/binarywang">Binary Wang</a>
*/
@Configuration
@EnableConfigurationProperties(WxPayProperties.class)
@ConditionalOnClass(WxPayService.class)
@ConditionalOnProperty(prefix = "wx.pay", value = "enabled", matchIfMissing = true)
public class WxPayAutoConfiguration {
private WxPayProperties properties;

@Autowired
public WxPayAutoConfiguration(WxPayProperties properties) {
this.properties = properties;
}

/**
* 构造微信支付服务对象.
*
* @return 微信支付service
*/
@Bean
@ConditionalOnMissingBean(WxPayService.class)
public WxPayService wxPayService() {
final WxPayServiceImpl wxPayService = new WxPayServiceImpl();
WxPayConfig payConfig = new WxPayConfig();
payConfig.setAppId(StringUtils.trimToNull(this.properties.getAppId()));
payConfig.setMchId(StringUtils.trimToNull(this.properties.getMchId()));
payConfig.setMchKey(StringUtils.trimToNull(this.properties.getMchKey()));
payConfig.setSubAppId(StringUtils.trimToNull(this.properties.getSubAppId()));
payConfig.setSubMchId(StringUtils.trimToNull(this.properties.getSubMchId()));
payConfig.setKeyPath(StringUtils.trimToNull(this.properties.getKeyPath()));

wxPayService.setConfig(payConfig);
return wxPayService;
}

}

+ 46
- 0
starters/wx-java-pay-starter/src/main/java/com/binarywang/spring/starter/wxjava/pay/properties/WxPayProperties.java Переглянути файл

@@ -0,0 +1,46 @@
package com.binarywang.spring.starter.wxjava.pay.properties;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;

/**
* <pre>
* 微信支付属性配置类
* Created by Binary Wang on 2019/4/17.
* </pre>
*
* @author <a href="https://github.com/binarywang">Binary Wang</a>
*/
@Data
@ConfigurationProperties(prefix = "wx.pay")
public class WxPayProperties {
/**
* 设置微信公众号或者小程序等的appid.
*/
private String appId;

/**
* 微信支付商户号.
*/
private String mchId;

/**
* 微信支付商户密钥.
*/
private String mchKey;

/**
* 服务商模式下的子商户公众账号ID,普通模式请不要配置,请在配置文件中将对应项删除.
*/
private String subAppId;

/**
* 服务商模式下的子商户号,普通模式请不要配置,最好是请在配置文件中将对应项删除.
*/
private String subMchId;

/**
* apiclient_cert.p12文件的绝对路径,或者如果放在项目中,请以classpath:开头指定.
*/
private String keyPath;
}

+ 1
- 0
starters/wx-java-pay-starter/src/main/resources/META-INF/spring.factories Переглянути файл

@@ -0,0 +1 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.binarywang.spring.starter.wxjava.pay.config.WxPayAutoConfiguration

+ 3
- 4
weixin-java-common/pom.xml Переглянути файл

@@ -1,17 +1,16 @@
<?xml version="1.0"?>
<project
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
xmlns="http://maven.apache.org/POM/4.0.0">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.github.binarywang</groupId>
<artifactId>wx-java</artifactId>
<version>3.3.0</version>
<version>3.4.0</version>
</parent>

<artifactId>weixin-java-common</artifactId>
<name>WxJava - Common</name>
<name>WxJava - Common Java SDK</name>
<description>微信开发Java SDK公共模块</description>

<dependencies>


+ 10
- 0
weixin-java-common/src/main/java/me/chanjar/weixin/common/api/WxConsts.java Переглянути файл

@@ -62,6 +62,11 @@ public class WxConsts {
* 图文消息(点击跳转到图文消息页面).
*/
public static final String MPNEWS = "mpnews";
/**
* markdown消息.
* (目前仅支持markdown语法的子集,微工作台(原企业号)不支持展示markdown消息)
*/
public static final String MARKDOWN = "markdown";
/**
* 发送文件(CP专用).
*/
@@ -83,6 +88,11 @@ public class WxConsts {
* 小程序卡片(要求小程序与公众号已关联)
*/
public static final String MINIPROGRAMPAGE = "miniprogrampage";

/**
* 任务卡片消息
*/
public static final String TASKCARD = "taskcard";
}

/**


+ 1
- 0
weixin-java-common/src/main/java/me/chanjar/weixin/common/util/crypto/WxCryptUtil.java Переглянути файл

@@ -39,6 +39,7 @@ public class WxCryptUtil {
try {
final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setExpandEntityReferences(false);
factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
return factory.newDocumentBuilder();
} catch (ParserConfigurationException exc) {
throw new IllegalArgumentException(exc);


+ 1
- 1
weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/ApacheMediaDownloadRequestExecutor.java Переглянути файл

@@ -57,7 +57,7 @@ public class ApacheMediaDownloadRequestExecutor extends BaseMediaDownloadRequest

String fileName = new HttpResponseProxy(response).getFileName();
if (StringUtils.isBlank(fileName)) {
return null;
fileName = String.valueOf(System.currentTimeMillis());
}

return FileUtils.createTmpFile(inputStream, FilenameUtils.getBaseName(fileName), FilenameUtils.getExtension(fileName),


+ 2
- 1
weixin-java-common/src/main/java/me/chanjar/weixin/common/util/res/StringManager.java Переглянути файл

@@ -19,6 +19,7 @@ package me.chanjar.weixin.common.util.res;

import java.text.MessageFormat;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/**
* An internationalization / localization helper class which reduces
@@ -46,7 +47,7 @@ import java.util.*;
*/
public class StringManager {

private static final Map<String, Map<Locale, StringManager>> MANAGERS = new Hashtable<>();
private static final Map<String, Map<Locale, StringManager>> MANAGERS = new ConcurrentHashMap<>();
private static int LOCALE_CACHE_SIZE = 10;
/**
* The ResourceBundle for this StringManager.


+ 3
- 0
weixin-java-common/src/test/java/me/chanjar/weixin/common/util/crypto/WxCryptUtilTest.java Переглянути файл

@@ -40,6 +40,7 @@ public class WxCryptUtilTest {

DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
documentBuilderFactory.setExpandEntityReferences(false);
documentBuilderFactory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
Document document = documentBuilder.parse(new InputSource(new StringReader(encryptedXml)));

@@ -83,6 +84,8 @@ public class WxCryptUtilTest {
String afterEncrpt = pc.encrypt(this.replyMsg);
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setExpandEntityReferences(false);
dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);

DocumentBuilder db = dbf.newDocumentBuilder();
StringReader sr = new StringReader(afterEncrpt);
InputSource is = new InputSource(sr);


+ 2
- 2
weixin-java-cp/pom.xml Переглянути файл

@@ -7,11 +7,11 @@
<parent>
<groupId>com.github.binarywang</groupId>
<artifactId>wx-java</artifactId>
<version>3.3.0</version>
<version>3.4.0</version>
</parent>

<artifactId>weixin-java-cp</artifactId>
<name>WxJava - CP</name>
<name>WxJava - CP Java SDK</name>
<description>微信企业号/企业微信 Java SDK</description>

<dependencies>


+ 47
- 0
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/WxCpConsts.java Переглянути файл

@@ -84,6 +84,11 @@ public class WxCpConsts {
*/
public static final String LOCATION_SELECT = "location_select";

/**
* 任务卡片事件推送.
*/
public static final String TASKCARD_CLICK = "taskcard_click";

}

/**
@@ -126,4 +131,46 @@ public class WxCpConsts {
public static final String UPDATE_TAG = "update_tag";

}

/**
* 应用推送消息的消息类型.
*/
public static class AppChatMsgType {
/**
* 文本消息.
*/
public static final String TEXT = "text";
/**
* 图片消息.
*/
public static final String IMAGE = "image";
/**
* 语音消息.
*/
public static final String VOICE = "voice";
/**
* 视频消息.
*/
public static final String VIDEO = "video";
/**
* 发送文件(CP专用).
*/
public static final String FILE = "file";
/**
* 文本卡片消息(CP专用).
*/
public static final String TEXTCARD = "textcard";
/**
* 图文消息(点击跳转到外链).
*/
public static final String NEWS = "news";
/**
* 图文消息(点击跳转到图文消息页面).
*/
public static final String MPNEWS = "mpnews";
/**
* markdown消息.
*/
public static final String MARKDOWN = "markdown";
}
}

+ 45
- 16
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpChatService.java Переглянути файл

@@ -3,46 +3,75 @@ package me.chanjar.weixin.cp.api;
import java.util.List;

import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.cp.bean.WxCpAppChatMessage;
import me.chanjar.weixin.cp.bean.WxCpChat;

/**
* 群聊服务
* 群聊服务.
*
* @author gaigeshen
*/
public interface WxCpChatService {
String APPCHAT_CREATE = "https://qyapi.weixin.qq.com/cgi-bin/appchat/create";
String APPCHAT_UPDATE = "https://qyapi.weixin.qq.com/cgi-bin/appchat/update";
String APPCHAT_GET_CHATID = "https://qyapi.weixin.qq.com/cgi-bin/appchat/get?chatid=";

/**
* 创建群聊会话,注意:刚创建的群,如果没有下发消息,在企业微信不会出现该群。
* 创建群聊会话,注意:刚创建的群,如果没有下发消息,在企业微信不会出现该群.
*
* @param name 群聊名,最多50个utf8字符,超过将截断
* @param owner 指定群主的id。如果不指定,系统会随机从userlist中选一人作为群主
* @param users 群成员id列表。至少2人,至多500人
* @param name 群聊名,最多50个utf8字符,超过将截断
* @param owner 指定群主的id。如果不指定,系统会随机从userlist中选一人作为群主
* @param users 群成员id列表。至少2人,至多500人
* @param chatId 群聊的唯一标志,不能与已有的群重复;字符串类型,最长32个字符。只允许字符0-9及字母a-zA-Z。如果不填,系统会随机生成群id
* @return 创建群聊会话的结果,群聊的唯一标志
* @return 创建的群聊会话chatId
* @throws WxErrorException 发生异常
*/
String chatCreate(String name, String owner, List<String> users, String chatId) throws WxErrorException;
/**
* 修改群聊会话
*
* @param chatId 群聊id
* @param name 新的群聊名。若不需更新,请忽略此参数(null or empty)。最多50个utf8字符,超过将截断
* @param owner 新群主的id。若不需更新,请忽略此参数(null or empty)
* @param usersToAdd 添加成员的id列表,若不需要更新,则传递空对象或者空集合
* chatCreate 同名方法
*/
String create(String name, String owner, List<String> users, String chatId) throws WxErrorException;

/**
* 修改群聊会话.
*
* @param chatId 群聊id
* @param name 新的群聊名。若不需更新,请忽略此参数(null or empty)。最多50个utf8字符,超过将截断
* @param owner 新群主的id。若不需更新,请忽略此参数(null or empty)
* @param usersToAdd 添加成员的id列表,若不需要更新,则传递空对象或者空集合
* @param usersToDelete 踢出成员的id列表,若不需要更新,则传递空对象或者空集合
* @throws WxErrorException 发生异常
*/
void chatUpdate(String chatId, String name, String owner, List<String> usersToAdd, List<String> usersToDelete) throws WxErrorException;

/**
* 获取群聊会话
*
* chatUpdate 同名方法
*/
void update(String chatId, String name, String owner, List<String> usersToAdd, List<String> usersToDelete) throws WxErrorException;

/**
* 获取群聊会话.
*
* @param chatId 群聊编号
* @return 群聊会话
* @throws WxErrorException 发生异常
*/
WxCpChat chatGet(String chatId) throws WxErrorException;

/**
* chatGet 同名方法
*/
WxCpChat get(String chatId) throws WxErrorException;

/**
* 应用支持推送文本、图片、视频、文件、图文等类型.
* 请求方式: POST(HTTPS)
* 请求地址: https://qyapi.weixin.qq.com/cgi-bin/appchat/send?access_token=ACCESS_TOKEN
* 文档地址:https://work.weixin.qq.com/api/doc#90000/90135/90248
*
* @param message 要发送的消息内容对象
*/
void sendMsg(WxCpAppChatMessage message) throws WxErrorException;

}

+ 1
- 1
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpDepartmentService.java Переглянути файл

@@ -26,7 +26,7 @@ public interface WxCpDepartmentService {
* @return 部门id
* @throws WxErrorException 异常
*/
Integer create(WxCpDepart depart) throws WxErrorException;
Long create(WxCpDepart depart) throws WxErrorException;

/**
* <pre>


+ 66
- 0
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpOAService.java Переглянути файл

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

import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.cp.bean.WxCpApprovalDataResult;
import me.chanjar.weixin.cp.bean.WxCpCheckinData;
import me.chanjar.weixin.cp.bean.WxCpCheckinOption;
import me.chanjar.weixin.cp.bean.WxCpDialRecord;

import java.util.Date;
import java.util.List;

/**
* @author Element
* @Package me.chanjar.weixin.cp.api
* @date 2019-04-06 10:52
* @Description: <pre>
* 企业微信OA相关接口
*
* </pre>
*/
public interface WxCpOAService {

/**
* <pre>
* 获取打卡数据
* API doc : https://work.weixin.qq.com/api/doc#90000/90135/90262
* </pre>
*
* @param openCheckinDataType 打卡类型。1:上下班打卡;2:外出打卡;3:全部打卡
* @param starttime 获取打卡记录的开始时间
* @param endtime 获取打卡记录的结束时间
* @param userIdList 需要获取打卡记录的用户列表
*/
List<WxCpCheckinData> getCheckinData(Integer openCheckinDataType, Date starttime, Date endtime, List<String> userIdList) throws WxErrorException;

/**
* <pre>
* 获取打卡规则
* API doc : https://work.weixin.qq.com/api/doc#90000/90135/90263
* </pre>
*
* @param datetime 需要获取规则的当天日期
* @param userIdList 需要获取打卡规则的用户列表
* @return
* @throws WxErrorException
*/
List<WxCpCheckinOption> getCheckinOption(Date datetime, List<String> userIdList) throws WxErrorException;

/**
* <pre>
* 获取审批数据
* 通过本接口来获取公司一段时间内的审批记录。一次拉取调用最多拉取10000个审批记录,可以通过多次拉取的方式来满足需求,但调用频率不可超过600次/分。
* API doc : https://work.weixin.qq.com/api/doc#90000/90135/91530
* </pre>
*
* @param starttime 获取审批记录的开始时间
* @param endtime 获取审批记录的结束时间
* @param nextSpnum 第一个拉取的审批单号,不填从该时间段的第一个审批单拉取
* @return
* @throws WxErrorException
*/
WxCpApprovalDataResult getApprovalData(Date starttime, Date endtime, Long nextSpnum) throws WxErrorException;

List<WxCpDialRecord> getDialRecord(Date starttime, Date endtime, Integer offset, Integer limit) throws WxErrorException;

}

+ 60
- 0
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpService.java Переглянути файл

@@ -7,6 +7,7 @@ import me.chanjar.weixin.common.session.WxSessionManager;
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.cp.bean.WxCpMaJsCode2SessionResult;
import me.chanjar.weixin.cp.bean.WxCpMessage;
import me.chanjar.weixin.cp.bean.WxCpMessageSendResult;
import me.chanjar.weixin.cp.config.WxCpConfigStorage;
@@ -16,6 +17,15 @@ import me.chanjar.weixin.cp.config.WxCpConfigStorage;
* @author chanjaster
*/
public interface WxCpService {
String GET_JSAPI_TICKET = "https://qyapi.weixin.qq.com/cgi-bin/get_jsapi_ticket";
String GET_AGENT_CONFIG_TICKET = "https://qyapi.weixin.qq.com/cgi-bin/ticket/get?&type=agent_config";
String MESSAGE_SEND = "https://qyapi.weixin.qq.com/cgi-bin/message/send";
String GET_CALLBACK_IP = "https://qyapi.weixin.qq.com/cgi-bin/getcallbackip";
String BATCH_REPLACE_PARTY = "https://qyapi.weixin.qq.com/cgi-bin/batch/replaceparty";
String BATCH_REPLACE_USER = "https://qyapi.weixin.qq.com/cgi-bin/batch/replaceuser";
String BATCH_GET_RESULT = "https://qyapi.weixin.qq.com/cgi-bin/batch/getresult?jobid=";
String JSCODE_TO_SESSION_URL = "https://qyapi.weixin.qq.com/cgi-bin/miniprogram/jscode2session";

/**
* <pre>
* 验证推送过来的消息的正确性
@@ -68,6 +78,33 @@ public interface WxCpService {
*/
String getJsapiTicket(boolean forceRefresh) throws WxErrorException;

/**
* 获得jsapi_ticket,不强制刷新jsapi_ticket
* 应用的jsapi_ticket用于计算agentConfig(参见“通过agentConfig注入应用的权限”)的签名,签名计算方法与上述介绍的config的签名算法完全相同,但需要注意以下区别:
*
* 签名的jsapi_ticket必须使用以下接口获取。且必须用wx.agentConfig中的agentid对应的应用secret去获取access_token。
* 签名用的noncestr和timestamp必须与wx.agentConfig中的nonceStr和timestamp相同。
* @see #getJsapiTicket(boolean)
*/
String getAgentJsapiTicket() throws WxErrorException;

/**
* <pre>
* 获取应用的jsapi_ticket
* 应用的jsapi_ticket用于计算agentConfig(参见“通过agentConfig注入应用的权限”)的签名,签名计算方法与上述介绍的config的签名算法完全相同,但需要注意以下区别:
*
* 签名的jsapi_ticket必须使用以下接口获取。且必须用wx.agentConfig中的agentid对应的应用secret去获取access_token。
* 签名用的noncestr和timestamp必须与wx.agentConfig中的nonceStr和timestamp相同。
*
* 获得时会检查jsapiToken是否过期,如果过期了,那么就刷新一下,否则就什么都不干
*
* 详情请见:https://work.weixin.qq.com/api/doc#10029/%E8%8E%B7%E5%8F%96%E5%BA%94%E7%94%A8%E7%9A%84jsapi_ticket
* </pre>
*
* @param forceRefresh 强制刷新
*/
String getAgentJsapiTicket(boolean forceRefresh) throws WxErrorException;

/**
* <pre>
* 创建调用jsapi时所需要的签名
@@ -89,6 +126,13 @@ public interface WxCpService {
*/
WxCpMessageSendResult messageSend(WxCpMessage message) throws WxErrorException;

/**
* 小程序登录凭证校验
*
* @param jsCode 登录时获取的 code
*/
WxCpMaJsCode2SessionResult jsCode2Session(String jsCode) throws WxErrorException;

/**
* <pre>
* 获取微信服务器的ip段
@@ -165,6 +209,13 @@ public interface WxCpService {
*/
WxSession getSession(String id, boolean create);

/**
* 获取WxSessionManager 对象
*
* @return WxSessionManager
*/
WxSessionManager getSessionManager();
/**
* <pre>
* 设置WxSessionManager,只有当需要使用个性化的WxSessionManager的时候才需要调用此方法,
@@ -250,8 +301,17 @@ public interface WxCpService {
*/
WxCpChatService getChatService();

/**
* 获取任务卡片服务
*
* @return 任务卡片服务
*/
WxCpTaskCardService getTaskCardService();

WxCpAgentService getAgentService();

WxCpOAService getOAService();

/**
* http请求对象
*/


+ 30
- 0
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpTaskCardService.java Переглянути файл

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

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

import java.util.List;

/**
* <pre>
* 任务卡片管理接口.
* Created by Jeff on 2019-05-16.
* </pre>
*
* @author <a href="https://github.com/domainname">Jeff</a>
* @date 2019-05-16
*/
public interface WxCpTaskCardService {
/**
* <pre>
* 更新任务卡片消息状态
* 详情请见: https://work.weixin.qq.com/api/doc#90000/90135/91579
*
* 注意: 这个方法使用WxCpConfigStorage里的agentId
* </pre>
*
* @param userIds 企业的成员ID列表
* @param taskId 任务卡片ID
* @param clickedKey 已点击按钮的Key
*/
void update(List<String> userIds, String taskId, String clickedKey) throws WxErrorException;
}

weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpServiceAbstractImpl.java → weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/BaseWxCpServiceImpl.java Переглянути файл

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

import java.io.File;
import java.io.IOException;

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

import com.google.common.base.Joiner;
import com.google.gson.JsonArray;
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.WxError;
import me.chanjar.weixin.common.error.WxErrorException;
@@ -24,20 +18,23 @@ 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.cp.api.WxCpAgentService;
import me.chanjar.weixin.cp.api.WxCpChatService;
import me.chanjar.weixin.cp.api.WxCpDepartmentService;
import me.chanjar.weixin.cp.api.WxCpMediaService;
import me.chanjar.weixin.cp.api.WxCpMenuService;
import me.chanjar.weixin.cp.api.WxCpOAuth2Service;
import me.chanjar.weixin.cp.api.WxCpService;
import me.chanjar.weixin.cp.api.WxCpTagService;
import me.chanjar.weixin.cp.api.WxCpUserService;
import me.chanjar.weixin.cp.api.*;
import me.chanjar.weixin.cp.bean.WxCpMaJsCode2SessionResult;
import me.chanjar.weixin.cp.bean.WxCpMessage;
import me.chanjar.weixin.cp.bean.WxCpMessageSendResult;
import me.chanjar.weixin.cp.config.WxCpConfigStorage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

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

public abstract class WxCpServiceAbstractImpl<H, P> implements WxCpService, RequestHttp<H, P> {
/**
* @author chanjarster
*/
public abstract class BaseWxCpServiceImpl<H, P> implements WxCpService, RequestHttp<H, P> {
protected final Logger log = LoggerFactory.getLogger(this.getClass());

private WxCpUserService userService = new WxCpUserServiceImpl(this);
@@ -48,6 +45,8 @@ public abstract class WxCpServiceAbstractImpl<H, P> implements WxCpService, Requ
private WxCpOAuth2Service oauth2Service = new WxCpOAuth2ServiceImpl(this);
private WxCpTagService tagService = new WxCpTagServiceImpl(this);
private WxCpAgentService agentService = new WxCpAgentServiceImpl(this);
private WxCpOAService oaService = new WxCpOAServiceImpl(this);
private WxCpTaskCardService taskCardService = new WxCpTaskCardServiceImpl(this);

/**
* 全局的是否正在刷新access token的锁
@@ -59,14 +58,19 @@ public abstract class WxCpServiceAbstractImpl<H, P> implements WxCpService, Requ
*/
protected final Object globalJsapiTicketRefreshLock = new Object();

/**
* 全局的是否正在刷新agent的jsapi_ticket的锁
*/
protected final Object globalAgentJsapiTicketRefreshLock = new Object();

protected WxCpConfigStorage configStorage;

private WxSessionManager sessionManager = new StandardSessionManager();

protected WxSessionManager sessionManager = new StandardSessionManager();
/**
* 临时文件目录
*/
protected File tmpDirFile;
private File tmpDirFile;
private int retrySleepMillis = 1000;
private int maxRetryTimes = 5;

@@ -86,6 +90,30 @@ public abstract class WxCpServiceAbstractImpl<H, P> implements WxCpService, Requ
return getAccessToken(false);
}

@Override
public String getAgentJsapiTicket() throws WxErrorException {
return this.getAgentJsapiTicket(false);
}

@Override
public String getAgentJsapiTicket(boolean forceRefresh) throws WxErrorException {
if (forceRefresh) {
this.configStorage.expireAgentJsapiTicket();
}

if (this.configStorage.isAgentJsapiTicketExpired()) {
synchronized (this.globalAgentJsapiTicketRefreshLock) {
if (this.configStorage.isAgentJsapiTicketExpired()) {
String responseContent = this.get(WxCpService.GET_AGENT_CONFIG_TICKET, null);
JsonObject jsonObject = new JsonParser().parse(responseContent).getAsJsonObject();
this.configStorage.updateAgentJsapiTicket(jsonObject.get("ticket").getAsString(),
jsonObject.get("expires_in").getAsInt());
}
}
}

return this.configStorage.getAgentJsapiTicket();
}

@Override
public String getJsapiTicket() throws WxErrorException {
@@ -97,20 +125,18 @@ public abstract class WxCpServiceAbstractImpl<H, P> implements WxCpService, Requ
if (forceRefresh) {
this.configStorage.expireJsapiTicket();
}

if (this.configStorage.isJsapiTicketExpired()) {
synchronized (this.globalJsapiTicketRefreshLock) {
if (this.configStorage.isJsapiTicketExpired()) {
String url = "https://qyapi.weixin.qq.com/cgi-bin/get_jsapi_ticket";
String responseContent = execute(SimpleGetRequestExecutor.create(this), url, null);
JsonElement tmpJsonElement = new JsonParser().parse(responseContent);
JsonObject tmpJsonObject = tmpJsonElement.getAsJsonObject();
String jsapiTicket = tmpJsonObject.get("ticket").getAsString();
int expiresInSeconds = tmpJsonObject.get("expires_in").getAsInt();
this.configStorage.updateJsapiTicket(jsapiTicket,
expiresInSeconds);
String responseContent = this.get(WxCpService.GET_JSAPI_TICKET, null);
JsonObject tmpJsonObject = new JsonParser().parse(responseContent).getAsJsonObject();
this.configStorage.updateJsapiTicket(tmpJsonObject.get("ticket").getAsString(),
tmpJsonObject.get("expires_in").getAsInt());
}
}
}

return this.configStorage.getJsapiTicket();
}

@@ -139,18 +165,27 @@ public abstract class WxCpServiceAbstractImpl<H, P> implements WxCpService, Requ

@Override
public WxCpMessageSendResult messageSend(WxCpMessage message) throws WxErrorException {
String url = "https://qyapi.weixin.qq.com/cgi-bin/message/send";
Integer agentId = message.getAgentId();
if(null == agentId){
if (null == agentId) {
message.setAgentId(this.getWxCpConfigStorage().getAgentId());
}
return WxCpMessageSendResult.fromJson(this.post(url, message.toJson()));

return WxCpMessageSendResult.fromJson(this.post(WxCpService.MESSAGE_SEND, message.toJson()));
}

@Override
public WxCpMaJsCode2SessionResult jsCode2Session(String jsCode) throws WxErrorException {
Map<String, String> params = new HashMap<>(2);
params.put("js_code", jsCode);
params.put("grant_type", "authorization_code");

String result = this.get(JSCODE_TO_SESSION_URL, Joiner.on("&").withKeyValueSeparator("=").join(params));
return WxCpMaJsCode2SessionResult.fromJson(result);
}

@Override
public String[] getCallbackIp() throws WxErrorException {
String url = "https://qyapi.weixin.qq.com/cgi-bin/getcallbackip";
String responseContent = get(url, null);
String responseContent = get(WxCpService.GET_CALLBACK_IP, null);
JsonElement tmpJsonElement = new JsonParser().parse(responseContent);
JsonArray jsonArray = tmpJsonElement.getAsJsonObject().get("ip_list").getAsJsonArray();
String[] ips = new String[jsonArray.size()];
@@ -171,7 +206,7 @@ public abstract class WxCpServiceAbstractImpl<H, P> implements WxCpService, Requ
}

/**
* 向微信端发送请求,在这里执行的策略是当发生access_token过期时才去刷新,然后重新执行请求,而不是全局定时请求
* 向微信端发送请求,在这里执行的策略是当发生access_token过期时才去刷新,然后重新执行请求,而不是全局定时请求.
*/
@Override
public <T, E> T execute(RequestExecutor<T, E> executor, String uri, E data) throws WxErrorException {
@@ -285,25 +320,28 @@ public abstract class WxCpServiceAbstractImpl<H, P> implements WxCpService, Requ
this.sessionManager = sessionManager;
}

@Override
public WxSessionManager getSessionManager() {
return this.sessionManager;
}

@Override
public String replaceParty(String mediaId) throws WxErrorException {
String url = "https://qyapi.weixin.qq.com/cgi-bin/batch/replaceparty";
JsonObject jsonObject = new JsonObject();
jsonObject.addProperty("media_id", mediaId);
return post(url, jsonObject.toString());
return post(WxCpService.BATCH_REPLACE_PARTY, jsonObject.toString());
}

@Override
public String replaceUser(String mediaId) throws WxErrorException {
String url = "https://qyapi.weixin.qq.com/cgi-bin/batch/replaceuser";
JsonObject jsonObject = new JsonObject();
jsonObject.addProperty("media_id", mediaId);
return post(url, jsonObject.toString());
return post(WxCpService.BATCH_REPLACE_USER, jsonObject.toString());
}

@Override
public String getTaskResult(String joinId) throws WxErrorException {
String url = "https://qyapi.weixin.qq.com/cgi-bin/batch/getresult?jobid=" + joinId;
String url = WxCpService.BATCH_GET_RESULT + joinId;
return get(url, null);
}

@@ -350,6 +388,16 @@ public abstract class WxCpServiceAbstractImpl<H, P> implements WxCpService, Requ
return chatService;
}

@Override
public WxCpOAService getOAService() {
return oaService;
}

@Override
public WxCpTaskCardService getTaskCardService() {
return taskCardService;
}

@Override
public RequestHttp<?, ?> getRequestHttp() {
return this;

+ 43
- 22
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpChatServiceImpl.java Переглянути файл

@@ -1,36 +1,35 @@
package me.chanjar.weixin.cp.api.impl;

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

import org.apache.commons.lang3.StringUtils;

import com.google.gson.JsonParser;

import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.common.util.json.WxGsonBuilder;
import me.chanjar.weixin.cp.api.WxCpChatService;
import me.chanjar.weixin.cp.api.WxCpService;
import me.chanjar.weixin.cp.bean.WxCpAppChatMessage;
import me.chanjar.weixin.cp.bean.WxCpChat;
import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder;
import org.apache.commons.lang3.StringUtils;

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

/**
* 群聊服务实现
* 群聊服务实现.
*
* @author gaigeshen
*/
public class WxCpChatServiceImpl implements WxCpChatService {
public class WxCpChatServiceImpl implements WxCpChatService {
private static final JsonParser JSON_PARSER = new JsonParser();
private final WxCpService cpService;

private final WxCpService internalService;
/**
* 创建群聊服务实现的实例
*
* @param internalService 企业微信的服务
* 创建群聊服务实现的实例.
*
* @param cpService 企业微信的服务
*/
public WxCpChatServiceImpl(WxCpService internalService) {
this.internalService = internalService;
WxCpChatServiceImpl(WxCpService cpService) {
this.cpService = cpService;
}

@Override
@@ -48,12 +47,18 @@ public class WxCpChatServiceImpl implements WxCpChatService {
if (StringUtils.isNotBlank(chatId)) {
data.put("chatid", chatId);
}
String result = internalService.post("https://qyapi.weixin.qq.com/cgi-bin/appchat/create", WxGsonBuilder.create().toJson(data));
String result = this.cpService.post(APPCHAT_CREATE, WxGsonBuilder.create().toJson(data));
return new JsonParser().parse(result).getAsJsonObject().get("chatid").getAsString();
}

@Override
public void chatUpdate(String chatId, String name, String owner, List<String> usersToAdd, List<String> usersToDelete) throws WxErrorException {
public String create(String name, String owner, List<String> users, String chatId) throws WxErrorException {
return chatCreate(name, owner, users, chatId);
}

@Override
public void chatUpdate(String chatId, String name, String owner, List<String> usersToAdd, List<String> usersToDelete)
throws WxErrorException {
Map<String, Object> data = new HashMap<>(5);
if (StringUtils.isNotBlank(chatId)) {
data.put("chatid", chatId);
@@ -70,14 +75,30 @@ public class WxCpChatServiceImpl implements WxCpChatService {
if (usersToDelete != null && !usersToDelete.isEmpty()) {
data.put("del_user_list", usersToDelete);
}
internalService.post("https://qyapi.weixin.qq.com/cgi-bin/appchat/update", WxGsonBuilder.create().toJson(data));

this.cpService.post(APPCHAT_UPDATE, WxGsonBuilder.create().toJson(data));
}

@Override
public void update(String chatId, String name, String owner, List<String> usersToAdd, List<String> usersToDelete) throws WxErrorException {
chatUpdate(chatId, name, owner, usersToAdd, usersToDelete);
}

@Override
public WxCpChat chatGet(String chatId) throws WxErrorException {
String result = internalService.get("https://qyapi.weixin.qq.com/cgi-bin/appchat/get?chatid=" + chatId, null);
return WxCpGsonBuilder.create().fromJson(
new JsonParser().parse(result).getAsJsonObject().getAsJsonObject("chat_info").toString(), WxCpChat.class);
String result = this.cpService.get(APPCHAT_GET_CHATID + chatId, null);
return WxCpGsonBuilder.create()
.fromJson(JSON_PARSER.parse(result).getAsJsonObject().getAsJsonObject("chat_info").toString(), WxCpChat.class);
}

@Override
public WxCpChat get(String chatId) throws WxErrorException {
return chatGet(chatId);
}

@Override
public void sendMsg(WxCpAppChatMessage message) throws WxErrorException {
this.cpService.post("https://qyapi.weixin.qq.com/cgi-bin/appchat/send", message.toJson());
}

}

+ 2
- 2
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpDepartmentServiceImpl.java Переглянути файл

@@ -28,11 +28,11 @@ public class WxCpDepartmentServiceImpl implements WxCpDepartmentService {
}

@Override
public Integer create(WxCpDepart depart) throws WxErrorException {
public Long create(WxCpDepart depart) throws WxErrorException {
String url = "https://qyapi.weixin.qq.com/cgi-bin/department/create";
String responseContent = this.mainService.post(url, depart.toJson());
JsonElement tmpJsonElement = new JsonParser().parse(responseContent);
return GsonHelper.getAsInteger(tmpJsonElement.getAsJsonObject().get("id"));
return GsonHelper.getAsLong(tmpJsonElement.getAsJsonObject().get("id"));
}

@Override


+ 165
- 0
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpOAServiceImpl.java Переглянути файл

@@ -0,0 +1,165 @@
package me.chanjar.weixin.cp.api.impl;

import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
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.cp.api.WxCpOAService;
import me.chanjar.weixin.cp.api.WxCpService;
import me.chanjar.weixin.cp.bean.WxCpApprovalDataResult;
import me.chanjar.weixin.cp.bean.WxCpCheckinData;
import me.chanjar.weixin.cp.bean.WxCpCheckinOption;
import me.chanjar.weixin.cp.bean.WxCpDialRecord;
import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder;

import java.util.Date;
import java.util.List;

/**
* @author Element
* @Package me.chanjar.weixin.cp.api.impl
* @date 2019-04-06 11:20
* @Description: TODO
*/
public class WxCpOAServiceImpl implements WxCpOAService {

private WxCpService mainService;

public WxCpOAServiceImpl(WxCpService mainService) {
this.mainService = mainService;
}

@Override
public List<WxCpCheckinData> getCheckinData(Integer openCheckinDataType, Date starttime, Date endtime, List<String> userIdList) throws WxErrorException {

if (starttime == null || endtime == null) {
throw new RuntimeException("starttime and endtime can't be null");
}

if (userIdList == null || userIdList.size() > 100) {
throw new RuntimeException("用户列表不能为空,不超过100个,若用户超过100个,请分批获取");
}

long endtimestamp = endtime.getTime() / 1000L;
long starttimestamp = starttime.getTime() / 1000L;

if (endtimestamp - starttimestamp < 0 || endtimestamp - starttimestamp >= 30 * 24 * 60 * 60) {
throw new RuntimeException("获取记录时间跨度不超过一个月");
}

String url = "https://qyapi.weixin.qq.com/cgi-bin/checkin/getcheckindata";

JsonObject jsonObject = new JsonObject();
JsonArray jsonArray = new JsonArray();

jsonObject.addProperty("opencheckindatatype", openCheckinDataType);
jsonObject.addProperty("starttime", starttimestamp);
jsonObject.addProperty("endtime", endtimestamp);

for (String userid : userIdList) {
jsonArray.add(userid);
}

jsonObject.add("useridlist", jsonArray);

String responseContent = this.mainService.post(url, jsonObject.toString());
JsonElement tmpJsonElement = new JsonParser().parse(responseContent);
return WxCpGsonBuilder.create()
.fromJson(
tmpJsonElement.getAsJsonObject().get("checkindata"),
new TypeToken<List<WxCpCheckinData>>() {
}.getType()
);
}

@Override
public List<WxCpCheckinOption> getCheckinOption(Date datetime, List<String> userIdList) throws WxErrorException {
String url = "https://qyapi.weixin.qq.com/cgi-bin/checkin/getcheckinoption";
if (datetime == null) {
throw new RuntimeException("datetime can't be null");
}

if (userIdList == null || userIdList.size() > 100) {
throw new RuntimeException("用户列表不能为空,不超过100个,若用户超过100个,请分批获取");
}

JsonArray jsonArray = new JsonArray();
for (String userid : userIdList) {
jsonArray.add(userid);
}

JsonObject jsonObject = new JsonObject();
jsonObject.addProperty("datetime", datetime.getTime() / 1000L);
jsonObject.add("useridlist", jsonArray);

String responseContent = this.mainService.post(url, jsonObject.toString());
JsonElement tmpJsonElement = new JsonParser().parse(responseContent);

return WxCpGsonBuilder.create()
.fromJson(
tmpJsonElement.getAsJsonObject().get("info"),
new TypeToken<List<WxCpCheckinOption>>() {
}.getType()
);
}

@Override
public WxCpApprovalDataResult getApprovalData(Date starttime, Date endtime, Long nextSpnum) throws WxErrorException {

String url = "https://qyapi.weixin.qq.com/cgi-bin/corp/getapprovaldata";
JsonObject jsonObject = new JsonObject();
jsonObject.addProperty("starttime", starttime.getTime() / 1000L);
jsonObject.addProperty("endtime", endtime.getTime() / 1000L);
if (nextSpnum != null) {
jsonObject.addProperty("next_spnum", nextSpnum);
}

String responseContent = this.mainService.post(url, jsonObject.toString());
return WxCpGsonBuilder.create().fromJson(responseContent, WxCpApprovalDataResult.class);
}

@Override
public List<WxCpDialRecord> getDialRecord(Date starttime, Date endtime, Integer offset, Integer limit) throws WxErrorException {

String url = "https://qyapi.weixin.qq.com/cgi-bin/dial/get_dial_record";
JsonObject jsonObject = new JsonObject();

if (offset == null) {
offset = 0;
}

if (limit == null || limit <= 0) {
limit = 100;
}

jsonObject.addProperty("offset", offset);
jsonObject.addProperty("limit", limit);

if (starttime != null && endtime != null) {

long endtimestamp = endtime.getTime() / 1000L;
long starttimestamp = starttime.getTime() / 1000L;

if (endtimestamp - starttimestamp < 0 || endtimestamp - starttimestamp >= 30 * 24 * 60 * 60) {
throw new RuntimeException("受限于网络传输,起止时间的最大跨度为30天,如超过30天,则以结束时间为基准向前取30天进行查询");
}

jsonObject.addProperty("start_time", starttimestamp);
jsonObject.addProperty("end_time", endtimestamp);


}

String responseContent = this.mainService.post(url, jsonObject.toString());
JsonElement tmpJsonElement = new JsonParser().parse(responseContent);

return WxCpGsonBuilder.create()
.fromJson(
tmpJsonElement.getAsJsonObject().get("record"),
new TypeToken<List<WxCpDialRecord>>() {
}.getType()
);
}
}

+ 32
- 31
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpServiceApacheHttpClientImpl.java Переглянути файл

@@ -8,6 +8,7 @@ import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.common.util.http.HttpType;
import me.chanjar.weixin.common.util.http.apache.ApacheHttpClientBuilder;
import me.chanjar.weixin.common.util.http.apache.DefaultApacheHttpClientBuilder;
import me.chanjar.weixin.cp.api.WxCpOAService;
import me.chanjar.weixin.cp.config.WxCpConfigStorage;
import org.apache.http.HttpHost;
import org.apache.http.client.config.RequestConfig;
@@ -18,7 +19,7 @@ import org.apache.http.impl.client.CloseableHttpClient;

import java.io.IOException;

public class WxCpServiceApacheHttpClientImpl extends WxCpServiceAbstractImpl<CloseableHttpClient, HttpHost> {
public class WxCpServiceApacheHttpClientImpl extends BaseWxCpServiceImpl<CloseableHttpClient, HttpHost> {
protected CloseableHttpClient httpClient;
protected HttpHost httpProxy;

@@ -39,37 +40,37 @@ public class WxCpServiceApacheHttpClientImpl extends WxCpServiceAbstractImpl<Clo

@Override
public String getAccessToken(boolean forceRefresh) throws WxErrorException {
if (this.configStorage.isAccessTokenExpired() || forceRefresh) {
synchronized (this.globalAccessTokenRefreshLock) {
if (this.configStorage.isAccessTokenExpired()) {
String url = "https://qyapi.weixin.qq.com/cgi-bin/gettoken?"
+ "&corpid=" + this.configStorage.getCorpId()
+ "&corpsecret=" + this.configStorage.getCorpSecret();
try {
HttpGet httpGet = new HttpGet(url);
if (this.httpProxy != null) {
RequestConfig config = RequestConfig.custom()
.setProxy(this.httpProxy).build();
httpGet.setConfig(config);
}
String resultContent = null;
try (CloseableHttpClient httpclient = getRequestHttpClient();
CloseableHttpResponse response = httpclient.execute(httpGet)) {
resultContent = new BasicResponseHandler().handleResponse(response);
} finally {
httpGet.releaseConnection();
}
WxError error = WxError.fromJson(resultContent, WxType.CP);
if (error.getErrorCode() != 0) {
throw new WxErrorException(error);
}
WxAccessToken accessToken = WxAccessToken.fromJson(resultContent);
this.configStorage.updateAccessToken(
accessToken.getAccessToken(), accessToken.getExpiresIn());
} catch (IOException e) {
throw new RuntimeException(e);
}
if (!this.configStorage.isAccessTokenExpired() && !forceRefresh) {
return this.configStorage.getAccessToken();
}

synchronized (this.globalAccessTokenRefreshLock) {
String url = "https://qyapi.weixin.qq.com/cgi-bin/gettoken?"
+ "&corpid=" + this.configStorage.getCorpId()
+ "&corpsecret=" + this.configStorage.getCorpSecret();
try {
HttpGet httpGet = new HttpGet(url);
if (this.httpProxy != null) {
RequestConfig config = RequestConfig.custom()
.setProxy(this.httpProxy).build();
httpGet.setConfig(config);
}
String resultContent;
try (CloseableHttpClient httpclient = getRequestHttpClient();
CloseableHttpResponse response = httpclient.execute(httpGet)) {
resultContent = new BasicResponseHandler().handleResponse(response);
} finally {
httpGet.releaseConnection();
}
WxError error = WxError.fromJson(resultContent, WxType.CP);
if (error.getErrorCode() != 0) {
throw new WxErrorException(error);
}

WxAccessToken accessToken = WxAccessToken.fromJson(resultContent);
this.configStorage.updateAccessToken(accessToken.getAccessToken(), accessToken.getExpiresIn());
} catch (IOException e) {
throw new RuntimeException(e);
}
}
return this.configStorage.getAccessToken();


+ 21
- 22
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpServiceJoddHttpImpl.java Переглянути файл

@@ -8,7 +8,7 @@ import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.common.util.http.HttpType;
import me.chanjar.weixin.cp.config.WxCpConfigStorage;

public class WxCpServiceJoddHttpImpl extends WxCpServiceAbstractImpl<HttpConnectionProvider, ProxyInfo> {
public class WxCpServiceJoddHttpImpl extends BaseWxCpServiceImpl<HttpConnectionProvider, ProxyInfo> {
protected HttpConnectionProvider httpClient;
protected ProxyInfo httpProxy;

@@ -30,30 +30,29 @@ public class WxCpServiceJoddHttpImpl extends WxCpServiceAbstractImpl<HttpConnect

@Override
public String getAccessToken(boolean forceRefresh) throws WxErrorException {
if (this.configStorage.isAccessTokenExpired() || forceRefresh) {
synchronized (this.globalAccessTokenRefreshLock) {
if (this.configStorage.isAccessTokenExpired()) {
String url = "https://qyapi.weixin.qq.com/cgi-bin/gettoken?"
+ "&corpid=" + this.configStorage.getCorpId()
+ "&corpsecret=" + this.configStorage.getCorpSecret();
if (!this.configStorage.isAccessTokenExpired() && !forceRefresh) {
return this.configStorage.getAccessToken();
}

synchronized (this.globalAccessTokenRefreshLock) {
String url = "https://qyapi.weixin.qq.com/cgi-bin/gettoken?"
+ "&corpid=" + this.configStorage.getCorpId()
+ "&corpsecret=" + this.configStorage.getCorpSecret();

HttpRequest request = HttpRequest.get(url);
if (this.httpProxy != null) {
httpClient.useProxy(this.httpProxy);
}
request.withConnectionProvider(httpClient);
HttpResponse response = request.send();
HttpRequest request = HttpRequest.get(url);
if (this.httpProxy != null) {
httpClient.useProxy(this.httpProxy);
}
request.withConnectionProvider(httpClient);
HttpResponse response = request.send();

String resultContent = response.bodyText();
WxError error = WxError.fromJson(resultContent, WxType.CP);
if (error.getErrorCode() != 0) {
throw new WxErrorException(error);
}
WxAccessToken accessToken = WxAccessToken.fromJson(resultContent);
this.configStorage.updateAccessToken(
accessToken.getAccessToken(), accessToken.getExpiresIn());
}
String resultContent = response.bodyText();
WxError error = WxError.fromJson(resultContent, WxType.CP);
if (error.getErrorCode() != 0) {
throw new WxErrorException(error);
}
WxAccessToken accessToken = WxAccessToken.fromJson(resultContent);
this.configStorage.updateAccessToken(accessToken.getAccessToken(), accessToken.getExpiresIn());
}
return this.configStorage.getAccessToken();
}


+ 26
- 27
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpServiceOkHttpImpl.java Переглянути файл

@@ -11,7 +11,7 @@ import okhttp3.*;

import java.io.IOException;

public class WxCpServiceOkHttpImpl extends WxCpServiceAbstractImpl<OkHttpClient, OkHttpProxyInfo> {
public class WxCpServiceOkHttpImpl extends BaseWxCpServiceImpl<OkHttpClient, OkHttpProxyInfo> {
protected OkHttpClient httpClient;
protected OkHttpProxyInfo httpProxy;

@@ -33,34 +33,33 @@ public class WxCpServiceOkHttpImpl extends WxCpServiceAbstractImpl<OkHttpClient,

@Override
public String getAccessToken(boolean forceRefresh) throws WxErrorException {
this.log.debug("WxCpServiceOkHttpImpl is running");
if (this.configStorage.isAccessTokenExpired() || forceRefresh) {
synchronized (this.globalAccessTokenRefreshLock) {
if (this.configStorage.isAccessTokenExpired()) {
String url = "https://qyapi.weixin.qq.com/cgi-bin/gettoken?"
+ "&corpid=" + this.configStorage.getCorpId()
+ "&corpsecret=" + this.configStorage.getCorpSecret();
//得到httpClient
OkHttpClient client = getRequestHttpClient();
//请求的request
Request request = new Request.Builder().url(url).get().build();
String resultContent = null;
try {
Response response = client.newCall(request).execute();
resultContent = response.body().string();
} catch (IOException e) {
this.log.error(e.getMessage(), e);
}
if (!this.configStorage.isAccessTokenExpired() && !forceRefresh) {
return this.configStorage.getAccessToken();
}

synchronized (this.globalAccessTokenRefreshLock) {
String url = "https://qyapi.weixin.qq.com/cgi-bin/gettoken?"
+ "&corpid=" + this.configStorage.getCorpId()
+ "&corpsecret=" + this.configStorage.getCorpSecret();
//得到httpClient
OkHttpClient client = getRequestHttpClient();
//请求的request
Request request = new Request.Builder().url(url).get().build();
String resultContent = null;
try {
Response response = client.newCall(request).execute();
resultContent = response.body().string();
} catch (IOException e) {
this.log.error(e.getMessage(), e);
}

WxError error = WxError.fromJson(resultContent, WxType.CP);
if (error.getErrorCode() != 0) {
throw new WxErrorException(error);
}
WxAccessToken accessToken = WxAccessToken.fromJson(resultContent);
this.configStorage.updateAccessToken(accessToken.getAccessToken(),
accessToken.getExpiresIn());
WxError error = WxError.fromJson(resultContent, WxType.CP);
if (error.getErrorCode() != 0) {
throw new WxErrorException(error);
}
}
WxAccessToken accessToken = WxAccessToken.fromJson(resultContent);
this.configStorage.updateAccessToken(accessToken.getAccessToken(),
accessToken.getExpiresIn());
}
return this.configStorage.getAccessToken();
}


+ 39
- 0
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpTaskCardServiceImpl.java Переглянути файл

@@ -0,0 +1,39 @@
package me.chanjar.weixin.cp.api.impl;

import lombok.RequiredArgsConstructor;
import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.common.util.json.WxGsonBuilder;
import me.chanjar.weixin.cp.api.WxCpService;
import me.chanjar.weixin.cp.api.WxCpTaskCardService;

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

/**
* <pre>
* 任务卡片管理接口.
* Created by Jeff on 2019-05-16.
* </pre>
*
* @author <a href="https://github.com/domainname">Jeff</a>
* @date 2019-05-16
*/
@RequiredArgsConstructor
public class WxCpTaskCardServiceImpl implements WxCpTaskCardService {
private final WxCpService mainService;

@Override
public void update(List<String> userIds, String taskId, String clickedKey) throws WxErrorException {
Integer agentId = this.mainService.getWxCpConfigStorage().getAgentId();

Map<String, Object> data = new HashMap<>(4);
data.put("userids", userIds);
data.put("agentid", agentId);
data.put("task_id", taskId);
data.put("clicked_key", clickedKey);

String url = "https://qyapi.weixin.qq.com/cgi-bin/message/update_taskcard";
this.mainService.post(url, WxGsonBuilder.create().toJson(data));
}
}

+ 163
- 0
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpAppChatMessage.java Переглянути файл

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

import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import me.chanjar.weixin.cp.WxCpConsts.AppChatMsgType;
import me.chanjar.weixin.cp.bean.article.MpnewsArticle;
import me.chanjar.weixin.cp.bean.article.NewArticle;

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

/**
* <pre>
* 应用推送消息
* Created by Binary Wang on 2019/1/26.
* </pre>
*
* @author <a href="https://github.com/binarywang">Binary Wang</a>
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class WxCpAppChatMessage implements Serializable {
private static final long serialVersionUID = -5469013416372240229L;

private String msgType;
private String content;
private String chatId;
private String mediaId;
private String title;
private String description;
private Boolean safe;
private String url;
private String btnTxt;
private List<NewArticle> articles;
private List<MpnewsArticle> mpnewsArticles;

/**
* 构建文本消息.
*/
public static WxCpAppChatMessage buildTextMsg(String chatId, String content, boolean safe) {
final WxCpAppChatMessage message = new WxCpAppChatMessage();
message.setMsgType(AppChatMsgType.TEXT);
message.setContent(content);
message.setChatId(chatId);
message.setSafe(safe);
return message;
}

/**
* 生成json字符串.
*/
public String toJson() {
JsonObject messageJson = new JsonObject();
messageJson.addProperty("msgtype", this.getMsgType());
messageJson.addProperty("chatid", this.getChatId());

if (this.getSafe() != null && this.getSafe()) {
messageJson.addProperty("safe", 1);
}

this.handleMsgType(messageJson);

return messageJson.toString();
}

private void handleMsgType(JsonObject messageJson) {
switch (this.getMsgType()) {
case AppChatMsgType.TEXT: {
JsonObject text = new JsonObject();
text.addProperty("content", this.getContent());
messageJson.add("text", text);
break;
}
case AppChatMsgType.MARKDOWN: {
JsonObject text = new JsonObject();
text.addProperty("content", this.getContent());
messageJson.add("markdown", text);
break;
}
case AppChatMsgType.TEXTCARD: {
JsonObject text = new JsonObject();
text.addProperty("title", this.getTitle());
text.addProperty("description", this.getDescription());
text.addProperty("url", this.getUrl());
text.addProperty("btntxt", this.getBtnTxt());
messageJson.add("textcard", text);
break;
}
case AppChatMsgType.IMAGE: {
JsonObject image = new JsonObject();
image.addProperty("media_id", this.getMediaId());
messageJson.add("image", image);
break;
}
case AppChatMsgType.FILE: {
JsonObject image = new JsonObject();
image.addProperty("media_id", this.getMediaId());
messageJson.add("file", image);
break;
}
case AppChatMsgType.VOICE: {
JsonObject voice = new JsonObject();
voice.addProperty("media_id", this.getMediaId());
messageJson.add("voice", voice);
break;
}
case AppChatMsgType.VIDEO: {
JsonObject video = new JsonObject();
video.addProperty("media_id", this.getMediaId());
video.addProperty("title", this.getTitle());
video.addProperty("description", this.getDescription());
messageJson.add("video", video);
break;
}
case AppChatMsgType.NEWS: {
JsonObject newsJsonObject = new JsonObject();
JsonArray articleJsonArray = new JsonArray();
for (NewArticle article : this.getArticles()) {
JsonObject articleJson = new JsonObject();
articleJson.addProperty("title", article.getTitle());
articleJson.addProperty("description", article.getDescription());
articleJson.addProperty("url", article.getUrl());
articleJson.addProperty("picurl", article.getPicUrl());
articleJsonArray.add(articleJson);
}
newsJsonObject.add("articles", articleJsonArray);
messageJson.add("news", newsJsonObject);
break;
}
case AppChatMsgType.MPNEWS: {
JsonObject newsJsonObject = new JsonObject();
if (this.getMediaId() != null) {
newsJsonObject.addProperty("media_id", this.getMediaId());
} else {
JsonArray articleJsonArray = new JsonArray();
for (MpnewsArticle article : this.getMpnewsArticles()) {
JsonObject articleJson = new JsonObject();
articleJson.addProperty("title", article.getTitle());
articleJson.addProperty("thumb_media_id", article.getThumbMediaId());
articleJson.addProperty("author", article.getAuthor());
articleJson.addProperty("content_source_url", article.getContentSourceUrl());
articleJson.addProperty("content", article.getContent());
articleJson.addProperty("digest", article.getDigest());
articleJsonArray.add(articleJson);
}

newsJsonObject.add("articles", articleJsonArray);
}
messageJson.add("mpnews", newsJsonObject);
break;
}
default: {
//do nothing
}
}
}
}

+ 69
- 0
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpApprovalDataResult.java Переглянути файл

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

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

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

/**
* @author Element
* @Package me.chanjar.weixin.cp.bean
* @date 2019-04-06 14:36
* @Description: 企业微信 OA 审批数据
*/
@Data
public class WxCpApprovalDataResult implements Serializable {
private static final long serialVersionUID = -1046940445840716590L;

@SerializedName("errcode")
private Integer errCode;

@SerializedName("errmsg")
private String errMsg;

private Integer count;

private Integer total;

@SerializedName("next_spnum")
private Long nextSpnum;

private WxCpApprovalData[] data;


@Data
public static class WxCpApprovalData implements Serializable{

private static final long serialVersionUID = -3051785319608491640L;

private String spname;

@SerializedName("apply_name")
private String applyName;

@SerializedName("apply_org")
private String applyOrg;

@SerializedName("approval_name")
private String[] approvalName;

@SerializedName("notify_name")
private String[] notifyName;

@SerializedName("sp_status")
private Integer spStatus;

@SerializedName("sp_num")
private Long spNum;

@SerializedName("apply_time")
private Long applyTime;

@SerializedName("apply_user_id")
private String applyUserId;

@SerializedName("comm")
private Map<String,String> comm;
}
}

+ 51
- 0
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpCheckinData.java Переглянути файл

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

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

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

/**
* @author Element
* @Package me.chanjar.weixin.cp.bean
* @date 2019-04-06 11:01
* @Description: 企业微信打卡数据
*/
@Data
public class WxCpCheckinData implements Serializable {

private static final long serialVersionUID = 1915820330847799605L;

@SerializedName("userid")
private String userId;

@SerializedName("groupname")
private String groupName;

@SerializedName("checkin_type")
private String checkinType;

@SerializedName("exception_type")
private String exceptionType;

@SerializedName("checkin_time")
private Long checkinTime;

@SerializedName("location_title")
private String locationTitle;

@SerializedName("location_detail")
private String locationDetail;

@SerializedName("wifiname")
private String wifiName;

@SerializedName("wifimac")
private String wifiMAC;

private String notes;

@SerializedName("mediaids")
private List<String> mediaIds;
}

+ 151
- 0
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpCheckinOption.java Переглянути файл

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

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

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

/**
* @author Element
* @Package me.chanjar.weixin.cp.bean
* @date 2019-04-06 13:22
* @Description: 企业微信打卡规则
*/
@Data
public class WxCpCheckinOption implements Serializable {
private static final long serialVersionUID = -1964233697990417482L;

@SerializedName("userid")
private String userId;

private Group group;


@Data
public static class CheckinDate implements Serializable {

private static final long serialVersionUID = -5601722383347110974L;

private List<Integer> workdays;

@SerializedName("checkintime")
private CheckinTime[] checkinTime;

@SerializedName("flex_time")
private Long flexTime;

@SerializedName("noneed_offwork")
private Boolean noneedOffwork;

@SerializedName("limit_aheadtime")
private Long limitAheadtime;
}

@Data
public static class CheckinTime implements Serializable {

private static final long serialVersionUID = -8579954143265336276L;

@SerializedName("work_sec")
private Long workSec;

@SerializedName("off_work_sec")
private Long offWorkSec;

@SerializedName("remind_work_sec")
private Long remindWorkSec;

@SerializedName("remind_off_work_sec")
private Long remindOffWorkSec;
}

@Data
public static class Group implements Serializable {

private static final long serialVersionUID = -5888406969613403044L;

@SerializedName("groupid")
private Long id;

@SerializedName("groupname")
private String name;

@SerializedName("grouptype")
private Integer type;

@SerializedName("checkindate")
private List<CheckinDate> checkinDate;

@SerializedName("spe_workdays")
private List<SpeDay> speWorkdays;

@SerializedName("spe_offdays")
private List<SpeDay> speOffdays;

@SerializedName("sync_holidays")
private Boolean syncHolidays;

@SerializedName("need_photo")
private Boolean needPhoto;

@SerializedName("note_can_use_local_pic")
private Boolean note_can_use_local_pic;

@SerializedName("allow_checkin_offworkday")
private Boolean allow_checkin_offworkday;

@SerializedName("allow_apply_offworkday")
private Boolean allow_apply_offworkday;

@SerializedName("wifimac_infos")
private List<WifiMACInfo> wifiMACInfos;

@SerializedName("loc_infos")
private List<LocInfo> locInfos;

}

@Data
public static class WifiMACInfo implements Serializable{

private static final long serialVersionUID = -4657809185716627368L;

@SerializedName("wifiname")
private String name;

@SerializedName("wifimac")
private String mac;
}

@Data
public static class LocInfo implements Serializable{

private static final long serialVersionUID = -618965280668099608L;

private Long lat;
private Long lng;

@SerializedName("loc_title")
private String title;

@SerializedName("loc_detail")
private String detail;

private Long distance;
}

@Data
public static class SpeDay implements Serializable{

private static final long serialVersionUID = -3538818921359212748L;

private Long timestamp;
private String notes;

@SerializedName("checkintime")
private List<CheckinTime> checkinTime;

}

}

+ 73
- 0
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpDialRecord.java Переглянути файл

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

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

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

/**
* @author Element
* @Package me.chanjar.weixin.cp.bean
* @date 2019-04-06 15:38
* @Description: 公费电话拨打记录
*/
@Data
public class WxCpDialRecord implements Serializable {

private static final long serialVersionUID = 4178886812949929116L;
@SerializedName("call_time")
private Long callTime;

/**
* 总通话时长,单位为分钟
*/
@SerializedName("total_duration")
private Integer totalDuration;

/**
* 通话类型,1-单人通话 2-多人通话
*/
@SerializedName("call_type")
private Integer callType;

private Caller caller;

private List<Callee> callee;

/**
* 主叫信息
*/
@Data
public static class Caller implements Serializable{

private static final long serialVersionUID = 4792200404338145607L;

@SerializedName("userid")
private String userId;

private Integer duration;
}

/**
* 被叫信息
*/
@Data
public static class Callee implements Serializable{

private static final long serialVersionUID = 2390963671336179550L;

/**
* 被叫用户的userid,当被叫用户为企业内用户时返回
*/
@SerializedName("userid")
private String userId;

/**
* 被叫用户的号码,当被叫用户为外部用户时返回
*/
private String phone;

private Integer duration;
}
}

+ 1
- 13
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpInviteResult.java Переглянути файл

@@ -37,7 +37,7 @@ public class WxCpInviteResult implements Serializable {
private String errMsg;

@SerializedName("invaliduser")
private String invalidUsers;
private String[] invalidUsers;

@SerializedName("invalidparty")
private String[] invalidParties;
@@ -45,16 +45,4 @@ public class WxCpInviteResult implements Serializable {
@SerializedName("invalidtag")
private String[] invalidTags;

public List<String> getInvalidUserList() {
return this.content2List(this.invalidUsers);
}

private List<String> content2List(String content) {
if (StringUtils.isBlank(content)) {
return Collections.emptyList();
}

return Splitter.on("|").splitToList(content);
}

}

+ 33
- 0
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpMaJsCode2SessionResult.java Переглянути файл

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

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

import java.io.Serializable;

/**
* <pre>
* 小程序登录凭证校验
* 文档地址:https://work.weixin.qq.com/api/doc#90000/90136/90289/wx.qy.login
* </pre>
* @author <a href="https://github.com/binarywang">Binary Wang</a>
*/
@Data
public class WxCpMaJsCode2SessionResult implements Serializable {
private static final long serialVersionUID = 6229609023682814765L;

@SerializedName("session_key")
private String sessionKey;

@SerializedName("userid")
private String userId;

@SerializedName("corpid")
private String corpId;

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

}

+ 195
- 22
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpMessage.java Переглянути файл

@@ -1,22 +1,18 @@
package me.chanjar.weixin.cp.bean;

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

import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import lombok.Data;
import me.chanjar.weixin.common.api.WxConsts;
import me.chanjar.weixin.common.api.WxConsts.KefuMsgType;
import me.chanjar.weixin.cp.bean.article.MpnewsArticle;
import me.chanjar.weixin.cp.bean.article.NewArticle;
import me.chanjar.weixin.cp.bean.messagebuilder.FileBuilder;
import me.chanjar.weixin.cp.bean.messagebuilder.ImageBuilder;
import me.chanjar.weixin.cp.bean.messagebuilder.MpnewsBuilder;
import me.chanjar.weixin.cp.bean.messagebuilder.NewsBuilder;
import me.chanjar.weixin.cp.bean.messagebuilder.TextBuilder;
import me.chanjar.weixin.cp.bean.messagebuilder.TextCardBuilder;
import me.chanjar.weixin.cp.bean.messagebuilder.VideoBuilder;
import me.chanjar.weixin.cp.bean.messagebuilder.VoiceBuilder;
import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder;
import me.chanjar.weixin.cp.bean.messagebuilder.*;
import me.chanjar.weixin.cp.bean.taskcard.TaskCardButton;
import org.apache.commons.lang3.StringUtils;

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

/**
* 消息.
@@ -45,6 +41,12 @@ public class WxCpMessage implements Serializable {
private List<NewArticle> articles = new ArrayList<>();
private List<MpnewsArticle> mpnewsArticles = new ArrayList<>();

/**
* 任务卡片特有的属性
*/
private String taskId;
private List<TaskCardButton> taskButtons = new ArrayList<>();

/**
* 获得文本消息builder.
*/
@@ -94,6 +96,13 @@ public class WxCpMessage implements Serializable {
return new MpnewsBuilder();
}

/**
* 获得markdown消息builder.
*/
public static MarkdownMsgBuilder MARKDOWN() {
return new MarkdownMsgBuilder();
}

/**
* 获得文件消息builder.
*/
@@ -101,17 +110,26 @@ public class WxCpMessage implements Serializable {
return new FileBuilder();
}

/**
* 获得任务卡片消息builder.
*/
public static TaskCardBuilder TASKCARD() {
return new TaskCardBuilder();
}


/**
* <pre>
* 请使用
* {@link WxConsts.KefuMsgType#TEXT}
* {@link WxConsts.KefuMsgType#IMAGE}
* {@link WxConsts.KefuMsgType#VOICE}
* {@link WxConsts.KefuMsgType#MUSIC}
* {@link WxConsts.KefuMsgType#VIDEO}
* {@link WxConsts.KefuMsgType#NEWS}
* {@link WxConsts.KefuMsgType#MPNEWS}
* {@link KefuMsgType#TEXT}
* {@link KefuMsgType#IMAGE}
* {@link KefuMsgType#VOICE}
* {@link KefuMsgType#MUSIC}
* {@link KefuMsgType#VIDEO}
* {@link KefuMsgType#NEWS}
* {@link KefuMsgType#MPNEWS}
* {@link KefuMsgType#MARKDOWN}
* {@link KefuMsgType#TASKCARD}
* </pre>
*
* @param msgType 消息类型
@@ -121,7 +139,162 @@ public class WxCpMessage implements Serializable {
}

public String toJson() {
return WxCpGsonBuilder.create().toJson(this);
JsonObject messageJson = new JsonObject();
if (this.getAgentId() != null) {
messageJson.addProperty("agentid", this.getAgentId());
}

if (StringUtils.isNotBlank(this.getToUser())) {
messageJson.addProperty("touser", this.getToUser());
}

messageJson.addProperty("msgtype", this.getMsgType());

if (StringUtils.isNotBlank(this.getToParty())) {
messageJson.addProperty("toparty", this.getToParty());
}

if (StringUtils.isNotBlank(this.getToTag())) {
messageJson.addProperty("totag", this.getToTag());
}

this.handleMsgType(messageJson);

if (StringUtils.isNotBlank(this.getSafe())) {
messageJson.addProperty("safe", this.getSafe());
}

return messageJson.toString();
}

private void handleMsgType(JsonObject messageJson) {
switch (this.getMsgType()) {
case KefuMsgType.TEXT: {
JsonObject text = new JsonObject();
text.addProperty("content", this.getContent());
messageJson.add("text", text);
break;
}
case KefuMsgType.MARKDOWN: {
JsonObject text = new JsonObject();
text.addProperty("content", this.getContent());
messageJson.add("markdown", text);
break;
}
case KefuMsgType.TEXTCARD: {
JsonObject text = new JsonObject();
text.addProperty("title", this.getTitle());
text.addProperty("description", this.getDescription());
text.addProperty("url", this.getUrl());
text.addProperty("btntxt", this.getBtnTxt());
messageJson.add("textcard", text);
break;
}
case KefuMsgType.IMAGE: {
JsonObject image = new JsonObject();
image.addProperty("media_id", this.getMediaId());
messageJson.add("image", image);
break;
}
case KefuMsgType.FILE: {
JsonObject image = new JsonObject();
image.addProperty("media_id", this.getMediaId());
messageJson.add("file", image);
break;
}
case KefuMsgType.VOICE: {
JsonObject voice = new JsonObject();
voice.addProperty("media_id", this.getMediaId());
messageJson.add("voice", voice);
break;
}
case KefuMsgType.VIDEO: {
JsonObject video = new JsonObject();
video.addProperty("media_id", this.getMediaId());
video.addProperty("thumb_media_id", this.getThumbMediaId());
video.addProperty("title", this.getTitle());
video.addProperty("description", this.getDescription());
messageJson.add("video", video);
break;
}
case KefuMsgType.NEWS: {
JsonObject newsJsonObject = new JsonObject();
JsonArray articleJsonArray = new JsonArray();
for (NewArticle article : this.getArticles()) {
JsonObject articleJson = new JsonObject();
articleJson.addProperty("title", article.getTitle());
articleJson.addProperty("description", article.getDescription());
articleJson.addProperty("url", article.getUrl());
articleJson.addProperty("picurl", article.getPicUrl());
articleJsonArray.add(articleJson);
}
newsJsonObject.add("articles", articleJsonArray);
messageJson.add("news", newsJsonObject);
break;
}
case KefuMsgType.MPNEWS: {
JsonObject newsJsonObject = new JsonObject();
if (this.getMediaId() != null) {
newsJsonObject.addProperty("media_id", this.getMediaId());
} else {
JsonArray articleJsonArray = new JsonArray();
for (MpnewsArticle article : this.getMpnewsArticles()) {
JsonObject articleJson = new JsonObject();
articleJson.addProperty("title", article.getTitle());
articleJson.addProperty("thumb_media_id", article.getThumbMediaId());
articleJson.addProperty("author", article.getAuthor());
articleJson.addProperty("content_source_url", article.getContentSourceUrl());
articleJson.addProperty("content", article.getContent());
articleJson.addProperty("digest", article.getDigest());
articleJson.addProperty("show_cover_pic", article.getShowCoverPic());
articleJsonArray.add(articleJson);
}

newsJsonObject.add("articles", articleJsonArray);
}
messageJson.add("mpnews", newsJsonObject);
break;
}
case KefuMsgType.TASKCARD: {
JsonObject text = new JsonObject();
text.addProperty("title", this.getTitle());
text.addProperty("description", this.getDescription());

if (StringUtils.isNotBlank(this.getUrl())) {
text.addProperty("url", this.getUrl());
}

text.addProperty("task_id", this.getTaskId());

JsonArray buttonJsonArray = new JsonArray();
for (TaskCardButton button : this.getTaskButtons()) {
JsonObject buttonJson = new JsonObject();
buttonJson.addProperty("key", button.getKey());
buttonJson.addProperty("name", button.getName());

if (StringUtils.isNotBlank(button.getReplaceName())) {
buttonJson.addProperty("replace_name", button.getReplaceName());
}

if (StringUtils.isNotBlank(button.getColor())) {
buttonJson.addProperty("color", button.getColor());
}

if (button.getBold() != null) {
buttonJson.addProperty("is_bold", button.getBold());
}

buttonJsonArray.add(buttonJson);
}
text.add("btn", buttonJsonArray);

messageJson.add("taskcard", text);
break;
}
default: {
// do nothing
}
}
}

}

+ 42
- 0
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTaskCardUpdateResult.java Переглянути файл

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

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

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

/**
* <pre>
* 更新任务卡片消息状态的返回类
* 参考文档:https://work.weixin.qq.com/api/doc#90000/90135/91579
* Created by Jeff on 2019-05-16.
* </pre>
*
* @author <a href="https://github.com/domainname">Jeff</a>
* @date 2019-05-16
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class WxCpTaskCardUpdateResult implements Serializable {

@SerializedName("errcode")
private Integer errcode;

@SerializedName("errmsg")
private String errmsg;

/**
* 用户列表
*/
@SerializedName("invaliduser")
private List<String> invalidUsers;

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

+ 2
- 1
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpUser.java Переглянути файл

@@ -20,7 +20,8 @@ public class WxCpUser implements Serializable {
private static final long serialVersionUID = -5696099236344075582L;
private String userId;
private String name;
private Integer[] departIds;
private Long[] departIds;
private Integer[] orders;
private String position;
private String mobile;
private Gender gender;


+ 36
- 11
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpXmlMessage.java Переглянути файл

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

import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;

import org.apache.commons.io.IOUtils;

import com.thoughtworks.xstream.annotations.XStreamAlias;
import com.thoughtworks.xstream.annotations.XStreamConverter;
import com.thoughtworks.xstream.annotations.XStreamImplicit;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.common.api.WxConsts;
import me.chanjar.weixin.common.util.XmlUtils;
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.json.WxCpGsonBuilder;
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.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
* <pre>
@@ -36,6 +38,11 @@ import me.chanjar.weixin.cp.util.xml.XStreamTransformer;
public class WxCpXmlMessage implements Serializable {
private static final long serialVersionUID = -1042994982179476410L;

/**
* 使用dom4j解析的存放所有xml属性和值的map.
*/
private Map<String, Object> allFieldsMap;

///////////////////////
// 以下都是微信推送过来的消息的xml的element所对应的属性
///////////////////////
@@ -149,6 +156,10 @@ public class WxCpXmlMessage implements Serializable {
@XStreamConverter(value = XStreamCDataConverter.class)
private String recognition;

@XStreamAlias("TaskId")
@XStreamConverter(value = XStreamCDataConverter.class)
private String taskId;

/**
* 通讯录变更事件.
* 请参考常量 me.chanjar.weixin.cp.WxCpConsts.ContactChangeType
@@ -240,6 +251,13 @@ public class WxCpXmlMessage implements Serializable {
@XStreamConverter(value = XStreamCDataConverter.class)
private String telephone;

/**
* 地址.
*/
@XStreamAlias("Address")
@XStreamConverter(value = XStreamCDataConverter.class)
private String address;

/**
* 扩展属性.
*/
@@ -320,17 +338,20 @@ public class WxCpXmlMessage implements Serializable {
*/
@XStreamAlias("TotalCount")
private Integer totalCount;

/**
* 过滤.
* (过滤是指特定地区、性别的过滤、用户设置拒收的过滤,用户接收已超4条的过滤)后,准备发送的粉丝数,原则上,filterCount = sentCount + errorCount
*/
@XStreamAlias("FilterCount")
private Integer filterCount;

/**
* 发送成功的粉丝数.
*/
@XStreamAlias("SentCount")
private Integer sentCount;

/**
* 发送失败的粉丝数.
*/
@@ -349,7 +370,9 @@ public class WxCpXmlMessage implements Serializable {
protected static WxCpXmlMessage fromXml(String xml) {
//修改微信变态的消息内容格式,方便解析
xml = xml.replace("</PicList><PicList>", "");
return XStreamTransformer.fromXml(WxCpXmlMessage.class, xml);
final WxCpXmlMessage xmlMessage = XStreamTransformer.fromXml(WxCpXmlMessage.class, xml);
xmlMessage.setAllFieldsMap(XmlUtils.xml2Map(xml));
return xmlMessage;
}

protected static WxCpXmlMessage fromXml(InputStream is) {
@@ -402,9 +425,11 @@ public class WxCpXmlMessage implements Serializable {

@Data
public static class ExtAttr {
@XStreamAlias("Item")

@XStreamImplicit(itemFieldName = "Item")
protected final List<Item> items = new ArrayList<>();

@XStreamAlias("Item")
@Data
public static class Item {
@XStreamAlias("Name")


+ 8
- 2
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/article/NewArticle.java Переглянути файл

@@ -1,9 +1,12 @@
package me.chanjar.weixin.cp.bean.article;

import lombok.Data;

import java.io.Serializable;

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

/**
* <pre>
* Created by BinaryWang on 2017/3/27.
@@ -12,6 +15,9 @@ import java.io.Serializable;
* @author Binary Wang
*/
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class NewArticle implements Serializable {
private static final long serialVersionUID = 4087852055781140659L;



+ 32
- 0
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/messagebuilder/MarkdownMsgBuilder.java Переглянути файл

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

import me.chanjar.weixin.common.api.WxConsts;
import me.chanjar.weixin.cp.bean.WxCpMessage;

/**
* <pre>
* markdown类型的消息builder
* Created by Binary Wang on 2019/1/20.
* </pre>
*
* @author <a href="https://github.com/binarywang">Binary Wang</a>
*/
public class MarkdownMsgBuilder extends BaseBuilder<MarkdownMsgBuilder> {
private String content;

public MarkdownMsgBuilder() {
this.msgType = WxConsts.KefuMsgType.MARKDOWN;
}

public MarkdownMsgBuilder content(String content) {
this.content = content;
return this;
}

@Override
public WxCpMessage build() {
WxCpMessage m = super.build();
m.setContent(this.content);
return m;
}
}

+ 68
- 0
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/messagebuilder/TaskCardBuilder.java Переглянути файл

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

import me.chanjar.weixin.common.api.WxConsts;
import me.chanjar.weixin.cp.bean.WxCpMessage;
import me.chanjar.weixin.cp.bean.taskcard.TaskCardButton;

import java.util.List;

/**
* <pre>
* 任务卡片消息Builder
* 用法: WxCustomMessage m = WxCustomMessage.TASKCARD().title(...)....toUser(...).build();
* </pre>
*
* @author <a href="https://github.com/domainname">Jeff</a>
* @date 2019-05-16
*/
public class TaskCardBuilder extends BaseBuilder<TaskCardBuilder> {
private String title;
private String description;
private String url;
private String taskId;
/**
* 按钮个数为1~2个
*/
private List<TaskCardButton> buttons;

public TaskCardBuilder() {
this.msgType = WxConsts.KefuMsgType.TASKCARD;
}

public TaskCardBuilder title(String title) {
this.title = title;
return this;
}

public TaskCardBuilder description(String description) {
this.description = description;
return this;
}

public TaskCardBuilder url(String url) {
this.url = url;
return this;
}

public TaskCardBuilder taskId(String taskId) {
this.taskId = taskId;
return this;
}

public TaskCardBuilder buttons(List<TaskCardButton> buttons) {
this.buttons = buttons;
return this;
}

@Override
public WxCpMessage build() {
WxCpMessage m = super.build();
m.setSafe(null);
m.setTitle(this.title);
m.setDescription(this.description);
m.setUrl(this.url);
m.setTaskId(this.taskId);
m.setTaskButtons(this.buttons);
return m;
}
}

+ 23
- 0
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/taskcard/TaskCardButton.java Переглянути файл

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

import lombok.Builder;
import lombok.Data;

/**
* <pre>
* 任务卡片按钮
* Created by Jeff on 2019-05-16.
* </pre>
*
* @author <a href="https://github.com/domainname">Jeff</a>
* @date 2019-05-16
*/
@Data
@Builder
public class TaskCardButton {
private String key;
private String name;
private String replaceName;
private String color;
private Boolean bold;
}

+ 14
- 2
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/WxCpConfigStorage.java Переглянути файл

@@ -36,11 +36,23 @@ public interface WxCpConfigStorage {

/**
* 应该是线程安全的
*
* @param jsapiTicket
*/
void updateJsapiTicket(String jsapiTicket, int expiresInSeconds);

String getAgentJsapiTicket();

boolean isAgentJsapiTicketExpired();

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

/**
* 应该是线程安全的
*/
void updateAgentJsapiTicket(String jsapiTicket, int expiresInSeconds);

String getCorpId();

String getCorpSecret();


+ 27
- 2
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/WxCpInMemoryConfigStorage.java Переглянути файл

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

import java.io.File;

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

import java.io.File;

/**
* 基于内存的微信配置provider,在实际生产环境中应该将这些配置持久化
*
@@ -32,6 +32,9 @@ public class WxCpInMemoryConfigStorage implements WxCpConfigStorage {
protected volatile String jsapiTicket;
protected volatile long jsapiTicketExpiresTime;

protected volatile String agentJsapiTicket;
protected volatile long agentJsapiTicketExpiresTime;

protected volatile File tmpDirFile;

private volatile ApacheHttpClientBuilder apacheHttpClientBuilder;
@@ -95,6 +98,28 @@ public class WxCpInMemoryConfigStorage implements WxCpConfigStorage {
this.jsapiTicketExpiresTime = System.currentTimeMillis() + (expiresInSeconds - 200) * 1000L;
}

@Override
public String getAgentJsapiTicket() {
return this.agentJsapiTicket;
}

@Override
public boolean isAgentJsapiTicketExpired() {
return System.currentTimeMillis() > this.agentJsapiTicketExpiresTime;
}

@Override
public void expireAgentJsapiTicket() {
this.agentJsapiTicketExpiresTime = 0;
}

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

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


+ 44
- 17
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/WxCpJedisConfigStorage.java Переглянути файл

@@ -9,26 +9,21 @@ import redis.clients.jedis.JedisPoolConfig;
import java.io.File;

/**
* Jedis client implementor for wechat config storage.
* <pre>
* 使用说明:本实现仅供参考,并不完整
* 使用说明:本实现仅供参考,并不完整.
* 比如为减少项目依赖,未加入redis分布式锁的实现,如有需要请自行实现。
* </pre>
*
* @author gaigeshen
*/
public class WxCpJedisConfigStorage implements WxCpConfigStorage {

/**
* Redis keys here
*/
private static final String ACCESS_TOKEN_KEY = "WX_CP_ACCESS_TOKEN";
private static final String ACCESS_TOKEN_EXPIRES_TIME_KEY = "WX_CP_ACCESS_TOKEN_EXPIRES_TIME";
private static final String JS_API_TICKET_KEY = "WX_CP_JS_API_TICKET";
private static final String JS_API_TICKET_EXPIRES_TIME_KEY = "WX_CP_JS_API_TICKET_EXPIRES_TIME";
/**
* Redis clients pool
*/
private static final String AGENT_JSAPI_TICKET_KEY = "WX_CP_AGENT_%s_JSAPI_TICKET";
private static final String AGENT_JSAPI_TICKET_EXPIRES_TIME_KEY = "WX_CP_AGENT_%s_JSAPI_TICKET_EXPIRES_TIME";
private final JedisPool jedisPool;
private volatile String corpId;
private volatile String corpSecret;
@@ -46,7 +41,7 @@ public class WxCpJedisConfigStorage implements WxCpConfigStorage {
public WxCpJedisConfigStorage(JedisPool jedisPool) {
this.jedisPool = jedisPool;
}
public WxCpJedisConfigStorage(String host, int port) {
jedisPool = new JedisPool(host, port);
}
@@ -83,8 +78,7 @@ public class WxCpJedisConfigStorage implements WxCpConfigStorage {
String expiresTimeStr = jedis.get(ACCESS_TOKEN_EXPIRES_TIME_KEY);

if (expiresTimeStr != null) {
Long expiresTime = Long.parseLong(expiresTimeStr);
return System.currentTimeMillis() > expiresTime;
return System.currentTimeMillis() > Long.parseLong(expiresTimeStr);
}

return true;
@@ -123,17 +117,15 @@ public class WxCpJedisConfigStorage implements WxCpConfigStorage {

@Override
public boolean isJsapiTicketExpired() {

try (Jedis jedis = this.jedisPool.getResource()) {
String expiresTimeStr = jedis.get(JS_API_TICKET_EXPIRES_TIME_KEY);

if (expiresTimeStr != null) {
Long expiresTime = Long.parseLong(expiresTimeStr);
long expiresTime = Long.parseLong(expiresTimeStr);
return System.currentTimeMillis() > expiresTime;
}

return true;

}
}

@@ -146,16 +138,51 @@ public class WxCpJedisConfigStorage implements WxCpConfigStorage {

@Override
public synchronized void updateJsapiTicket(String jsapiTicket, int expiresInSeconds) {

try (Jedis jedis = this.jedisPool.getResource()) {
jedis.set(JS_API_TICKET_KEY, jsapiTicket);

jedis.set(JS_API_TICKET_EXPIRES_TIME_KEY,
(System.currentTimeMillis() + (expiresInSeconds - 200) * 1000L + ""));
}

}

@Override
public String getAgentJsapiTicket() {
try (Jedis jedis = this.jedisPool.getResource()) {
return jedis.get(String.format(AGENT_JSAPI_TICKET_KEY, agentId));
}
}

@Override
public boolean isAgentJsapiTicketExpired() {
try (Jedis jedis = this.jedisPool.getResource()) {
String expiresTimeStr = jedis.get(String.format(AGENT_JSAPI_TICKET_EXPIRES_TIME_KEY, agentId));

if (expiresTimeStr != null) {
return System.currentTimeMillis() > Long.parseLong(expiresTimeStr);
}

return true;
}
}

@Override
public void expireAgentJsapiTicket() {
try (Jedis jedis = this.jedisPool.getResource()) {
jedis.set(String.format(AGENT_JSAPI_TICKET_EXPIRES_TIME_KEY, agentId), "0");
}
}

@Override
public void updateAgentJsapiTicket(String jsapiTicket, int expiresInSeconds) {
try (Jedis jedis = this.jedisPool.getResource()) {
jedis.set(String.format(AGENT_JSAPI_TICKET_KEY, agentId), jsapiTicket);
jedis.set(String.format(AGENT_JSAPI_TICKET_EXPIRES_TIME_KEY, agentId),
(System.currentTimeMillis() + (expiresInSeconds - 200) * 1000L + ""));
}

}

@Override
public String getCorpId() {
return this.corpId;


+ 1
- 1
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/message/WxCpMessageRouter.java Переглянути файл

@@ -73,7 +73,7 @@ public class WxCpMessageRouter {
this.wxCpService = wxCpService;
this.executorService = Executors.newFixedThreadPool(DEFAULT_THREAD_POOL_SIZE);
this.messageDuplicateChecker = new WxMessageInMemoryDuplicateChecker();
this.sessionManager = new StandardSessionManager();
this.sessionManager = wxCpService.getSessionManager();
this.exceptionHandler = new LogExceptionHandler();
}



+ 0
- 2
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/util/json/WxCpGsonBuilder.java Переглянути файл

@@ -7,7 +7,6 @@ import me.chanjar.weixin.common.error.WxError;
import me.chanjar.weixin.common.util.json.WxErrorAdapter;
import me.chanjar.weixin.cp.bean.WxCpChat;
import me.chanjar.weixin.cp.bean.WxCpDepart;
import me.chanjar.weixin.cp.bean.WxCpMessage;
import me.chanjar.weixin.cp.bean.WxCpTag;
import me.chanjar.weixin.cp.bean.WxCpUser;

@@ -20,7 +19,6 @@ public class WxCpGsonBuilder {

static {
INSTANCE.disableHtmlEscaping();
INSTANCE.registerTypeAdapter(WxCpMessage.class, new WxCpMessageGsonAdapter());
INSTANCE.registerTypeAdapter(WxCpChat.class, new WxCpChatGsonAdapter());
INSTANCE.registerTypeAdapter(WxCpDepart.class, new WxCpDepartGsonAdapter());
INSTANCE.registerTypeAdapter(WxCpUser.class, new WxCpUserGsonAdapter());


+ 0
- 127
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/util/json/WxCpMessageGsonAdapter.java Переглянути файл

@@ -1,127 +0,0 @@
/*
* KINGSTAR MEDIA SOLUTIONS Co.,LTD. Copyright c 2005-2013. All rights reserved.
*
* This source code is the property of KINGSTAR MEDIA SOLUTIONS LTD. It is intended
* only for the use of KINGSTAR MEDIA application development. Reengineering, reproduction
* arose from modification of the original source, or other redistribution of this source
* is not permitted without written permission of the KINGSTAR MEDIA SOLUTIONS LTD.
*/
package me.chanjar.weixin.cp.util.json;

import com.google.gson.*;
import me.chanjar.weixin.common.api.WxConsts;
import me.chanjar.weixin.cp.bean.WxCpMessage;
import me.chanjar.weixin.cp.bean.article.MpnewsArticle;
import me.chanjar.weixin.cp.bean.article.NewArticle;
import org.apache.commons.lang3.StringUtils;

import java.lang.reflect.Type;

/**
* @author Daniel Qian
*/
public class WxCpMessageGsonAdapter implements JsonSerializer<WxCpMessage> {

@Override
public JsonElement serialize(WxCpMessage message, Type typeOfSrc, JsonSerializationContext context) {
JsonObject messageJson = new JsonObject();
messageJson.addProperty("agentid", message.getAgentId());
if (StringUtils.isNotBlank(message.getToUser())) {
messageJson.addProperty("touser", message.getToUser());
}
messageJson.addProperty("msgtype", message.getMsgType());

if (StringUtils.isNotBlank(message.getToParty())) {
messageJson.addProperty("toparty", message.getToParty());
}
if (StringUtils.isNotBlank(message.getToTag())) {
messageJson.addProperty("totag", message.getToTag());
}
if (WxConsts.KefuMsgType.TEXT.equals(message.getMsgType())) {
JsonObject text = new JsonObject();
text.addProperty("content", message.getContent());
messageJson.add("text", text);
}

if (WxConsts.KefuMsgType.TEXTCARD.equals(message.getMsgType())) {
JsonObject text = new JsonObject();
text.addProperty("title", message.getTitle());
text.addProperty("description", message.getDescription());
text.addProperty("url", message.getUrl());
text.addProperty("btntxt", message.getBtnTxt());
messageJson.add("textcard", text);
}

if (WxConsts.KefuMsgType.IMAGE.equals(message.getMsgType())) {
JsonObject image = new JsonObject();
image.addProperty("media_id", message.getMediaId());
messageJson.add("image", image);
}

if (WxConsts.KefuMsgType.FILE.equals(message.getMsgType())) {
JsonObject image = new JsonObject();
image.addProperty("media_id", message.getMediaId());
messageJson.add("file", image);
}

if (WxConsts.KefuMsgType.VOICE.equals(message.getMsgType())) {
JsonObject voice = new JsonObject();
voice.addProperty("media_id", message.getMediaId());
messageJson.add("voice", voice);
}

if (StringUtils.isNotBlank(message.getSafe())) {
messageJson.addProperty("safe", message.getSafe());
}

if (WxConsts.KefuMsgType.VIDEO.equals(message.getMsgType())) {
JsonObject video = new JsonObject();
video.addProperty("media_id", message.getMediaId());
video.addProperty("thumb_media_id", message.getThumbMediaId());
video.addProperty("title", message.getTitle());
video.addProperty("description", message.getDescription());
messageJson.add("video", video);
}

if (WxConsts.KefuMsgType.NEWS.equals(message.getMsgType())) {
JsonObject newsJsonObject = new JsonObject();
JsonArray articleJsonArray = new JsonArray();
for (NewArticle article : message.getArticles()) {
JsonObject articleJson = new JsonObject();
articleJson.addProperty("title", article.getTitle());
articleJson.addProperty("description", article.getDescription());
articleJson.addProperty("url", article.getUrl());
articleJson.addProperty("picurl", article.getPicUrl());
articleJsonArray.add(articleJson);
}
newsJsonObject.add("articles", articleJsonArray);
messageJson.add("news", newsJsonObject);
}

if (WxConsts.KefuMsgType.MPNEWS.equals(message.getMsgType())) {
JsonObject newsJsonObject = new JsonObject();
if (message.getMediaId() != null) {
newsJsonObject.addProperty("media_id", message.getMediaId());
} else {
JsonArray articleJsonArray = new JsonArray();
for (MpnewsArticle article : message.getMpnewsArticles()) {
JsonObject articleJson = new JsonObject();
articleJson.addProperty("title", article.getTitle());
articleJson.addProperty("thumb_media_id", article.getThumbMediaId());
articleJson.addProperty("author", article.getAuthor());
articleJson.addProperty("content_source_url", article.getContentSourceUrl());
articleJson.addProperty("content", article.getContent());
articleJson.addProperty("digest", article.getDigest());
articleJson.addProperty("show_cover_pic", article.getShowCoverPic());
articleJsonArray.add(articleJson);
}

newsJsonObject.add("articles", articleJsonArray);
}
messageJson.add("mpnews", newsJsonObject);
}

return messageJson;
}

}

+ 88
- 56
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/util/json/WxCpUserGsonAdapter.java Переглянути файл

@@ -6,6 +6,7 @@
* arose from modification of the original source, or other redistribution of this source
* is not permitted without written permission of the KINGSTAR MEDIA SOLUTIONS LTD.
*/

package me.chanjar.weixin.cp.util.json;

import java.lang.reflect.Type;
@@ -24,11 +25,14 @@ import me.chanjar.weixin.cp.bean.Gender;
import me.chanjar.weixin.cp.bean.WxCpUser;

/**
* cp user gson adapter.
*
* @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";
private static final String EXTATTR = "extattr";

@Override
public WxCpUser deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
@@ -37,14 +41,24 @@ public class WxCpUserGsonAdapter implements JsonDeserializer<WxCpUser>, JsonSeri

if (o.get("department") != null) {
JsonArray departJsonArray = o.get("department").getAsJsonArray();
Integer[] departIds = new Integer[departJsonArray.size()];
Long[] departIds = new Long[departJsonArray.size()];
int i = 0;
for (JsonElement jsonElement : departJsonArray) {
departIds[i++] = jsonElement.getAsInt();
departIds[i++] = jsonElement.getAsLong();
}
user.setDepartIds(departIds);
}

if (o.get("order") != null) {
JsonArray departJsonArray = o.get("order").getAsJsonArray();
Integer[] orders = new Integer[departJsonArray.size()];
int i = 0;
for (JsonElement jsonElement : departJsonArray) {
orders[i++] = jsonElement.getAsInt();
}
user.setOrders(orders);
}

user.setUserId(GsonHelper.getString(o, "userid"));
user.setName(GsonHelper.getString(o, "name"));
user.setPosition(GsonHelper.getString(o, "position"));
@@ -62,64 +76,73 @@ public class WxCpUserGsonAdapter implements JsonDeserializer<WxCpUser>, JsonSeri
user.setQrCode(GsonHelper.getString(o, "qr_code"));
user.setToInvite(GsonHelper.getBoolean(o, "to_invite"));

if (GsonHelper.isNotNull(o.get("extattr"))) {
JsonArray attrJsonElements = o.get("extattr").getAsJsonObject().get("attrs").getAsJsonArray();
for (JsonElement attrJsonElement : attrJsonElements) {
WxCpUser.Attr attr = new WxCpUser.Attr(
GsonHelper.getString(attrJsonElement.getAsJsonObject(), "name"),
GsonHelper.getString(attrJsonElement.getAsJsonObject(), "value")
);
user.getExtAttrs().add(attr);
}
if (GsonHelper.isNotNull(o.get(EXTATTR))) {
this.buildExtraAttrs(o, user);
}

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");
this.buildExternalAttrs(o, user);
}

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

private void buildExtraAttrs(JsonObject o, WxCpUser user) {
JsonArray attrJsonElements = o.get(EXTATTR).getAsJsonObject().get("attrs").getAsJsonArray();
for (JsonElement attrJsonElement : attrJsonElements) {
WxCpUser.Attr attr = new WxCpUser.Attr(
GsonHelper.getString(attrJsonElement.getAsJsonObject(), "name"),
GsonHelper.getString(attrJsonElement.getAsJsonObject(), "value")
);
user.getExtAttrs().add(attr);
}
}

private void buildExternalAttrs(JsonObject o, WxCpUser user) {
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;
}

@Override
@@ -133,11 +156,20 @@ public class WxCpUserGsonAdapter implements JsonDeserializer<WxCpUser>, JsonSeri
}
if (user.getDepartIds() != null) {
JsonArray jsonArray = new JsonArray();
for (Integer departId : user.getDepartIds()) {
for (Long departId : user.getDepartIds()) {
jsonArray.add(new JsonPrimitive(departId));
}
o.add("department", jsonArray);
}

if (user.getOrders() != null) {
JsonArray jsonArray = new JsonArray();
for (Integer order : user.getOrders()) {
jsonArray.add(new JsonPrimitive(order));
}
o.add("order", jsonArray);
}

if (user.getPosition() != null) {
o.addProperty("position", user.getPosition());
}
@@ -191,14 +223,14 @@ public class WxCpUserGsonAdapter implements JsonDeserializer<WxCpUser>, JsonSeri
}
JsonObject attrsJson = new JsonObject();
attrsJson.add("attrs", attrsJsonArray);
o.add("extattr", attrsJson);
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("type", attr.getType());
attrJson.addProperty("name", attr.getName());
switch (attr.getType()) {
case 0: {


+ 50
- 2
weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/WxCpMessageAPITest.java Переглянути файл

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

import org.testng.annotations.*;

import com.google.inject.Inject;
import me.chanjar.weixin.common.api.WxConsts;
import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.cp.bean.WxCpMessage;
import me.chanjar.weixin.cp.bean.WxCpMessageSendResult;
import org.testng.annotations.*;

import static org.testng.Assert.*;

@@ -14,7 +15,7 @@ import static org.testng.Assert.*;
* @author Daniel Qian
*
*/
@Test(groups = "customMessageAPI")
@Test
@Guice(modules = ApiTestModule.class)
public class WxCpMessageAPITest {

@@ -59,4 +60,51 @@ public class WxCpMessageAPITest {
System.out.println(messageSendResult.getInvalidUserList());
System.out.println(messageSendResult.getInvalidTagList());
}

@Test
public void testSendMessage_markdown() throws WxErrorException {
WxCpMessage message = WxCpMessage
.MARKDOWN()
.toUser(configStorage.getUserId())
.content("您的会议室已经预定,稍后会同步到`邮箱` \n" +
" >**事项详情** \n" +
" >事 项:<font color=\\\"info\\\">开会</font> \n" +
" >组织者:@miglioguan \n" +
" >参与者:@miglioguan、@kunliu、@jamdeezhou、@kanexiong、@kisonwang \n" +
" > \n" +
" >会议室:<font color=\\\"info\\\">广州TIT 1楼 301</font> \n" +
" >日 期:<font color=\\\"warning\\\">2018年5月18日</font> \n" +
" >时 间:<font color=\\\"comment\\\">上午9:00-11:00</font> \n" +
" > \n" +
" >请准时参加会议。 \n" +
" > \n" +
" >如需修改会议信息,请点击:[修改会议信息](https://work.weixin.qq.com)")
.build();

WxCpMessageSendResult messageSendResult = this.wxService.messageSend(message);
assertNotNull(messageSendResult);
System.out.println(messageSendResult);
System.out.println(messageSendResult.getInvalidPartyList());
System.out.println(messageSendResult.getInvalidUserList());
System.out.println(messageSendResult.getInvalidTagList());
}

@Test
public void testSendMessage_textCard() throws WxErrorException {
WxCpMessage message = WxCpMessage
.TEXTCARD()
.toUser(configStorage.getUserId())
.btnTxt("更多")
.description( "<div class=\"gray\">2016年9月26日</div> <div class=\"normal\">恭喜你抽中iPhone 7一台,领奖码:xxxx</div><div class=\"highlight\">请于2016年10月10日前联系行政同事领取</div>")
.url("URL")
.title("领奖通知")
.build();

WxCpMessageSendResult messageSendResult = this.wxService.messageSend(message);
assertNotNull(messageSendResult);
System.out.println(messageSendResult);
System.out.println(messageSendResult.getInvalidPartyList());
System.out.println(messageSendResult.getInvalidUserList());
System.out.println(messageSendResult.getInvalidTagList());
}
}

+ 36
- 0
weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/impl/BaseWxCpServiceImplTest.java Переглянути файл

@@ -0,0 +1,36 @@
package me.chanjar.weixin.cp.api.impl;

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 org.testng.annotations.Guice;
import org.testng.annotations.Test;

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

/**
* <pre>
* Created by BinaryWang on 2019/3/31.
* </pre>
*
* @author <a href="https://github.com/binarywang">Binary Wang</a>
*/
@Test
@Guice(modules = ApiTestModule.class)
public class BaseWxCpServiceImplTest {
@Inject
protected WxCpService wxService;

@Test
public void testGetAgentJsapiTicket() throws WxErrorException {
assertThat(this.wxService.getAgentJsapiTicket()).isNotEmpty();
assertThat(this.wxService.getAgentJsapiTicket(true)).isNotEmpty();
}

@Test
public void testJsCode2Session() throws WxErrorException {
assertThat(this.wxService.jsCode2Session("111")).isNotNull();
}
}

+ 126
- 14
weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/impl/WxCpChatServiceImplTest.java Переглянути файл

@@ -2,15 +2,21 @@ package me.chanjar.weixin.cp.api.impl;

import java.util.Arrays;

import me.chanjar.weixin.cp.bean.WxCpChat;
import org.testng.Assert;
import org.testng.annotations.Guice;
import org.testng.annotations.Test;
import org.testng.*;
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.WxCpConsts.AppChatMsgType;
import me.chanjar.weixin.cp.api.ApiTestModule;
import me.chanjar.weixin.cp.api.WxCpService;
import me.chanjar.weixin.cp.bean.WxCpAppChatMessage;
import me.chanjar.weixin.cp.bean.WxCpChat;
import me.chanjar.weixin.cp.bean.article.MpnewsArticle;
import me.chanjar.weixin.cp.bean.article.NewArticle;

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

/**
* 测试群聊服务
@@ -19,28 +25,134 @@ import me.chanjar.weixin.cp.api.WxCpService;
*/
@Guice(modules = ApiTestModule.class)
public class WxCpChatServiceImplTest {
private String chatId;
private String userId;

@Inject
private WxCpService wxCpService;
private WxCpService cpService;

@BeforeTest
public void init() {
this.chatId = "mychatid";
this.userId = ((ApiTestModule.WxXmlCpInMemoryConfigStorage) this.cpService.getWxCpConfigStorage()).getUserId();
}

@Test
public void create() throws Exception {
wxCpService.getChatService().chatCreate("测试群聊", "gaige_shen", Arrays.asList("gaige_shen", "ZhangXiaoMing"), "mychatid");
public void testChatCreate() throws Exception {
final String result = cpService.getChatService().chatCreate("测试群聊", userId,
Arrays.asList(userId, userId), chatId);
assertThat(result).isNotEmpty();
assertThat(result).isEqualTo(chatId);
}

@Test
public void get() throws Exception {
WxCpChat chat = wxCpService.getChatService().chatGet("mychatid");
public void testChatGet() throws Exception {
WxCpChat chat = this.cpService.getChatService().chatGet(chatId);
System.out.println(chat);
Assert.assertEquals(chat.getName(), "测试群聊");
}

@Test
public void update() throws Exception {
wxCpService.getChatService().chatUpdate("mychatid", "", "", Arrays.asList("ZhengWuYao"), null);
WxCpChat chat = wxCpService.getChatService().chatGet("mychatid");
public void testChatUpdate() throws Exception {
this.cpService.getChatService().chatUpdate(chatId, "", "", Arrays.asList("ZhengWuYao"), null);
WxCpChat chat = this.cpService.getChatService().chatGet(chatId);
System.out.println(chat);
Assert.assertEquals(chat.getUsers().size(), 3);
}

@DataProvider
public Object[][] messages() {
return new Object[][]{
{WxCpAppChatMessage.builder()
.msgType(AppChatMsgType.TEXT)
.chatId(chatId)
.content("你的快递已到\n请携带工卡前往邮件中心领取")
.build()
},
{WxCpAppChatMessage.builder()
.msgType(AppChatMsgType.IMAGE)
.chatId(chatId)
.mediaId("3_xWGPXZhpOKZrlRISWrjhPrDUZqZ-jIEVzxd56jLuqM")
.build()
},
{WxCpAppChatMessage.builder()
.msgType(AppChatMsgType.VOICE)
.chatId(chatId)
.mediaId("3X5t6HkdN1hUgB7OzrdRnc8v0yI0CqlAxFxnCkS3msTnTLanpYrV4esLv4foZVnlf")
.build()
},
{WxCpAppChatMessage.builder()
.msgType(AppChatMsgType.VIDEO)
.chatId(chatId)
.mediaId("3otWyy_acbID8fyltmCOW5hGVD8oa0_p0za5jhukxKTUDoGT71lqTvtQAWoycXpQf")
.title("aaaa")
.description("ddddd")
.build()
},
{WxCpAppChatMessage.builder()
.msgType(AppChatMsgType.FILE)
.chatId(chatId)
.mediaId("34AyVyDdndVhB4Z2tT-_FYKZ7Xqrr47LPC11GHH4oy7o")
.build()
},
{WxCpAppChatMessage.builder()
.msgType(AppChatMsgType.TEXTCARD)
.chatId(chatId)
.btnTxt("更多")
.title("领奖通知")
.url("https://zhidao.baidu.com/question/2073647112026042748.html")
.description("<div class=\"gray\">2016年9月26日</div> <div class=\"normal\"> 恭喜你抽中iPhone 7一台,领奖码:520258</div><div class=\"highlight\">请于2016年10月10日前联系行 政同事领取</div>")
.build()
},
{WxCpAppChatMessage.builder()
.msgType(AppChatMsgType.NEWS)
.chatId(chatId)
.articles(Lists.newArrayList(NewArticle.builder()
.title("领奖通知")
.url("https://zhidao.baidu.com/question/2073647112026042748.html")
.description("今年中秋节公司有豪礼相送")
.picUrl("http://res.mail.qq.com/node/ww/wwopenmng/images/independent/doc/test_pic_msg1.png")
.build()
))
.build()
},
{WxCpAppChatMessage.builder()
.msgType(AppChatMsgType.MPNEWS)
.chatId(chatId)
.mpnewsArticles(Lists.newArrayList(MpnewsArticle.newBuilder()
.title("地球一小时")
.thumbMediaId("3_xWGPXZhpOKZrlRISWrjhPrDUZqZ-jIEVzxd56jLuqM")
.author("Author")
.contentSourceUrl("https://work.weixin.qq.com")
.content("3月24日20:30-21:30 \n办公区将关闭照明一小时,请各部门同事相互转告")
.digest("3月24日20:30-21:30 \n办公区将关闭照明一小时")
.build()
))
.build()
},
{WxCpAppChatMessage.builder()
.msgType(AppChatMsgType.MARKDOWN)
.chatId(chatId)
.content("您的会议室已经预定,稍后会同步到`邮箱` \n" +
" >**事项详情** \n" +
" >事 项:<font color=\\\"info\\\">开会</font> \n" +
" >组织者:@miglioguan \n" +
" >参与者:@miglioguan、@kunliu、@jamdeezhou、@kanexiong、@kisonwang \n" +
" > \n" +
" >会议室:<font color=\\\"info\\\">广州TIT 1楼 301</font> \n" +
" >日 期:<font color=\\\"warning\\\">2018年5月18日</font> \n" +
" >时 间:<font color=\\\"comment\\\">上午9:00-11:00</font> \n" +
" > \n" +
" >请准时参加会议。 \n" +
" > \n" +
" >如需修改会议信息,请点击:[修改会议信息](https://work.weixin.qq.com)")
.build()
},
};
}

@Test(dataProvider = "messages")
public void testSendMsg(WxCpAppChatMessage message) throws WxErrorException {
this.cpService.getChatService().sendMsg(message);
}
}

+ 1
- 1
weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/impl/WxCpDepartmentServiceImplTest.java Переглянути файл

@@ -31,7 +31,7 @@ public class WxCpDepartmentServiceImplTest {
cpDepart.setName("子部门" + System.currentTimeMillis());
cpDepart.setParentId(1L);
cpDepart.setOrder(1L);
Integer departId = this.wxCpService.getDepartmentService().create(cpDepart);
Long departId = this.wxCpService.getDepartmentService().create(cpDepart);
System.out.println(departId);
}



+ 49
- 0
weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/impl/WxCpOAServiceImplTest.java Переглянути файл

@@ -0,0 +1,49 @@
package me.chanjar.weixin.cp.api.impl;

import com.google.common.collect.Lists;
import com.google.gson.Gson;
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.WxCpCheckinData;
import me.chanjar.weixin.cp.bean.WxCpCheckinOption;
import org.apache.commons.lang3.time.DateFormatUtils;
import org.testng.annotations.Guice;
import org.testng.annotations.Test;

import java.text.ParseException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

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

/**
* @author Element
* @date 2019-04-20 13:46
*/
@Guice(modules = ApiTestModule.class)
public class WxCpOAServiceImplTest {
@Inject
protected WxCpService wxService;

@Test
public void testGetCheckinData() throws ParseException, WxErrorException {
Date startTime = DateFormatUtils.ISO_8601_EXTENDED_DATE_FORMAT.parse("2019-04-11");
Date endTime = DateFormatUtils.ISO_8601_EXTENDED_DATE_FORMAT.parse("2019-05-10");

List<WxCpCheckinData> results = wxService.getOAService()
.getCheckinData(1, startTime, endTime, Lists.newArrayList("binary"));

assertThat(results).isNotNull();
}

@Test
public void testGetCheckinOption() throws WxErrorException {
Date now = new Date();
List<WxCpCheckinOption> results = wxService.getOAService()
.getCheckinOption(now, Lists.newArrayList("binary"));
assertThat(results).isNotNull();
}
}

+ 65
- 0
weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/impl/WxCpTaskCardServiceImplTest.java Переглянути файл

@@ -0,0 +1,65 @@
package me.chanjar.weixin.cp.api.impl;

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.WxCpMessage;
import me.chanjar.weixin.cp.bean.WxCpMessageSendResult;
import me.chanjar.weixin.cp.bean.taskcard.TaskCardButton;
import org.testng.annotations.Guice;
import org.testng.annotations.Test;

import java.util.Arrays;

import static org.testng.Assert.assertNotNull;

/**
* 测试任务卡片服务
*
* @author <a href="https://github.com/domainname">Jeff</a>
* @date 2019-05-16
*/
@Guice(modules = ApiTestModule.class)
public class WxCpTaskCardServiceImplTest {

@Inject
private WxCpService wxCpService;

@Test
public void testSendTaskCard() throws WxErrorException {
TaskCardButton btn1 = TaskCardButton.builder()
.key("key1")
.name("同意")
.replaceName("已同意")
.bold(true)
.build();
TaskCardButton btn2 = TaskCardButton.builder()
.key("key2")
.name("拒绝")
.replaceName("已拒绝")
.color("red")
.build();
WxCpMessage message = WxCpMessage.TASKCARD()
.toUser("jeff|mr.t")
.title("有一个待审批的请求")
.description("申请:购买图书\n金额:100 元")
.taskId("task_1")
.url("http://www.qq.com")
.buttons(Arrays.asList(btn1, btn2))
.build();

WxCpMessageSendResult messageSendResult = this.wxCpService.messageSend(message);
assertNotNull(messageSendResult);
System.out.println(messageSendResult);
System.out.println(messageSendResult.getInvalidPartyList());
System.out.println(messageSendResult.getInvalidUserList());
System.out.println(messageSendResult.getInvalidTagList());
}

@Test
public void testUpdate() throws Exception {
wxCpService.getTaskCardService().update(Arrays.asList("jeff", "mr.t"), "task_1", "key1");
}

}

+ 1
- 1
weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/impl/WxCpUserServiceImplTest.java Переглянути файл

@@ -42,7 +42,7 @@ public class WxCpUserServiceImplTest {
WxCpUser user = new WxCpUser();
user.setUserId(userId);
user.setName("Some Woman");
user.setDepartIds(new Integer[]{2});
user.setDepartIds(new Long[]{2L});
user.setEmail("none@none.com");
user.setGender(Gender.FEMALE);
user.setMobile("13560084979");


+ 11
- 4
weixin-java-cp/src/test/java/me/chanjar/weixin/cp/bean/WxCpAgentTest.java Переглянути файл

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

import org.testng.Assert;
import org.testng.annotations.Test;
import org.testng.*;
import org.testng.annotations.*;

/**
* Created by huansinho on 2018/4/13.
@@ -10,7 +10,13 @@ import org.testng.annotations.Test;
public class WxCpAgentTest {

public void testDeserialize() {
String json = "{\"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\": \"\"}";
String json = "{\"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\": \"\"}";

WxCpAgent wxCpAgent = WxCpAgent.fromJson(json);

@@ -18,7 +24,8 @@ public class WxCpAgentTest {

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());

}



+ 50
- 8
weixin-java-cp/src/test/java/me/chanjar/weixin/cp/bean/WxCpMessageTest.java Переглянути файл

@@ -2,10 +2,12 @@ package me.chanjar.weixin.cp.bean;

import me.chanjar.weixin.cp.bean.article.MpnewsArticle;
import me.chanjar.weixin.cp.bean.article.NewArticle;
import me.chanjar.weixin.cp.bean.taskcard.TaskCardButton;
import org.testng.annotations.Test;

import java.util.Arrays;

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

@Test
public class WxCpMessageTest {
@@ -19,12 +21,16 @@ public class WxCpMessageTest {
public void testTextCardBuild() {
WxCpMessage reply = WxCpMessage.TEXTCARD().toUser("OPENID")
.title("领奖通知")
.description("<div class=\"gray\">2016年9月26日</div> <div class=\"normal\">恭喜你抽中iPhone 7一台,领奖码:xxxx</div><div class=\"highlight\">请于2016年10月10日前联系行政同事领取</div>")
.description("<div class=\"gray\">2016年9月26日</div> <div class=\"normal\">恭喜你抽中iPhone 7一台," +
"领奖码:xxxx</div><div class=\"highlight\">请于2016年10月10日前联系行政同事领取</div>")
.url("http://www.qq.com")
.btnTxt("更多")
.build();
assertThat(reply.toJson())
.isEqualTo("{\"touser\":\"OPENID\",\"msgtype\":\"textcard\",\"textcard\":{\"title\":\"领奖通知\",\"description\":\"<div class=\\\"gray\\\">2016年9月26日</div> <div class=\\\"normal\\\">恭喜你抽中iPhone 7一台,领奖码:xxxx</div><div class=\\\"highlight\\\">请于2016年10月10日前联系行政同事领取</div>\",\"url\":\"http://www.qq.com\",\"btntxt\":\"更多\"},\"safe\":\"0\"}");
.isEqualTo("{\"touser\":\"OPENID\",\"msgtype\":\"textcard\",\"textcard\":{\"title\":\"领奖通知\"," +
"\"description\":\"<div class=\\\"gray\\\">2016年9月26日</div> <div class=\\\"normal\\\">" +
"恭喜你抽中iPhone 7一台,领奖码:xxxx</div><div class=\\\"highlight\\\">请于2016年10月10日前联系行政同事领取</div>\"," +
"\"url\":\"http://www.qq.com\",\"btntxt\":\"更多\"},\"safe\":\"0\"}");
}

public void testImageBuild() {
@@ -40,9 +46,11 @@ public class WxCpMessageTest {
}

public void testVideoBuild() {
WxCpMessage reply = WxCpMessage.VIDEO().toUser("OPENID").title("TITLE").mediaId("MEDIA_ID").thumbMediaId("MEDIA_ID").description("DESCRIPTION").build();
WxCpMessage reply = WxCpMessage.VIDEO().toUser("OPENID").title("TITLE").mediaId("MEDIA_ID").thumbMediaId("MEDIA_ID")
.description("DESCRIPTION").build();
assertThat(reply.toJson())
.isEqualTo("{\"touser\":\"OPENID\",\"msgtype\":\"video\",\"safe\":\"0\",\"video\":{\"media_id\":\"MEDIA_ID\",\"thumb_media_id\":\"MEDIA_ID\",\"title\":\"TITLE\",\"description\":\"DESCRIPTION\"}}");
.isEqualTo("{\"touser\":\"OPENID\",\"msgtype\":\"video\",\"video\":{\"media_id\":\"MEDIA_ID\"," +
"\"thumb_media_id\":\"MEDIA_ID\",\"title\":\"TITLE\",\"description\":\"DESCRIPTION\"},\"safe\":\"0\"}");
}

public void testNewsBuild() {
@@ -61,7 +69,10 @@ public class WxCpMessageTest {
WxCpMessage reply = WxCpMessage.NEWS().toUser("OPENID").addArticle(article1).addArticle(article2).build();

assertThat(reply.toJson())
.isEqualTo( "{\"touser\":\"OPENID\",\"msgtype\":\"news\",\"safe\":\"0\",\"news\":{\"articles\":[{\"title\":\"Happy Day\",\"description\":\"Is Really A Happy Day\",\"url\":\"URL\",\"picurl\":\"PIC_URL\"},{\"title\":\"Happy Day\",\"description\":\"Is Really A Happy Day\",\"url\":\"URL\",\"picurl\":\"PIC_URL\"}]}}");
.isEqualTo("{\"touser\":\"OPENID\",\"msgtype\":\"news\",\"news\":{\"articles\":" +
"[{\"title\":\"Happy Day\",\"description\":\"Is Really A Happy Day\",\"url\":\"URL\",\"picurl\":\"PIC_URL\"}," +
"{\"title\":\"Happy Day\",\"description\":\"Is Really A Happy Day\",\"url\":\"URL\",\"picurl\":\"PIC_URL\"}]}," +
"\"safe\":\"0\"}");
}

public void testMpnewsBuild_with_articles() {
@@ -88,14 +99,45 @@ public class WxCpMessageTest {
WxCpMessage reply = WxCpMessage.MPNEWS().toUser("OPENID").addArticle(article1, article2).build();

assertThat(reply.toJson())
.isEqualTo( "{\"touser\":\"OPENID\",\"msgtype\":\"mpnews\",\"safe\":\"0\",\"mpnews\":{\"articles\":[{\"title\":\"Happy Day\",\"thumb_media_id\":\"thumb\",\"author\":\"aaaaaa\",\"content_source_url\":\"nice url\",\"content\":\"hahaha\",\"digest\":\"digest\",\"show_cover_pic\":\"heihei\"},{\"title\":\"Happy Day\",\"thumb_media_id\":\"thumb\",\"author\":\"aaaaaa\",\"content_source_url\":\"nice url\",\"content\":\"hahaha\",\"digest\":\"digest\",\"show_cover_pic\":\"heihei\"}]}}");
.isEqualTo("{\"touser\":\"OPENID\",\"msgtype\":\"mpnews\",\"mpnews\":{\"articles\":" +
"[{\"title\":\"Happy Day\",\"thumb_media_id\":\"thumb\",\"author\":\"aaaaaa\"," +
"\"content_source_url\":\"nice url\",\"content\":\"hahaha\",\"digest\":\"digest\",\"show_cover_pic\":\"heihei\"}" +
",{\"title\":\"Happy Day\",\"thumb_media_id\":\"thumb\",\"author\":\"aaaaaa\"," +
"\"content_source_url\":\"nice url\",\"content\":\"hahaha\",\"digest\":\"digest\",\"show_cover_pic\":\"heihei\"}]}," +
"\"safe\":\"0\"}");
}

public void testMpnewsBuild_with_media_id() {
WxCpMessage reply = WxCpMessage.MPNEWS().toUser("OPENID").mediaId("mmm").build();

assertThat(reply.toJson())
.isEqualTo("{\"touser\":\"OPENID\",\"msgtype\":\"mpnews\",\"safe\":\"0\",\"mpnews\":{\"media_id\":\"mmm\"}}");
.isEqualTo("{\"touser\":\"OPENID\",\"msgtype\":\"mpnews\",\"mpnews\":{\"media_id\":\"mmm\"},\"safe\":\"0\"}");
}

public void testTaskCardBuilder() {
TaskCardButton button1 = TaskCardButton.builder()
.key("yes")
.name("批准")
.replaceName("已批准")
.color("blue")
.bold(true)
.build();
TaskCardButton button2 = TaskCardButton.builder()
.key("yes")
.name("拒绝")
.replaceName("已拒绝")
.color("red")
.bold(false)
.build();
WxCpMessage reply = WxCpMessage.TASKCARD().toUser("OPENID")
.title("任务卡片")
.description("有一条待处理任务")
.url("http://www.qq.com")
.taskId("task_123")
.buttons(Arrays.asList(button1, button2))
.build();
assertThat(reply.toJson())
.isEqualTo("{\"touser\":\"OPENID\",\"msgtype\":\"taskcard\",\"taskcard\":{\"title\":\"任务卡片\",\"description\":\"有一条待处理任务\",\"url\":\"http://www.qq.com\",\"task_id\":\"task_123\",\"btn\":[{\"key\":\"yes\",\"name\":\"批准\",\"replace_name\":\"已批准\",\"color\":\"blue\",\"is_bold\":true},{\"key\":\"yes\",\"name\":\"拒绝\",\"replace_name\":\"已拒绝\",\"color\":\"red\",\"is_bold\":false}]}}");
}

}

+ 58
- 2
weixin-java-cp/src/test/java/me/chanjar/weixin/cp/bean/WxCpXmlMessageTest.java Переглянути файл

@@ -1,9 +1,11 @@
package me.chanjar.weixin.cp.bean;

import me.chanjar.weixin.common.api.WxConsts;
import org.testng.annotations.*;
import org.testng.annotations.Test;

import static org.testng.Assert.*;
import static me.chanjar.weixin.cp.WxCpConsts.EventType.TASKCARD_CLICK;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertNotNull;

@Test
public class WxCpXmlMessageTest {
@@ -117,4 +119,58 @@ public class WxCpXmlMessageTest {
assertEquals(wxMessage.getSendPicsInfo().getPicList().get(0).getPicMd5Sum(), "aef52ae501537e552725c5d7f99c1741");
assertEquals(wxMessage.getSendPicsInfo().getPicList().get(1).getPicMd5Sum(), "c4564632a4fab91378c39bea6aad6f9e");
}

public void testExtAttr() {

String xml = "<xml>" +
" <ToUserName><![CDATA[w56c9fe3d50ad1ea2]]></ToUserName>" +
" <FromUserName><![CDATA[sys]]></FromUserName>" +
" <CreateTime>1557241961</CreateTime>" +
" <MsgType><![CDATA[event]]></MsgType>" +
" <Event><![CDATA[change_contact]]></Event>" +
" <ChangeType><![CDATA[update_user]]></ChangeType>" +
" <UserID><![CDATA[zhangsan]]></UserID>" +
" <ExtAttr>" +
" <Item><Name><![CDATA[爱好]]></Name><Value><![CDATA[111]]></Value><Text><Value><![CDATA[111]]></Value></Text></Item>" +
" <Item><Name><![CDATA[入职时间]]></Name><Value><![CDATA[11111]]></Value><Text><Value><![CDATA[11111]]></Value></Text></Item>" +
" <Item><Name><![CDATA[城市]]></Name><Value><![CDATA[11111]]></Value><Text><Value><![CDATA[11111]]></Value></Text></Item>" +
" </ExtAttr>" +
" <Address><![CDATA[11111]]></Address>" +
"</xml>";
WxCpXmlMessage wxMessage = WxCpXmlMessage.fromXml(xml);
assertEquals(wxMessage.getToUserName(), "w56c9fe3d50ad1ea2");
assertEquals(wxMessage.getFromUserName(), "sys");
assertEquals(wxMessage.getCreateTime(), new Long(1557241961));
assertEquals(wxMessage.getMsgType(), WxConsts.XmlMsgType.EVENT);
assertEquals(wxMessage.getEvent(), "change_contact");
assertEquals(wxMessage.getChangeType(), "update_user");
assertEquals(wxMessage.getUserId(), "zhangsan");
assertNotNull(wxMessage.getExtAttrs());
assertNotNull(wxMessage.getExtAttrs().getItems());
assertEquals(wxMessage.getExtAttrs().getItems().size(), 3);
assertEquals(wxMessage.getExtAttrs().getItems().get(0).getName(), "爱好");

}

public void testTaskCardEvent() {
String xml = "<xml>" +
"<ToUserName><![CDATA[toUser]]></ToUserName>" +
"<FromUserName><![CDATA[FromUser]]></FromUserName>" +
"<CreateTime>123456789</CreateTime>" +
"<MsgType><![CDATA[event]]></MsgType>" +
"<Event><![CDATA[taskcard_click]]></Event>" +
"<EventKey><![CDATA[key111]]></EventKey>" +
"<TaskId><![CDATA[taskid111]]></TaskId >" +
"<AgentID>1</AgentID>" +
"</xml>";
WxCpXmlMessage wxMessage = WxCpXmlMessage.fromXml(xml);
assertEquals(wxMessage.getToUserName(), "toUser");
assertEquals(wxMessage.getFromUserName(), "FromUser");
assertEquals(wxMessage.getCreateTime(), Long.valueOf(123456789L));
assertEquals(wxMessage.getMsgType(), WxConsts.XmlMsgType.EVENT);
assertEquals(wxMessage.getAgentId(), Integer.valueOf(1));
assertEquals(wxMessage.getEvent(), TASKCARD_CLICK);
assertEquals(wxMessage.getEventKey(), "key111");
assertEquals(wxMessage.getTaskId(), "taskid111");
}
}

+ 5
- 10
weixin-java-cp/src/test/java/me/chanjar/weixin/cp/demo/WxCpDemoInMemoryConfigStorage.java Переглянути файл

@@ -1,28 +1,23 @@
package me.chanjar.weixin.cp.demo;

import java.io.InputStream;

import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.annotations.XStreamAlias;
import lombok.ToString;
import me.chanjar.weixin.common.util.xml.XStreamInitializer;
import me.chanjar.weixin.cp.config.WxCpInMemoryConfigStorage;

import java.io.InputStream;

/**
* @author Daniel Qian
*/
@XStreamAlias("xml")
class WxCpDemoInMemoryConfigStorage extends WxCpInMemoryConfigStorage {
@ToString
public class WxCpDemoInMemoryConfigStorage extends WxCpInMemoryConfigStorage {
public static WxCpDemoInMemoryConfigStorage fromXml(InputStream is) {
XStream xstream = XStreamInitializer.getInstance();
xstream.processAnnotations(WxCpDemoInMemoryConfigStorage.class);
return (WxCpDemoInMemoryConfigStorage) xstream.fromXML(is);
}

@Override
public String toString() {
return "SimpleWxConfigProvider [appidOrCorpid=" + this.corpId + ", corpSecret=" + this.corpSecret + ", accessToken=" + this.accessToken
+ ", expiresTime=" + this.expiresTime + ", token=" + this.token + ", aesKey=" + this.aesKey + "]";
}

}

+ 13
- 1
weixin-java-cp/src/test/java/me/chanjar/weixin/cp/util/json/WxCpUserGsonAdapterTest.java Переглянути файл

@@ -76,6 +76,13 @@ public class WxCpUserGsonAdapterTest {

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

assertThat(user.getOrders()).isNotEmpty();
assertThat(user.getOrders().length).isEqualTo(2);
assertThat(user.getOrders()[0]).isEqualTo(1);
assertThat(user.getOrders()[1]).isEqualTo(2);


assertThat(user.getExternalAttrs()).isNotEmpty();

final WxCpUser.ExternalAttribute externalAttr1 = user.getExternalAttrs().get(0);
@@ -100,6 +107,7 @@ public class WxCpUserGsonAdapterTest {
@Test
public void testSerialize() {
WxCpUser user = new WxCpUser();
user.setOrders(new Integer[]{1, 2});
user.addExternalAttr(WxCpUser.ExternalAttribute.builder()
.type(0)
.name("文本名称")
@@ -119,6 +127,10 @@ public class WxCpUserGsonAdapterTest {
.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\"}}]}}");
assertThat(user.toJson()).isEqualTo("{\"order\":[1,2],\"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\"}}]}}");
}
}

+ 9
- 3
weixin-java-miniapp/pom.xml Переглянути файл

@@ -7,11 +7,12 @@
<parent>
<groupId>com.github.binarywang</groupId>
<artifactId>wx-java</artifactId>
<version>3.3.0</version>
<version>3.4.0</version>
</parent>

<artifactId>weixin-java-miniapp</artifactId>
<name>WxJava - MiniApp</name>
<description>微信小程序Java SDK</description>
<name>WxJava - MiniApp Java SDK</name>
<description>微信小程序 Java SDK</description>

<dependencies>
<dependency>
@@ -70,6 +71,11 @@
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcpkix-jdk15on</artifactId>
<version>1.59</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>


+ 18
- 22
weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaCodeService.java Переглянути файл

@@ -1,14 +1,10 @@
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;
import cn.binarywang.wx.miniapp.bean.code.WxMaCodeSubmitAuditRequest;
import cn.binarywang.wx.miniapp.bean.code.WxMaCodeVersionDistribution;
import cn.binarywang.wx.miniapp.bean.code.*;
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
@@ -18,7 +14,7 @@ import me.chanjar.weixin.common.error.WxErrorException;
*/
public interface WxMaCodeService {
/**
* 为授权的小程序帐号上传小程序代码
* 为授权的小程序帐号上传小程序代码.
*/
String COMMIT_URL = "https://api.weixin.qq.com/wxa/commit";
String GET_QRCODE_URL = "https://api.weixin.qq.com/wxa/get_qrcode";
@@ -35,7 +31,7 @@ public interface WxMaCodeService {
String UNDO_CODE_AUDIT_URL = "https://api.weixin.qq.com/wxa/undocodeaudit";

/**
* 为授权的小程序帐号上传小程序代码(仅仅支持第三方开放平台)
* 为授权的小程序帐号上传小程序代码(仅仅支持第三方开放平台).
*
* @param commitRequest 参数
* @throws WxErrorException 上传失败时抛出,具体错误码请看类注释文档
@@ -43,19 +39,19 @@ public interface WxMaCodeService {
void commit(WxMaCodeCommitRequest commitRequest) throws WxErrorException;

/**
* 获取体验小程序的体验二维码
* 获取体验小程序的体验二维码.
* 文档地址:
* 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
* 具体的路径加参数需要urlencode(方法内部处理),比如page/index?action=1编码后得到page%2Findex%3Faction%3D1
* @return 二维码 bytes
* @throws WxErrorException 上传失败时抛出,具体错误码请看类注释文档
*/
byte[] getQrCode(String path) throws WxErrorException;

/**
* 获取授权小程序帐号的可选类目
* 获取授权小程序帐号的可选类目.
*
* @return List<WxMaCategory>
* @throws WxErrorException 获取失败时返回,具体错误码请看此接口的注释文档
@@ -63,7 +59,7 @@ public interface WxMaCodeService {
List<WxMaCategory> getCategory() throws WxErrorException;

/**
* 获取小程序的第三方提交代码的页面配置(仅供第三方开发者代小程序调用)
* 获取小程序的第三方提交代码的页面配置(仅供第三方开发者代小程序调用).
*
* @return page_list 页面配置列表
* @throws WxErrorException 获取失败时返回,具体错误码请看此接口的注释文档
@@ -71,7 +67,7 @@ public interface WxMaCodeService {
List<String> getPage() throws WxErrorException;

/**
* 将第三方提交的代码包提交审核(仅供第三方开发者代小程序调用)
* 将第三方提交的代码包提交审核(仅供第三方开发者代小程序调用).
*
* @param auditRequest 提交审核参数
* @return 审核编号
@@ -80,7 +76,7 @@ public interface WxMaCodeService {
long submitAudit(WxMaCodeSubmitAuditRequest auditRequest) throws WxErrorException;

/**
* 查询某个指定版本的审核状态(仅供第三方代小程序调用)
* 查询某个指定版本的审核状态(仅供第三方代小程序调用).
*
* @param auditId 提交审核时获得的审核id
* @return 审核状态
@@ -89,7 +85,7 @@ public interface WxMaCodeService {
WxMaCodeAuditStatus getAuditStatus(long auditId) throws WxErrorException;

/**
* 查询最新一次提交的审核状态(仅供第三方代小程序调用)
* 查询最新一次提交的审核状态(仅供第三方代小程序调用).
*
* @return 审核状态
* @throws WxErrorException 查询失败时返回,具体错误码请看此接口的注释文档
@@ -97,14 +93,14 @@ public interface WxMaCodeService {
WxMaCodeAuditStatus getLatestAuditStatus() throws WxErrorException;

/**
* 发布已通过审核的小程序(仅供第三方代小程序调用)
* 发布已通过审核的小程序(仅供第三方代小程序调用).
*
* @throws WxErrorException 发布失败时抛出,具体错误码请看此接口的注释文档
*/
void release() throws WxErrorException;

/**
* 修改小程序线上代码的可见状态(仅供第三方代小程序调用)
* 修改小程序线上代码的可见状态(仅供第三方代小程序调用).
*
* @param action 设置可访问状态,发布后默认可访问,close为不可见,open为可见
* @throws WxErrorException 发布失败时抛出,具体错误码请看此接口的注释文档
@@ -112,14 +108,14 @@ public interface WxMaCodeService {
void changeVisitStatus(String action) throws WxErrorException;

/**
* 小程序版本回退(仅供第三方代小程序调用)
* 小程序版本回退(仅供第三方代小程序调用).
*
* @throws WxErrorException 失败时抛出,具体错误码请看此接口的注释文档
*/
void revertCodeRelease() throws WxErrorException;

/**
* 查询当前设置的最低基础库版本及各版本用户占比 (仅供第三方代小程序调用)
* 查询当前设置的最低基础库版本及各版本用户占比 (仅供第三方代小程序调用).
*
* @return 小程序版本分布信息
* @throws WxErrorException 失败时抛出,具体错误码请看此接口的注释文档
@@ -127,7 +123,7 @@ public interface WxMaCodeService {
WxMaCodeVersionDistribution getSupportVersion() throws WxErrorException;

/**
* 设置最低基础库版本(仅供第三方代小程序调用)
* 设置最低基础库版本(仅供第三方代小程序调用).
*
* @param version 版本
* @throws WxErrorException 失败时抛出,具体错误码请看此接口的注释文档
@@ -135,7 +131,7 @@ public interface WxMaCodeService {
void setSupportVersion(String version) throws WxErrorException;

/**
* 小程序审核撤回
* 小程序审核撤回.
* 单个帐号每天审核撤回次数最多不超过1次,一个月不超过10次
*
* @throws WxErrorException 失败时抛出,具体错误码请看此接口的注释文档


+ 2
- 2
weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaMsgService.java Переглянути файл

@@ -20,7 +20,7 @@ public interface WxMaMsgService {
/**
* <pre>
* 发送客服消息
* 详情请见: <a href="https://mp.weixin.qq.com/debug/wxadoc/dev/api/custommsg/conversation.html">发送客服消息</a>
* 详情请见: <a href="https://developers.weixin.qq.com/miniprogram/dev/api-backend/customerServiceMessage.send.html">发送客服消息</a>
* 接口url格式:https://api.weixin.qq.com/cgi-bin/message/custom/send?access_token=ACCESS_TOKEN
* </pre>
*/
@@ -29,7 +29,7 @@ public interface WxMaMsgService {
/**
* <pre>
* 发送模板消息
* 详情请见: <a href="https://mp.weixin.qq.com/debug/wxadoc/dev/api/notice.html#接口说明">发送模板消息</a>
* 详情请见: <a href="https://developers.weixin.qq.com/miniprogram/dev/api-backend/templateMessage.send.html">发送模板消息</a>
* 接口url格式:https://api.weixin.qq.com/cgi-bin/message/wxopen/template/send?access_token=ACCESS_TOKEN
* </pre>
*/


+ 23
- 0
weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaService.java Переглянути файл

@@ -17,6 +17,10 @@ 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";
/**
* getPaidUnionId
*/
String GET_PAID_UNION_ID_URL = "https://api.weixin.qq.com/wxa/getpaidunionid";

/**
* 获取登录后的session信息.
@@ -56,6 +60,22 @@ public interface WxMaService {
*/
String getAccessToken(boolean forceRefresh) throws WxErrorException;

/**
* <pre>
* 用户支付完成后,获取该用户的 UnionId,无需用户授权。本接口支持第三方平台代理查询。
*
* 注意:调用前需要用户完成支付,且在支付后的五分钟内有效。
* 请求地址: GET https://api.weixin.qq.com/wxa/getpaidunionid?access_token=ACCESS_TOKEN&openid=OPENID
* 文档地址:https://developers.weixin.qq.com/miniprogram/dev/api/getPaidUnionId.html
* </pre>
*
* @param openid 必填 支付用户唯一标识
* @param transactionId 非必填 微信支付订单号
* @param mchId 非必填 微信支付分配的商户号,和商户订单号配合使用
* @param outTradeNo 非必填 微信支付商户订单号,和商户号配合使用
*/
String getPaidUnionId(String openid, String transactionId, String mchId, String outTradeNo) throws WxErrorException;

/**
* 当本Service没有实现某个API的时候,可以用这个,针对所有微信API中的GET请求.
*/
@@ -168,18 +188,21 @@ public interface WxMaService {

/**
* 返回分享相关查询服务.
*
* @return WxMaShareService
*/
WxMaShareService getShareService();

/**
* 返回微信运动相关接口服务对象.
*
* @return WxMaShareService
*/
WxMaRunService getRunService();

/**
* 返回内容安全相关接口服务对象.
*
* @return WxMaShareService
*/
WxMaSecCheckService getSecCheckService();


+ 72
- 57
weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaServiceImpl.java Переглянути файл

@@ -1,47 +1,33 @@
package cn.binarywang.wx.miniapp.api.impl;

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 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;
import cn.binarywang.wx.miniapp.api.WxMaRunService;
import cn.binarywang.wx.miniapp.api.WxMaSecCheckService;
import cn.binarywang.wx.miniapp.api.WxMaService;
import cn.binarywang.wx.miniapp.api.WxMaSettingService;
import cn.binarywang.wx.miniapp.api.WxMaShareService;
import cn.binarywang.wx.miniapp.api.WxMaTemplateService;
import cn.binarywang.wx.miniapp.api.WxMaUserService;
import cn.binarywang.wx.miniapp.api.*;
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 com.google.gson.JsonParser;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.common.WxType;
import me.chanjar.weixin.common.bean.WxAccessToken;
import me.chanjar.weixin.common.error.WxError;
import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.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.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.*;
import me.chanjar.weixin.common.util.http.apache.ApacheHttpClientBuilder;
import me.chanjar.weixin.common.util.http.apache.DefaultApacheHttpClientBuilder;
import org.apache.commons.lang3.StringUtils;
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 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.*;

@@ -50,6 +36,7 @@ import static cn.binarywang.wx.miniapp.constant.WxMaConstants.ErrorCode.*;
*/
@Slf4j
public class WxMaServiceImpl implements WxMaService, RequestHttp<CloseableHttpClient, HttpHost> {
private static final JsonParser JSON_PARSER = new JsonParser();
private CloseableHttpClient httpClient;
private HttpHost httpProxy;
private WxMaConfig wxMaConfig;
@@ -114,40 +101,68 @@ public class WxMaServiceImpl implements WxMaService, RequestHttp<CloseableHttpCl

@Override
public String getAccessToken(boolean forceRefresh) throws WxErrorException {
if (!this.getWxMaConfig().isAccessTokenExpired() && !forceRefresh) {
return this.getWxMaConfig().getAccessToken();
}

Lock lock = this.getWxMaConfig().getAccessTokenLock();
lock.lock();
try {
lock.lock();

if (this.getWxMaConfig().isAccessTokenExpired() || forceRefresh) {
String url = String.format(WxMaService.GET_ACCESS_TOKEN_URL, this.getWxMaConfig().getAppid(),
this.getWxMaConfig().getSecret());
try {
HttpGet httpGet = new HttpGet(url);
if (this.getRequestHttpProxy() != null) {
RequestConfig config = RequestConfig.custom().setProxy(this.getRequestHttpProxy()).build();
httpGet.setConfig(config);
}
try (CloseableHttpResponse response = getRequestHttpClient().execute(httpGet)) {
String resultContent = new BasicResponseHandler().handleResponse(response);
WxError error = WxError.fromJson(resultContent);
if (error.getErrorCode() != 0) {
throw new WxErrorException(error);
}
WxAccessToken accessToken = WxAccessToken.fromJson(resultContent);
this.getWxMaConfig().updateAccessToken(accessToken.getAccessToken(),
accessToken.getExpiresIn());
} finally {
httpGet.releaseConnection();
String url = String.format(WxMaService.GET_ACCESS_TOKEN_URL, this.getWxMaConfig().getAppid(),
this.getWxMaConfig().getSecret());
try {
HttpGet httpGet = new HttpGet(url);
if (this.getRequestHttpProxy() != null) {
RequestConfig config = RequestConfig.custom().setProxy(this.getRequestHttpProxy()).build();
httpGet.setConfig(config);
}
try (CloseableHttpResponse response = getRequestHttpClient().execute(httpGet)) {
String resultContent = new BasicResponseHandler().handleResponse(response);
WxError error = WxError.fromJson(resultContent);
if (error.getErrorCode() != 0) {
throw new WxErrorException(error);
}
} catch (IOException e) {
throw new RuntimeException(e);
WxAccessToken accessToken = WxAccessToken.fromJson(resultContent);
this.getWxMaConfig().updateAccessToken(accessToken.getAccessToken(), accessToken.getExpiresIn());
return this.getWxMaConfig().getAccessToken();
} finally {
httpGet.releaseConnection();
}
} catch (IOException e) {
throw new RuntimeException(e);
}
} finally {
lock.unlock();
}

return this.getWxMaConfig().getAccessToken();
}

@Override
public String getPaidUnionId(String openid, String transactionId, String mchId, String outTradeNo)
throws WxErrorException {
Map<String, String> params = new HashMap<>(8);
params.put("openid", openid);

if (StringUtils.isNotEmpty(transactionId)) {
params.put("transaction_id", transactionId);
}

if (StringUtils.isNotEmpty(mchId)) {
params.put("mch_id", mchId);
}

if (StringUtils.isNotEmpty(outTradeNo)) {
params.put("out_trade_no", outTradeNo);
}

String responseContent = this.get(GET_PAID_UNION_ID_URL, Joiner.on("&").withKeyValueSeparator("=").join(params));
WxError error = WxError.fromJson(responseContent, WxType.MiniApp);
if (error.getErrorCode() != 0) {
throw new WxErrorException(error);
}

return JSON_PARSER.parse(responseContent).getAsJsonObject().get("unionid").getAsString();
}

@Override
@@ -168,7 +183,7 @@ public class WxMaServiceImpl implements WxMaService, RequestHttp<CloseableHttpCl
try {
return SHA1.gen(this.getWxMaConfig().getToken(), timestamp, nonce).equals(signature);
} catch (Exception e) {
this.log.error("Checking signature failed, and the reason is :" + e.getMessage());
log.error("Checking signature failed, and the reason is :" + e.getMessage());
return false;
}
}
@@ -246,7 +261,7 @@ public class WxMaServiceImpl implements WxMaService, RequestHttp<CloseableHttpCl
if (error.getErrorCode() == ERR_40001
|| error.getErrorCode() == ERR_42001
|| error.getErrorCode() == ERR_40014) {
// 强制设置wxMpConfigStorage它的access token过期了,这样在下一次请求里就会刷新access token
// 强制设置WxMaConfig的access token过期了,这样在下一次请求里就会刷新access token
this.getWxMaConfig().expireAccessToken();
if (this.getWxMaConfig().autoRefreshToken()) {
return this.execute(executor, uri, data);


+ 12
- 4
weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/WxMaKefuMessage.java Переглянути файл

@@ -41,20 +41,26 @@ public class WxMaKefuMessage implements Serializable {

@Data
@AllArgsConstructor
public static class KfText {
public static class KfText implements Serializable {
private static final long serialVersionUID = 151122958720941270L;

private String content;
}

@Data
@AllArgsConstructor
public static class KfImage {
public static class KfImage implements Serializable {
private static final long serialVersionUID = -5409342945117300782L;

@SerializedName("media_id")
private String mediaId;
}

@Data
@Builder
public static class KfLink {
public static class KfLink implements Serializable {
private static final long serialVersionUID = -6728776817556127413L;

private String title;
private String description;
private String url;
@@ -65,7 +71,9 @@ public class WxMaKefuMessage implements Serializable {

@Data
@Builder
public static class KfMaPage {
public static class KfMaPage implements Serializable {
private static final long serialVersionUID = -5633492281871634466L;

private String title;

@SerializedName("pagepath")


+ 2
- 1
weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/WxMaTemplateData.java Переглянути файл

@@ -5,7 +5,7 @@ import lombok.NoArgsConstructor;

/**
* <pre>
*
* 参考文档 https://developers.weixin.qq.com/miniprogram/dev/api-backend/templateMessage.send.html
* Created by Binary Wang on 2018/9/23.
* </pre>
*
@@ -29,4 +29,5 @@ public class WxMaTemplateData {
this.color = color;
}


}

+ 1
- 11
weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/WxMaTemplateMessage.java Переглянути файл

@@ -13,7 +13,7 @@ import lombok.Setter;

/**
* 模板消息.
* 参考 https://mp.weixin.qq.com/debug/wxadoc/dev/api/notice.html#接口说明 模板消息部分
* 参考 https://developers.weixin.qq.com/miniprogram/dev/api-backend/templateMessage.send.html
*
* @author <a href="https://github.com/binarywang">Binary Wang</a>
*/
@@ -75,16 +75,6 @@ public class WxMaTemplateMessage implements Serializable {
*/
private List<WxMaTemplateData> data;

/**
* 模板内容字体的颜色,不填默认黑色.
* <pre>
* 参数:color
* 是否必填: 否
* 描述: 模板内容字体的颜色,不填默认黑色
* </pre>
*/
private String color;

/**
* 模板需要放大的关键词,不填则默认无放大.
* <pre>


+ 6
- 0
weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/WxMaUniformMessage.java Переглянути файл

@@ -104,5 +104,11 @@ public class WxMaUniformMessage implements Serializable {
* 加入此字段是基于微信官方接口变化多端的考虑
*/
private boolean usePath = false;

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

+ 9
- 3
weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/code/WxMaCodeAuditStatus.java Переглянути файл

@@ -18,18 +18,24 @@ import java.io.Serializable;
public class WxMaCodeAuditStatus implements Serializable {
private static final long serialVersionUID = 4655119308692217268L;
/**
* 审核 ID
* 审核 ID.
*/
@SerializedName(value = "auditId", alternate = {"auditid"})
private Long auditId;
/**
* 审核状态,其中0为审核成功,1为审核失败,2为审核中
* 审核状态.
* 其中0为审核成功,1为审核失败,2为审核中
*/
private Integer status;
/**
* 当status=1,审核被拒绝时,返回的拒绝原因
* 当status=1,审核被拒绝时,返回的拒绝原因.
*/
private String reason;
/**
* 当status=1,审核被拒绝时,会返回审核失败的小程序截图示例。 xxx丨yyy丨zzz是media_id可通过获取永久素材接口 拉取截图内容).
*/
@SerializedName(value = "screenshot")
private String screenShot;

public static WxMaCodeAuditStatus fromJson(String json) {
return WxMaGsonBuilder.create().fromJson(json, WxMaCodeAuditStatus.class);


+ 49
- 9
weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/util/crypt/WxMaCryptUtils.java Переглянути файл

@@ -1,19 +1,27 @@
package cn.binarywang.wx.miniapp.util.crypt;

import cn.binarywang.wx.miniapp.config.WxMaConfig;
import me.chanjar.weixin.common.util.crypto.PKCS7Encoder;
import org.apache.commons.codec.binary.Base64;

import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.security.AlgorithmParameters;
import java.security.Key;
import java.security.Security;
import java.util.Arrays;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.AlgorithmParameters;

import org.apache.commons.codec.binary.Base64;
import org.bouncycastle.jce.provider.BouncyCastleProvider;

import cn.binarywang.wx.miniapp.config.WxMaConfig;
import me.chanjar.weixin.common.util.crypto.PKCS7Encoder;

/**
* @author <a href="https://github.com/binarywang">Binary Wang</a>
*/
public class WxMaCryptUtils extends me.chanjar.weixin.common.util.crypto.WxCryptUtil {
private static final Charset UTF_8 = StandardCharsets.UTF_8;

public WxMaCryptUtils(WxMaConfig config) {
this.appidOrCorpid = config.getAppid();
this.token = config.getToken();
@@ -21,8 +29,9 @@ public class WxMaCryptUtils extends me.chanjar.weixin.common.util.crypto.WxCrypt
}

/**
* AES解密
* AES解密.
*
* @param sessionKey session_key
* @param encryptedData 消息密文
* @param ivStr iv字符串
*/
@@ -34,9 +43,40 @@ public class WxMaCryptUtils extends me.chanjar.weixin.common.util.crypto.WxCrypt
Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(Base64.decodeBase64(sessionKey), "AES"), params);

return new String(PKCS7Encoder.decode(cipher.doFinal(Base64.decodeBase64(encryptedData))), StandardCharsets.UTF_8);
return new String(PKCS7Encoder.decode(cipher.doFinal(Base64.decodeBase64(encryptedData))), UTF_8);
} catch (Exception e) {
throw new RuntimeException("AES解密失败!", e);
}
}


/**
* AES解密.
*
* @param sessionKey session_key
* @param encryptedData 消息密文
* @param ivStr iv字符串
*/
public static String decryptAnotherWay(String sessionKey, String encryptedData, String ivStr) {
byte[] keyBytes = Base64.decodeBase64(sessionKey.getBytes(UTF_8));

int base = 16;
if (keyBytes.length % base != 0) {
int groups = keyBytes.length / base + (keyBytes.length % base != 0 ? 1 : 0);
byte[] temp = new byte[groups * base];
Arrays.fill(temp, (byte) 0);
System.arraycopy(keyBytes, 0, temp, 0, keyBytes.length);
keyBytes = temp;
}

Security.addProvider(new BouncyCastleProvider());
Key key = new SecretKeySpec(keyBytes, "AES");
try {
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding", "BC");
cipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(Base64.decodeBase64(ivStr.getBytes(UTF_8))));
return new String(cipher.doFinal(Base64.decodeBase64(encryptedData.getBytes(UTF_8))), UTF_8);
} catch (Exception e) {
throw new RuntimeException("AES解密失败", e);
throw new RuntimeException("AES解密失败", e);
}
}



+ 0
- 7
weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/util/json/WxMaTemplateMessageGsonAdapter.java Переглянути файл

@@ -27,10 +27,6 @@ public class WxMaTemplateMessageGsonAdapter implements JsonSerializer<WxMaTempla
messageJson.addProperty("form_id", message.getFormId());
}

if (message.getColor() != null) {
messageJson.addProperty("color", message.getColor());
}

if (message.getEmphasisKeyword() != null) {
messageJson.addProperty("emphasis_keyword", message.getEmphasisKeyword());
}
@@ -45,9 +41,6 @@ public class WxMaTemplateMessageGsonAdapter implements JsonSerializer<WxMaTempla
for (WxMaTemplateData datum : message.getData()) {
JsonObject dataJson = new JsonObject();
dataJson.addProperty("value", datum.getValue());
if (datum.getColor() != null) {
dataJson.addProperty("color", datum.getColor());
}
data.add(datum.getName(), dataJson);
}



+ 2
- 3
weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/util/json/WxMaUniformMessageGsonAdapter.java Переглянути файл

@@ -36,6 +36,8 @@ public class WxMaUniformMessageGsonAdapter implements JsonSerializer<WxMaUniform
miniProgramJson.addProperty("appid", miniProgram.getAppid());
if (miniProgram.isUsePath()) {
miniProgramJson.addProperty("path", miniProgram.getPagePath());
} else if (miniProgram.isUsePagePath()) {
miniProgramJson.addProperty("pagePath", miniProgram.getPagePath());
} else {
miniProgramJson.addProperty("pagepath", miniProgram.getPagePath());
}
@@ -79,9 +81,6 @@ public class WxMaUniformMessageGsonAdapter implements JsonSerializer<WxMaUniform
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);
}
}


+ 2
- 2
weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/util/json/WxMaVisitDistributionGsonAdapter.java Переглянути файл

@@ -10,9 +10,9 @@ import com.google.gson.JsonParseException;
import me.chanjar.weixin.common.util.json.GsonHelper;

import java.lang.reflect.Type;
import java.util.Hashtable;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
* @author <a href="https://github.com/charmingoh">Charming</a>
@@ -36,7 +36,7 @@ public class WxMaVisitDistributionGsonAdapter implements JsonDeserializer<WxMaVi
}

JsonArray listArray = object.getAsJsonArray("list");
Map<String, Map<Integer, Integer>> list = new Hashtable<>(listArray.size());
Map<String, Map<Integer, Integer>> list = new ConcurrentHashMap<>(listArray.size());
for (JsonElement indexElement : listArray) {
JsonObject indexObject = indexElement.getAsJsonObject();
String index = GsonHelper.getString(indexObject, "index");


+ 4
- 4
weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/api/impl/WxMaMsgServiceImplTest.java Переглянути файл

@@ -48,10 +48,10 @@ public class WxMaMsgServiceImplTest {
.formId("FORMID")
.page("index")
.data(Lists.newArrayList(
new WxMaTemplateData("keyword1", "339208499", "#173177"),
new WxMaTemplateData("keyword2", dateFormat.format(new Date()), "#173177"),
new WxMaTemplateData("keyword3", "粤海喜来登酒店", "#173177"),
new WxMaTemplateData("keyword4", "广州市天河区天河路208号", "#173177")))
new WxMaTemplateData("keyword1", "339208499"),
new WxMaTemplateData("keyword2", dateFormat.format(new Date())),
new WxMaTemplateData("keyword3", "粤海喜来登酒店"),
new WxMaTemplateData("keyword4", "广州市天河区天河路208号")))
.templateId(config.getTemplateId())
.emphasisKeyword("keyword1.DATA")
.build();


+ 6
- 2
weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/api/impl/WxMaServiceImplTest.java Переглянути файл

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

import java.io.File;

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

@@ -11,6 +9,7 @@ import cn.binarywang.wx.miniapp.test.ApiTestModule;
import com.google.inject.Inject;
import me.chanjar.weixin.common.error.WxErrorException;

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

/**
@@ -33,4 +32,9 @@ public class WxMaServiceImplTest {
assertTrue(StringUtils.isNotBlank(after));
}

@Test(expectedExceptions = {WxErrorException.class})
public void testGetPaidUnionId() throws WxErrorException {
final String unionId = this.wxService.getPaidUnionId("1", null, "3", "4");
assertThat(unionId).isNotEmpty();
}
}

+ 35
- 0
weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/util/crypt/WxMaCryptUtilsTest.java Переглянути файл

@@ -0,0 +1,35 @@
package cn.binarywang.wx.miniapp.util.crypt;


import org.testng.annotations.*;

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

/**
* <pre>
*
* Created by Binary Wang on 2018/12/25.
* </pre>
*
* @author <a href="https://github.com/binarywang">Binary Wang</a>
*/
public class WxMaCryptUtilsTest {
@Test
public void testDecrypt() {
String sessionKey = "7MG7jbTToVVRWRXVA885rg==";
String encryptedData = "BY6VOgcWbwGcyrunK0ECWI8rnDsT69DucZ+M78tc1aL9aM/3bEAHFYd4fu7kRjWhD4YfjObw44T9vUqKyHIjbKs6hvtEasZZEIW35x4a91xVgN48ZqZ7MTQqUlP13kDUlkuwYh+/8g8yceu4kNbjowYrhihx+SV7CfjKCveJ7TSepr5Z7aLv1o+rfeelfOwn++WN/YoQsuZ6S3L4fWlWe5DAAUnFUI6cJvxxCohVzbrVXhyH2AqQdSjH2WnMYFeaGFIbcoxMznlk7oEwFn+hBj63dyT/swdYQfEdzuyCBmKXy8d6l1RKVX6Y65coTD8kIlbr+FKsqYrXVUIUBSwehqYuOdhYWZ9Bntl5DWU1oqzAPCnMn2cAIoQpQPKP7IGSxMOvCNAMhVXbE7BvnWuVuGF+AM5tXAa9IVUhcMImGwLQqm4iV5uBd+5OcFObh3A4VJk9iBCBWSkBHa/rV9CVoY0bFv2F9/2Hv82++Ybl274=";
String ivStr = "TarMFjnzHVxy8pdS93wQbw==";
System.out.println(WxMaCryptUtils.decrypt(sessionKey, encryptedData, ivStr));
// System.out.println(WxMaCryptUtils.decryptAnotherWay(sessionKey, encryptedData, ivStr));
}

@Test
public void testDecryptAnotherWay() {
String encryptedData = "CiyLU1Aw2KjvrjMdj8YKliAjtP4gsMZMQmRzooG2xrDcvSnxIMXFufNstNGTyaGS9uT5geRa0W4oTOb1WT7fJlAC+oNPdbB+3hVbJSRgv+4lGOETKUQz6OYStslQ142dNCuabNPGBzlooOmB231qMM85d2/fV6ChevvXvQP8Hkue1poOFtnEtpyxVLW1zAo6/1Xx1COxFvrc2d7UL/lmHInNlxuacJXwu0fjpXfz/YqYzBIBzD6WUfTIF9GRHpOn/Hz7saL8xz+W//FRAUid1OksQaQx4CMs8LOddcQhULW4ucetDf96JcR3g0gfRK4PC7E/r7Z6xNrXd2UIeorGj5Ef7b1pJAYB6Y5anaHqZ9J6nKEBvB4DnNLIVWSgARns/8wR2SiRS7MNACwTyrGvt9ts8p12PKFdlqYTopNHR1Vf7XjfhQlVsAJdNiKdYmYVoKlaRv85IfVunYzO0IKXsyl7JCUjCpoG20f0a04COwfneQAGGwd5oa+T8yO5hzuyDb/XcxxmK01EpqOyuxINew==";
String ivStr = "r7BXXKkLb8qrSNn05n0qiA==";
String sessionKey = "tiihtNczf5v6AKRyjwEUhQ==";

assertThat(WxMaCryptUtils.decrypt(sessionKey, encryptedData, ivStr))
.isEqualTo(WxMaCryptUtils.decryptAnotherWay(sessionKey, encryptedData, ivStr));
}
}

+ 3
- 4
weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/util/json/WxMaUniformMessageGsonAdapterTest.java Переглянути файл

@@ -1,10 +1,9 @@
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 org.testng.annotations.Test;

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

@@ -26,7 +25,7 @@ public class WxMaUniformMessageGsonAdapterTest {
.appid("APPID")
.templateId("TEMPLATE_ID")
.url("http://weixin.qq.com/download")
.miniProgram(new WxMaUniformMessage.MiniProgram("xiaochengxuappid12345", "index?foo=bar", false))
.miniProgram(new WxMaUniformMessage.MiniProgram("xiaochengxuappid12345", "index?foo=bar", false, false))
.build();
message.addData(new WxMaTemplateData("first", "恭喜你购买成功!", "#173177"))
.addData(new WxMaTemplateData("keyword1", "巧克力", "#173177"))
@@ -72,7 +71,7 @@ public class WxMaUniformMessageGsonAdapterTest {

@Test
public void testSerialize_ma() {
WxMaUniformMessage message = WxMaUniformMessage.builder()
WxMaUniformMessage message = WxMaUniformMessage.builder()
.isMpTemplateMsg(false)
.toUser("OPENID")
.page("page/page/index")


+ 3
- 2
weixin-java-mp/pom.xml Переглянути файл

@@ -7,10 +7,11 @@
<parent>
<groupId>com.github.binarywang</groupId>
<artifactId>wx-java</artifactId>
<version>3.3.0</version>
<version>3.4.0</version>
</parent>

<artifactId>weixin-java-mp</artifactId>
<name>WxJava - MP</name>
<name>WxJava - MP Java SDK</name>
<description>微信公众号Java SDK</description>

<dependencies>


+ 5
- 27
weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpAiOpenService.java Переглянути файл

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

import java.io.File;

import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.mp.enums.AiLangType;

import java.io.File;

/**
* <pre>
* 微信AI开放接口(语音识别,微信翻译).
@@ -15,24 +15,15 @@ import java.io.File;
* @author <a href="https://github.com/binarywang">Binary Wang</a>
*/
public interface WxMpAiOpenService {
String TRANSLATE_URL = "http://api.weixin.qq.com/cgi-bin/media/voice/translatecontent?lfrom=%s&lto=%s";
String VOICE_UPLOAD_URL = "http://api.weixin.qq.com/cgi-bin/media/voice/addvoicetorecofortext?format=%s&voice_id=%s&lang=%s";
String VOICE_QUERY_RESULT_URL = "http://api.weixin.qq.com/cgi-bin/media/voice/queryrecoresultfortext";

/**
* <pre>
* 提交语音.
* 接口调用请求说明
*
* http请求方式: POST
* http://api.weixin.qq.com/cgi-bin/media/voice/addvoicetorecofortext?access_token=ACCESS_TOKEN&format=&voice_id=xxxxxx&lang=zh_CN
* 参数说明
*
* 参数 是否必须 说明
* access_token 是 接口调用凭证
* format 是 文件格式 (只支持mp3,16k,单声道,最大1M)
* voice_id 是 语音唯一标识
* lang 否 语言,zh_CN 或 en_US,默认中文
* 语音内容放body里或者上传文件的形式
* </pre>
*
* @param lang 语言,zh_CN 或 en_US,默认中文
@@ -46,16 +37,9 @@ public interface WxMpAiOpenService {
* 获取语音识别结果.
* 接口调用请求说明
*
* http请求方式: POST
* http://api.weixin.qq.com/cgi-bin/media/voice/queryrecoresultfortext?access_token=ACCESS_TOKEN&voice_id=xxxxxx&lang=zh_CN
* 请注意,添加完文件之后10s内调用这个接口
*
* 参数说明
*
* 参数 是否必须 说明
* access_token 是 接口调用凭证
* voice_id 是 语音唯一标识
* lang 否 语言,zh_CN 或 en_US,默认中文
* </pre>
*
* @param lang 语言,zh_CN 或 en_US,默认中文
@@ -80,18 +64,12 @@ public interface WxMpAiOpenService {
*
* http请求方式: POST
* http://api.weixin.qq.com/cgi-bin/media/voice/translatecontent?access_token=ACCESS_TOKEN&lfrom=xxx&lto=xxx
* 参数说明
*
* 参数 是否必须 说明
* access_token 是 接口调用凭证
* lfrom 是 源语言,zh_CN 或 en_US
* lto 是 目标语言,zh_CN 或 en_US
* 源内容放body里或者上传文件的形式(utf8格式,最大600Byte)
* </pre>
*
* @param langFrom 源语言,zh_CN 或 en_US
* @param langTo 目标语言,zh_CN 或 en_US
* @param content 要翻译的文本内容
* @param langTo 目标语言,zh_CN 或 en_US
* @param content 要翻译的文本内容
*/
String translate(AiLangType langFrom, AiLangType langTo, String content) throws WxErrorException;
}

+ 26
- 6
weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpCardService.java Переглянути файл

@@ -2,10 +2,8 @@ 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;
import me.chanjar.weixin.mp.bean.card.*;
import me.chanjar.weixin.mp.bean.card.WxMpCardResult;

/**
* 卡券相关接口
@@ -14,6 +12,7 @@ import me.chanjar.weixin.mp.bean.result.WxMpCardResult;
* @author yuanqixun 2018-08-29
*/
public interface WxMpCardService {
String CARD_CREATE = "https://api.weixin.qq.com/card/create";
String CARD_GET = "https://api.weixin.qq.com/card/get";
String CARD_GET_TICKET = "https://api.weixin.qq.com/cgi-bin/ticket/getticket?type=wx_card";
String CARD_CODE_DECRYPT = "https://api.weixin.qq.com/card/code/decrypt";
@@ -28,6 +27,11 @@ public interface WxMpCardService {
*/
String CARD_CODE_UNAVAILABLE = "https://api.weixin.qq.com/card/code/unavailable";

/**
* 卡券删除
*/
String CARD_DELETE = "https://api.weixin.qq.com/card/delete";

/**
* 得到WxMpService
*/
@@ -81,8 +85,8 @@ public interface WxMpCardService {
String decryptCardCode(String encryptCode) throws WxErrorException;

/**
* 卡券Code查询
*
* 卡券Code查询.
* 文档地址: https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1451025272&anchor=1
* @param cardId 卡券ID代表一类卡券
* @param code 单张卡券的唯一标准
* @param checkConsume 是否校验code核销状态,填入true和false时的code异常状态返回数据不同
@@ -142,6 +146,14 @@ public interface WxMpCardService {
*/
String addTestWhiteList(String openid) throws WxErrorException;

/**
*
* @param cardCreateMessage
* @return
* @throws WxErrorException
*/
WxMpCardCreateResult createCard(WxMpCardCreateMessage cardCreateMessage) throws WxErrorException;

/**
* 创建卡券二维码
*
@@ -182,4 +194,12 @@ public interface WxMpCardService {
*/
String unavailableCardCode(String cardId, String code, String reason) throws WxErrorException;

/**
* 删除卡券接口
* @param cardId
* @return
* @throws WxErrorException
*/
WxMpCardDeleteResult deleteCard(String cardId) throws WxErrorException;

}

+ 71
- 0
weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpMarketingService.java Переглянути файл

@@ -0,0 +1,71 @@
package me.chanjar.weixin.mp.api;

import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.mp.bean.marketing.WxMpAdLeadFilter;
import me.chanjar.weixin.mp.bean.marketing.WxMpAdLeadResult;
import me.chanjar.weixin.mp.bean.marketing.WxMpUserAction;
import me.chanjar.weixin.mp.bean.marketing.WxMpUserActionSet;

import java.io.IOException;
import java.util.Date;
import java.util.List;

/**
* <pre>
* 微信营销接口
* </pre>
*
* @author <a href="https://github.com/007gzs">007</a>
*/
public interface WxMpMarketingService {
String API_URL_PREFIX = "https://api.weixin.qq.com/marketing/";

/**
* <pre>
* 创建数据源
* 接口调用请求说明
* https://wximg.qq.com/wxp/pdftool/get.html?id=rkalQXDBM&pa=39
* </pre>
*
* @param type 用户行为源类型
* @param name 用户行为源名称 必填
* @param description 用户行为源描述,字段长度最小 1 字节,长度最大 128 字节
*/
long addUserActionSets(String type, String name, String description) throws WxErrorException;

/**
* <pre>
* 获取数据源信息
* </pre>
*
* @param userActionSetId 数据源唯一ID
*/
List<WxMpUserActionSet> getUserActionSets(Long userActionSetId) throws WxErrorException;

/**
* 回传数据
* 接口调用请求说明
* https://wximg.qq.com/wxp/pdftool/get.html?id=rkalQXDBM&pa=39
*
* @param actions 用户行为源类型
*/
void addUserAction(List<WxMpUserAction> actions) throws WxErrorException;

/**
* <pre>
* 获取朋友圈销售线索数据接口
* 接口调用请求说明
*
* http请求方式: POST
* http://api.weixin.qq.com/cgi-bin/media/voice/translatecontent?access_token=ACCESS_TOKEN&lfrom=xxx&lto=xxx
*
* </pre>
*
* @param beginDate 开始日期
* @param endDate 结束日期
* @param filtering 过滤条件
* @param page 页码,获取指定页数据
* @param page_size 一页获取的数据条数(1-100)
*/
WxMpAdLeadResult getAdLeads(Date beginDate, Date endDate, List<WxMpAdLeadFilter> filtering, Integer page, Integer page_size) throws WxErrorException, IOException;
}

+ 44
- 10
weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpMemberCardService.java Переглянути файл

@@ -6,12 +6,7 @@ import me.chanjar.weixin.mp.bean.card.MemberCardActivateUserFormRequest;
import me.chanjar.weixin.mp.bean.card.MemberCardActivateUserFormResult;
import me.chanjar.weixin.mp.bean.card.MemberCardUpdateRequest;
import me.chanjar.weixin.mp.bean.card.WxMpCardCreateResult;
import me.chanjar.weixin.mp.bean.membercard.ActivatePluginParam;
import me.chanjar.weixin.mp.bean.membercard.WxMpMemberCardActivatedMessage;
import me.chanjar.weixin.mp.bean.membercard.WxMpMemberCardCreateMessage;
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.membercard.*;

/**
* 会员卡相关接口.
@@ -22,14 +17,14 @@ import me.chanjar.weixin.mp.bean.membercard.WxMpMemberCardUserInfoResult;
* @date 2018-08-30
*/
public interface WxMpMemberCardService {
String MEMBER_CARD_CREAET = "https://api.weixin.qq.com/card/create";
String MEMBER_CARD_CREATE = "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";
String MEMBER_CARD_ACTIVATE_USER_FORM = "https://api.weixin.qq.com/card/membercard/activateuserform/set";

/**
* 获取会员卡开卡插件参数.
@@ -41,19 +36,34 @@ public interface WxMpMemberCardService {
*/
String MEMBER_CARD_UPDATE = "https://api.weixin.qq.com/card/update";

/**
* 跳转型会员卡开卡字段.
* 获取用户提交资料(wx_activate=true情况调用),开发者根据activate_ticket获取到用户填写的信息
*/
String MEMBER_CARD_ACTIVATE_TEMP_INFO = "https://api.weixin.qq.com/card/membercard/activatetempinfo/get";

/**
* 得到WxMpService.
*
* @return WxMpService
*/
WxMpService getWxMpService();

/**
* 会员卡创建接口.
*
* @param createJson 会员卡json字符串
* @return 返回json字符串
* @throws WxErrorException 接口调用失败抛出的异常
*/
WxMpCardCreateResult createMemberCard(String createJson) throws WxErrorException;

/**
* 会员卡创建接口.
* 会员卡创建接口
*
* @param createMessageMessage 会员卡创建对象
* @return 会员卡信息的结果对象
* @throws WxErrorException 接口调用失败抛出的异常
*/
WxMpCardCreateResult createMemberCard(WxMpMemberCardCreateMessage createMessageMessage) throws WxErrorException;

@@ -61,7 +71,7 @@ public interface WxMpMemberCardService {
* 会员卡激活接口.
*
* @param activatedMessage 激活所需参数
* @return 返回json字符串
* @return 会员卡激活后的json字符串
* @throws WxErrorException 接口调用失败抛出的异常
*/
String activateMemberCard(WxMpMemberCardActivatedMessage activatedMessage) throws WxErrorException;
@@ -91,16 +101,40 @@ public interface WxMpMemberCardService {

/**
* 设置会员卡激活的字段(会员卡设置:wx_activate=true 时需要).
*
* @param userFormRequest 会员卡激活字段对象
* @return 会员卡激活后结果对象
* @throws WxErrorException 接口调用失败抛出的异常
*/
MemberCardActivateUserFormResult setActivateUserForm(MemberCardActivateUserFormRequest userFormRequest) throws WxErrorException;

/**
* 获取会员卡开卡插件参数(跳转型开卡组件需要参数).
*
* @param cardId 会员卡的CardId,微信分配
* @param outStr 会员卡设置商户的渠道
* @return 会员卡开卡插件参数结果对象
* @throws WxErrorException 接口调用失败抛出的异常
*/
ActivatePluginParam getActivatePluginParam(String cardId, String outStr) throws WxErrorException;

/**
* 更新会员卡信息.
*
* @param memberCardUpdateRequest 会员卡更新对象
* @return 会员卡更新后结果对象
* @throws WxErrorException 接口调用失败抛出的异常
*/
CardUpdateResult updateCardInfo(MemberCardUpdateRequest memberCardUpdateRequest) throws WxErrorException;

/**
* 解析跳转型开卡字段用户提交的资料.
* 开发者在URL上截取ticket后须先进行urldecode
*
* @param activateTicket 用户提交的资料
* @return 开卡字段的会员信息对象
* @throws WxErrorException 接口调用失败抛出的异常
*/
WxMpMemberCardActivateTempInfoResult getActivateTempInfo(String activateTicket) throws WxErrorException;

}

+ 53
- 1
weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpService.java Переглянути файл

@@ -5,6 +5,7 @@ import me.chanjar.weixin.common.error.WxErrorException;
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.mp.api.impl.BaseWxMpServiceImpl;
import me.chanjar.weixin.mp.bean.WxMpSemanticQuery;
import me.chanjar.weixin.mp.bean.result.WxMpCurrentAutoReplyInfo;
import me.chanjar.weixin.mp.bean.result.WxMpOAuth2AccessToken;
@@ -12,6 +13,8 @@ import me.chanjar.weixin.mp.bean.result.WxMpSemanticQueryResult;
import me.chanjar.weixin.mp.bean.result.WxMpUser;
import me.chanjar.weixin.mp.enums.TicketType;

import java.util.Map;

/**
* 微信公众号API的Service.
*
@@ -302,10 +305,50 @@ public interface WxMpService {
WxMpConfigStorage getWxMpConfigStorage();

/**
* 注入 {@link WxMpConfigStorage} 的实现.
* 设置 {@link WxMpConfigStorage} 的实现. 兼容老版本
*/
void setWxMpConfigStorage(WxMpConfigStorage wxConfigProvider);

/**
* {@link Map<String, WxMpConfigStorage>} 加入新的 {@link WxMpConfigStorage},适用于动态添加新的微信公众号配置
* @param configStorage 新的微信配置
*/
void addConfigStorage(String mpId, WxMpConfigStorage configStorage);

/**
* 从{@link Map<String, WxMpConfigStorage>} 移除 {@link String mpId} 所对应的 {@link WxMpConfigStorage},适用于动态移除微信公众号配置
* @param mpId 对应公众号的标识
*/
void removeConfigStorage(String mpId);

/**
* 注入多个 {@link WxMpConfigStorage} 的实现. 并为每个 {@link WxMpConfigStorage} 赋予不同的 {@link String mpId} 值
* 随机采用一个{@link String mpId}进行Http初始化操作
* @param configStorages WxMpConfigStorage map
*/
void setMultiConfigStorages(Map<String, WxMpConfigStorage> configStorages);

/**
* 注入多个 {@link WxMpConfigStorage} 的实现. 并为每个 {@link WxMpConfigStorage} 赋予不同的 {@link String label} 值
* @param configStorages WxMpConfigStorage map
* @param defaultMpId 设置一个{@link WxMpConfigStorage} 所对应的{@link String mpId}进行Http初始化
*/
void setMultiConfigStorages(Map<String, WxMpConfigStorage> configStorages, String defaultMpId);

/**
* 进行相应的公众号切换
* @param mpId 公众号标识
* @return 切换是否成功
*/
boolean switchover(String mpId);

/**
* 进行相应的公众号切换
* @param mpId 公众号标识
* @return 切换成功,则返回当前对象,方便链式调用,否则抛出异常
*/
WxMpService switchoverTo(String mpId);

/**
* 返回客服接口方法实现类,以方便调用其各个接口.
*
@@ -411,6 +454,13 @@ public interface WxMpService {
*/
WxMpMemberCardService getMemberCardService();

/**
* 返回营销相关接口方法的实现类对象,以方便调用其各个接口.
*
* @return WxMpMarketingService
*/
WxMpMarketingService getMarketingService();

/**
* 初始化http请求对象.
*/
@@ -473,4 +523,6 @@ public interface WxMpService {
void setMassMessageService(WxMpMassMessageService massMessageService);

void setAiOpenService(WxMpAiOpenService aiOpenService);

void setMarketingService(WxMpMarketingService marketingService);
}

+ 82
- 36
weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/BaseWxMpServiceImpl.java Переглянути файл

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

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

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
@@ -19,37 +14,22 @@ import me.chanjar.weixin.common.session.WxSessionManager;
import me.chanjar.weixin.common.util.DataUtils;
import me.chanjar.weixin.common.util.RandomUtils;
import me.chanjar.weixin.common.util.crypto.SHA1;
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.URIUtil;
import me.chanjar.weixin.mp.api.WxMpAiOpenService;
import me.chanjar.weixin.mp.api.WxMpCardService;
import me.chanjar.weixin.mp.api.WxMpConfigStorage;
import me.chanjar.weixin.mp.api.WxMpDataCubeService;
import me.chanjar.weixin.mp.api.WxMpDeviceService;
import me.chanjar.weixin.mp.api.WxMpKefuService;
import me.chanjar.weixin.mp.api.WxMpMassMessageService;
import me.chanjar.weixin.mp.api.WxMpMaterialService;
import me.chanjar.weixin.mp.api.WxMpMemberCardService;
import me.chanjar.weixin.mp.api.WxMpMenuService;
import me.chanjar.weixin.mp.api.WxMpQrcodeService;
import me.chanjar.weixin.mp.api.WxMpService;
import me.chanjar.weixin.mp.api.WxMpShakeService;
import me.chanjar.weixin.mp.api.WxMpStoreService;
import me.chanjar.weixin.mp.api.WxMpSubscribeMsgService;
import me.chanjar.weixin.mp.api.WxMpTemplateMsgService;
import me.chanjar.weixin.mp.api.WxMpUserBlacklistService;
import me.chanjar.weixin.mp.api.WxMpUserService;
import me.chanjar.weixin.mp.api.WxMpUserTagService;
import me.chanjar.weixin.mp.api.WxMpWifiService;
import me.chanjar.weixin.common.util.http.*;
import me.chanjar.weixin.mp.api.*;
import me.chanjar.weixin.mp.bean.WxMpSemanticQuery;
import me.chanjar.weixin.mp.bean.result.WxMpCurrentAutoReplyInfo;
import me.chanjar.weixin.mp.bean.result.WxMpOAuth2AccessToken;
import me.chanjar.weixin.mp.bean.result.WxMpSemanticQueryResult;
import me.chanjar.weixin.mp.bean.result.WxMpUser;
import me.chanjar.weixin.mp.enums.TicketType;
import me.chanjar.weixin.mp.util.WxMpConfigStorageHolder;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

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

/**
* 基础实现类.
@@ -62,7 +42,6 @@ public abstract class BaseWxMpServiceImpl<H, P> implements WxMpService, RequestH
protected final Logger log = LoggerFactory.getLogger(this.getClass());

protected WxSessionManager sessionManager = new StandardSessionManager();
protected WxMpConfigStorage wxMpConfigStorage;
private WxMpKefuService kefuService = new WxMpKefuServiceImpl(this);
private WxMpMaterialService materialService = new WxMpMaterialServiceImpl(this);
private WxMpMenuService menuService = new WxMpMenuServiceImpl(this);
@@ -81,11 +60,13 @@ public abstract class BaseWxMpServiceImpl<H, P> implements WxMpService, RequestH
private WxMpMassMessageService massMessageService = new WxMpMassMessageServiceImpl(this);
private WxMpAiOpenService aiOpenService = new WxMpAiOpenServiceImpl(this);
private WxMpWifiService wifiService = new WxMpWifiServiceImpl(this);
private WxMpMarketingService marketingService = new WxMpMarketingServiceImpl(this);

private Map<String, WxMpConfigStorage> configStorageMap;

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


@Override
public boolean checkSignature(String timestamp, String nonce, String signature) {
try {
@@ -352,15 +333,70 @@ public abstract class BaseWxMpServiceImpl<H, P> implements WxMpService, RequestH

@Override
public WxMpConfigStorage getWxMpConfigStorage() {
return this.wxMpConfigStorage;
if (this.configStorageMap.size() == 1) {
// 只有一个公众号,直接返回其配置即可
return this.configStorageMap.values().iterator().next();
}

return this.configStorageMap.get(WxMpConfigStorageHolder.get());
}

@Override
public void setWxMpConfigStorage(WxMpConfigStorage wxConfigProvider) {
this.wxMpConfigStorage = wxConfigProvider;
final String defaultMpId = WxMpConfigStorageHolder.get();
this.setMultiConfigStorages(ImmutableMap.of(defaultMpId, wxConfigProvider), defaultMpId);
}

@Override
public void setMultiConfigStorages(Map<String, WxMpConfigStorage> configStorages) {
this.setMultiConfigStorages(configStorages, configStorages.keySet().iterator().next());
}

@Override
public void setMultiConfigStorages(Map<String, WxMpConfigStorage> configStorages, String defaultMpId) {
this.configStorageMap = Maps.newHashMap(configStorages);
WxMpConfigStorageHolder.set(defaultMpId);
this.initHttp();
}

@Override
public void addConfigStorage(String mpId, WxMpConfigStorage configStorages) {
synchronized (this) {
if (this.configStorageMap.containsKey(mpId)) {
throw new RuntimeException("该公众号标识已存在,请更换其他标识!");
}
this.configStorageMap.put(mpId, configStorages);
}
}

@Override
public void removeConfigStorage(String mpId) {
synchronized (this) {
this.configStorageMap.remove(mpId);
}
}

@Override
public WxMpService switchoverTo(String mpId) {
if (this.configStorageMap.containsKey(mpId)) {
WxMpConfigStorageHolder.set(mpId);
return this;
}

throw new RuntimeException(String.format("无法找到对应【%s】的公众号配置信息,请核实!", mpId));
}

@Override
public boolean switchover(String mpId) {
if (this.configStorageMap.containsKey(mpId)) {
WxMpConfigStorageHolder.set(mpId);
return true;
}

log.error("无法找到对应【{}】的公众号配置信息,请核实!", mpId);
return false;
}

@Override
public void setRetrySleepMillis(int retrySleepMillis) {
this.retrySleepMillis = retrySleepMillis;
@@ -545,4 +581,14 @@ public abstract class BaseWxMpServiceImpl<H, P> implements WxMpService, RequestH
public WxMpWifiService getWifiService() {
return this.wifiService;
}

@Override
public WxMpMarketingService getMarketingService() {
return this.marketingService;
}

@Override
public void setMarketingService(WxMpMarketingService marketingService) {
this.marketingService = marketingService;
}
}

+ 14
- 17
weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpAiOpenServiceImpl.java Переглянути файл

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

import com.google.gson.JsonObject;
import java.io.File;

import com.google.gson.JsonParser;
import me.chanjar.weixin.common.WxType;
import me.chanjar.weixin.common.error.WxError;
import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.mp.enums.AiLangType;
import me.chanjar.weixin.mp.api.WxMpAiOpenService;
import me.chanjar.weixin.mp.api.WxMpService;
import me.chanjar.weixin.mp.enums.AiLangType;
import me.chanjar.weixin.mp.util.requestexecuter.voice.VoiceUploadRequestExecutor;

import java.io.File;

/**
* <pre>
* Created by BinaryWang on 2018/6/9.
@@ -20,9 +19,7 @@ import java.io.File;
* @author <a href="https://github.com/binarywang">Binary Wang</a>
*/
public class WxMpAiOpenServiceImpl implements WxMpAiOpenService {

private static final JsonParser JSON_PARSER = new JsonParser();
public static final String TRANSLATE_URL = "http://api.weixin.qq.com/cgi-bin/media/voice/translatecontent?lfrom=%s&lto=%s";
private WxMpService wxMpService;

public WxMpAiOpenServiceImpl(WxMpService wxMpService) {
@@ -48,14 +45,14 @@ public class WxMpAiOpenServiceImpl implements WxMpAiOpenService {

@Override
public String translate(AiLangType langFrom, AiLangType langTo, String content) throws WxErrorException {
final String responseContent = this.wxMpService.post(String.format(TRANSLATE_URL, langFrom.getCode(), langTo.getCode()),
content);
final JsonObject jsonObject = new JsonParser().parse(responseContent).getAsJsonObject();
if (jsonObject.get("errcode") == null || jsonObject.get("errcode").getAsInt() == 0) {
return jsonObject.get("to_content").getAsString();
String response = this.wxMpService.post(String.format(TRANSLATE_URL, langFrom.getCode(), langTo.getCode()), content);
WxError error = WxError.fromJson(response, WxType.MP);
if (error.getErrorCode() != 0) {
throw new WxErrorException(error);
}

throw new WxErrorException(WxError.fromJson(responseContent, WxType.MP));
return JSON_PARSER.parse(response).getAsJsonObject().get("to_content").getAsString();
}

@Override
@@ -64,13 +61,13 @@ public class WxMpAiOpenServiceImpl implements WxMpAiOpenService {
lang = AiLangType.zh_CN;
}

final String responseContent = this.wxMpService.get(VOICE_QUERY_RESULT_URL,
final String response = this.wxMpService.get(VOICE_QUERY_RESULT_URL,
String.format("voice_id=%s&lang=%s", voiceId, lang.getCode()));
final JsonObject jsonObject = JSON_PARSER.parse(responseContent).getAsJsonObject();
if (jsonObject.get("errcode") == null || jsonObject.get("errcode").getAsInt() == 0) {
return jsonObject.get("result").getAsString();
WxError error = WxError.fromJson(response, WxType.MP);
if (error.getErrorCode() != 0) {
throw new WxErrorException(error);
}

throw new WxErrorException(WxError.fromJson(responseContent, WxType.MP));
return JSON_PARSER.parse(response).getAsJsonObject().get("result").getAsString();
}
}

+ 26
- 25
weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpCardServiceImpl.java Переглянути файл

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

import java.util.Arrays;
import java.util.concurrent.locks.Lock;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.gson.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.*;
import com.google.gson.reflect.TypeToken;
import me.chanjar.weixin.common.bean.WxCardApiSignature;
import me.chanjar.weixin.common.error.WxError;
@@ -22,12 +10,15 @@ 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.bean.card.*;
import me.chanjar.weixin.mp.enums.TicketType;
import me.chanjar.weixin.mp.util.json.WxMpGsonBuilder;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Arrays;
import java.util.concurrent.locks.Lock;

/**
* Created by Binary Wang on 2016/7/27.
@@ -150,14 +141,6 @@ public class WxMpCardServiceImpl implements WxMpCardService {
return jsonPrimitive.getAsString();
}

/**
* 卡券Code查询.
*
* @param cardId 卡券ID代表一类卡券
* @param code 单张卡券的唯一标准
* @param checkConsume 是否校验code核销状态,填入true和false时的code异常状态返回数据不同
* @return WxMpCardResult对象
*/
@Override
public WxMpCardResult queryCardCode(String cardId, String code, boolean checkConsume) throws WxErrorException {
JsonObject param = new JsonObject();
@@ -265,6 +248,13 @@ public class WxMpCardServiceImpl implements WxMpCardService {
return respone;
}

@Override
public WxMpCardCreateResult createCard(WxMpCardCreateMessage cardCreateMessage) throws WxErrorException {

String response = this.wxMpService.post(CARD_CREATE, GSON.toJson(cardCreateMessage));
return WxMpCardCreateResult.fromJson(response);
}

/**
* 创建卡券二维码.
*/
@@ -324,4 +314,15 @@ public class WxMpCardServiceImpl implements WxMpCardService {
jsonRequest.addProperty("reason", reason);
return this.wxMpService.post(CARD_CODE_UNAVAILABLE, GSON.toJson(jsonRequest));
}

@Override
public WxMpCardDeleteResult deleteCard(String cardId) throws WxErrorException {
if (StringUtils.isEmpty(cardId)) {
throw new WxErrorException(WxError.builder().errorCode(41012).errorMsg("cardId不能为空").build());
}
JsonObject param = new JsonObject();
param.addProperty("card_id", cardId);
String response = this.wxMpService.post(CARD_DELETE, param.toString());
return WxMpCardDeleteResult.fromJson(response);
}
}

+ 92
- 0
weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpMarketingServiceImpl.java Переглянути файл

@@ -0,0 +1,92 @@
package me.chanjar.weixin.mp.api.impl;

import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.mp.api.WxMpMarketingService;
import me.chanjar.weixin.mp.api.WxMpService;
import me.chanjar.weixin.mp.bean.marketing.WxMpAdLeadFilter;
import me.chanjar.weixin.mp.bean.marketing.WxMpAdLeadResult;
import me.chanjar.weixin.mp.bean.marketing.WxMpUserAction;
import me.chanjar.weixin.mp.bean.marketing.WxMpUserActionSet;
import org.apache.commons.lang3.time.DateFormatUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.Date;
import java.util.List;

/**
* @author <a href="https://github.com/007gzs">007</a>
*/
public class WxMpMarketingServiceImpl implements WxMpMarketingService {
protected final Logger log = LoggerFactory.getLogger(this.getClass());
private WxMpService wxMpService;

public WxMpMarketingServiceImpl(WxMpService wxMpService) {
this.wxMpService = wxMpService;
}

@Override
public long addUserActionSets(String type, String name, String description) throws WxErrorException {
String url = API_URL_PREFIX + "user_action_sets/add?version=v1.0";
JsonObject json = new JsonObject();
json.addProperty("type", type);
json.addProperty("name", name);
json.addProperty("description", description);
String responseContent = wxMpService.post(url, json.toString());
JsonElement tmpJsonElement = new JsonParser().parse(responseContent);
return tmpJsonElement.getAsJsonObject().get("data").getAsJsonObject().get("user_action_set_id").getAsLong();
}

@Override
public List<WxMpUserActionSet> getUserActionSets(Long userActionSetId) throws WxErrorException {
String url = API_URL_PREFIX + "user_action_sets/get";
String responseContent = wxMpService.get(url, "version=v1.0&user_action_set_id=" + userActionSetId);
return WxMpUserActionSet.fromJson(responseContent);
}

@Override
public void addUserAction(List<WxMpUserAction> actions) throws WxErrorException {
String url = API_URL_PREFIX + "user_actions/add?version=v1.0";
JsonArray json = new JsonArray();
for (WxMpUserAction action : actions) {
json.add(action.toJsonObject());
}
wxMpService.post(url, json.toString());
}

@Override
public WxMpAdLeadResult getAdLeads(Date beginDate, Date endDate, List<WxMpAdLeadFilter> filtering, Integer page, Integer page_size) throws WxErrorException, IOException {
Date today = new Date();
if (beginDate == null) {
beginDate = today;
}
if (endDate == null) {
endDate = today;
}
String url = API_URL_PREFIX + "wechat_ad_leads/get";
String params = "version=v1.0";
JsonObject dateRange = new JsonObject();
dateRange.addProperty("begin_date", DateFormatUtils.format(beginDate, "yyyy-MM-dd"));
dateRange.addProperty("end_date", DateFormatUtils.format(endDate, "yyyy-MM-dd"));
params += "&date_range=" + URLEncoder.encode(dateRange.toString(), StandardCharsets.UTF_8.name());
params += "&page=" + page;
params += "&page_size=" + page_size;
if (filtering != null) {
JsonArray filterJson = new JsonArray();
for (WxMpAdLeadFilter filter : filtering) {
filterJson.add(filter.toJsonObject());
}
params += "&filtering=" + URLEncoder.encode(filterJson.toString(), StandardCharsets.UTF_8.name());
;
}
String responseContent = wxMpService.get(url, params);
return WxMpAdLeadResult.fromJson(responseContent);
}
}

+ 13
- 10
weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpMemberCardServiceImpl.java Переглянути файл

@@ -5,6 +5,7 @@ import java.net.URLDecoder;
import java.util.HashMap;
import java.util.Map;

import me.chanjar.weixin.mp.bean.membercard.*;
import org.apache.commons.lang3.StringUtils;

import com.google.gson.Gson;
@@ -30,13 +31,6 @@ import me.chanjar.weixin.mp.bean.card.WxMpCardCreateResult;
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.ActivatePluginParam;
import me.chanjar.weixin.mp.bean.membercard.ActivatePluginParamResult;
import me.chanjar.weixin.mp.bean.membercard.WxMpMemberCardActivatedMessage;
import me.chanjar.weixin.mp.bean.membercard.WxMpMemberCardCreateMessage;
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.util.json.WxMpGsonBuilder;

/**
@@ -51,7 +45,7 @@ public class WxMpMemberCardServiceImpl implements WxMpMemberCardService {

private static final Gson GSON = WxMpGsonBuilder.create();

WxMpMemberCardServiceImpl(WxMpService wxMpService) {
public WxMpMemberCardServiceImpl(WxMpService wxMpService) {
this.wxMpService = wxMpService;
}

@@ -76,7 +70,7 @@ public class WxMpMemberCardServiceImpl implements WxMpMemberCardService {
return validResult;
}

String response = this.wxMpService.post(MEMBER_CARD_CREAET, GSON.toJson(createMessageMessage));
String response = this.wxMpService.post(MEMBER_CARD_CREATE, GSON.toJson(createMessageMessage));
return WxMpCardCreateResult.fromJson(response);
}

@@ -249,7 +243,7 @@ public class WxMpMemberCardServiceImpl implements WxMpMemberCardService {

@Override
public MemberCardActivateUserFormResult setActivateUserForm(MemberCardActivateUserFormRequest userFormRequest) throws WxErrorException {
String responseContent = this.getWxMpService().post(MEMBER_CARD_ACTIVATEUSERFORM, GSON.toJson(userFormRequest));
String responseContent = this.getWxMpService().post(MEMBER_CARD_ACTIVATE_USER_FORM, GSON.toJson(userFormRequest));
return MemberCardActivateUserFormResult.fromJson(responseContent);
}

@@ -284,6 +278,15 @@ public class WxMpMemberCardServiceImpl implements WxMpMemberCardService {
return result;
}

@Override
public WxMpMemberCardActivateTempInfoResult getActivateTempInfo(String activateTicket) throws WxErrorException {
JsonObject params = new JsonObject();
params.addProperty("activate_ticket", activateTicket);
String response = this.wxMpService.post(MEMBER_CARD_ACTIVATE_TEMP_INFO, GSON.toJson(params));
WxMpMemberCardActivateTempInfoResult result = GSON.fromJson(response, WxMpMemberCardActivateTempInfoResult.class);
return result;
}

private static String truncateUrlPage(String strURL) {
String strAllParam = null;
String[] arrSplit;


Деякі файли не було показано, через те що забагато файлів було змінено

Завантаження…
Відмінити
Зберегти