diff --git a/.github/agents/my-agent.agent.md b/.github/agents/my-agent.agent.md
new file mode 100644
index 0000000000..0c8481288a
--- /dev/null
+++ b/.github/agents/my-agent.agent.md
@@ -0,0 +1,14 @@
+---
+# Fill in the fields below to create a basic custom agent for your repository.
+# The Copilot CLI can be used for local testing: https://gh.io/customagents/cli
+# To make this agent available, merge this file into the default repository branch.
+# For format details, see: https://gh.io/customagents/config
+
+name: 全部用中文
+description: 需要用中文,包括PR标题和分析总结过程
+---
+
+# My Agent
+
+1、请使用中文输出思考过程和总结,包括PR标题,提交commit信息也要使用中文;
+2、生成代码时需要提供必要的单元测试代码。
diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md
new file mode 100644
index 0000000000..cad29d96d9
--- /dev/null
+++ b/.github/copilot-instructions.md
@@ -0,0 +1,202 @@
+# Copilot Instruction
+请始终使用中文生成 Pull Request 的标题、描述和提交信息
+
+
+# WxJava - 微信 Java SDK 开发说明
+
+WxJava 是一个支持多种微信平台的完整 Java SDK,包含公众号、小程序、微信支付、企业微信、开放平台、视频号、企点等多种功能模块。
+
+**请始终优先参考本说明,只有在遇到与此内容不一致的意外信息时,才退而使用搜索或 bash 命令。**
+
+## 高效开发指南
+
+### 前置条件与环境准备
+- **Java 要求**:JDK 8+(项目最低目标为 Java 8)
+- **Maven**:推荐 Maven 3.6+(已验证 Maven 3.9.11)
+- **IDE**:推荐使用 IntelliJ IDEA(项目针对 IDEA 优化)
+
+### 引导、构建与校验
+克隆仓库后按顺序执行以下命令:
+
+```bash
+# 1. 基础编译(请勿中断 - 约需 4-5 分钟)
+mvn clean compile -DskipTests=true --no-transfer-progress
+# 超时时间:建议设置 8 分钟以上。实际时间:约 4 分钟
+
+# 2. 完整打包(请勿中断 - 约需 2-3 分钟)
+mvn clean package -DskipTests=true --no-transfer-progress
+# 超时时间:建议设置 5 分钟以上。实际时间:约 2 分钟
+
+# 3. 代码质量校验(请勿中断 - 约需 45-60 秒)
+mvn checkstyle:check --no-transfer-progress
+# 超时时间:建议设置 3 分钟以上。实际时间:约 50 秒
+```
+
+重要时间说明:
+- 绝对不要中断任意 Maven 构建命令
+- 编译阶段耗时最长(约 4 分钟),原因是项目包含 34 个模块
+- 后续构建会更快,因为存在增量编译
+- 始终使用 `--no-transfer-progress` 以减少日志噪音
+
+### 测试结构
+- **测试框架**:TestNG(非 JUnit)
+- **测试文件**:共有 298 个测试文件
+- **默认行为**:pom.xml 中默认禁用测试(`
-
-
-
- |
- ||
-
-
-
- |
-
-
-
-
- |
- |
-
-
-
- |
-
-
-
-
- |
-
-
-
-
- |
-
|
-
- |
- ||
+ * {@link me.chanjar.weixin.cp.api.WxCpService#setMaxRetryTimes(int)}
+ * {@link me.chanjar.weixin.cp.api.impl.BaseWxCpServiceImpl#setMaxRetryTimes(int)}
+ *
+ */
+ private int maxRetryTimes = 5;
+
+ /**
+ * http 请求重试间隔
+ *
+ * {@link me.chanjar.weixin.cp.api.WxCpService#setRetrySleepMillis(int)}
+ * {@link me.chanjar.weixin.cp.api.impl.BaseWxCpServiceImpl#setRetrySleepMillis(int)}
+ *
+ */
+ private int retrySleepMillis = 1000;
+ }
+
+ public enum StorageType {
+ /**
+ * 内存
+ */
+ memory,
+ /**
+ * jedis
+ */
+ jedis,
+ /**
+ * redisson
+ */
+ redisson,
+ /**
+ * redistemplate
+ */
+ redistemplate
+ }
+
+ public enum HttpClientType {
+ /**
+ * HttpClient
+ */
+ HTTP_CLIENT,
+ /**
+ * OkHttp
+ */
+ OK_HTTP,
+ /**
+ * JoddHttp
+ */
+ JODD_HTTP
+ }
+}
diff --git a/spring-boot-starters/wx-java-cp-tp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/properties/WxCpTpMultiRedisProperties.java b/spring-boot-starters/wx-java-cp-tp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/properties/WxCpTpMultiRedisProperties.java
new file mode 100644
index 0000000000..b94711216f
--- /dev/null
+++ b/spring-boot-starters/wx-java-cp-tp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/properties/WxCpTpMultiRedisProperties.java
@@ -0,0 +1,48 @@
+package com.binarywang.spring.starter.wxjava.cp.properties;
+
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+
+/**
+ * Redis配置.
+ *
+ * @author yl
+ * created on 2023/10/16
+ */
+@Data
+@NoArgsConstructor
+public class WxCpTpMultiRedisProperties implements Serializable {
+ private static final long serialVersionUID = -5924815351660074401L;
+
+ /**
+ * 主机地址.
+ */
+ private String host;
+
+ /**
+ * 端口号.
+ */
+ 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;
+}
diff --git a/spring-boot-starters/wx-java-cp-tp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/properties/WxCpTpSingleProperties.java b/spring-boot-starters/wx-java-cp-tp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/properties/WxCpTpSingleProperties.java
new file mode 100644
index 0000000000..02a52657db
--- /dev/null
+++ b/spring-boot-starters/wx-java-cp-tp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/properties/WxCpTpSingleProperties.java
@@ -0,0 +1,43 @@
+package com.binarywang.spring.starter.wxjava.cp.properties;
+
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+
+/**
+ * 企业微信企业相关配置属性
+ *
+ * @author yl
+ * created on 2023/10/16
+ */
+@Data
+@NoArgsConstructor
+public class WxCpTpSingleProperties implements Serializable {
+ private static final long serialVersionUID = -7502823825007859418L;
+ /**
+ * 微信企业号 corpId
+ */
+ private String corpId;
+ /**
+ * 微信企业号 服务商 providerSecret
+ */
+ private String providerSecret;
+ /**
+ * 微信企业号应用 token
+ */
+ private String token;
+
+ private String encodingAESKey;
+
+ /**
+ * 微信企业号 第三方 应用 ID
+ */
+ private String suiteId;
+ /**
+ * 微信企业号应用
+ */
+ private String suiteSecret;
+
+
+}
diff --git a/spring-boot-starters/wx-java-cp-tp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/service/WxCpTpMultiServices.java b/spring-boot-starters/wx-java-cp-tp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/service/WxCpTpMultiServices.java
new file mode 100644
index 0000000000..c0a9faf51e
--- /dev/null
+++ b/spring-boot-starters/wx-java-cp-tp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/service/WxCpTpMultiServices.java
@@ -0,0 +1,29 @@
+package com.binarywang.spring.starter.wxjava.cp.service;
+
+
+import me.chanjar.weixin.cp.tp.service.WxCpTpService;
+
+/**
+ * 企业微信 {@link WxCpTpService} 所有实例存放类.
+ *
+ * @author yl
+ * created on 2023/10/16
+ */
+public interface WxCpTpMultiServices {
+ /**
+ * 通过租户 Id 获取 WxCpTpService
+ *
+ * @param tenantId 租户 Id
+ * @return WxCpTpService
+ */
+ WxCpTpService getWxCpTpService(String tenantId);
+
+ void addWxCpTpService(String tenantId, WxCpTpService wxCpService);
+
+ /**
+ * 根据租户 Id,从列表中移除一个 WxCpTpService 实例
+ *
+ * @param tenantId 租户 Id
+ */
+ void removeWxCpTpService(String tenantId);
+}
diff --git a/spring-boot-starters/wx-java-cp-tp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/service/WxCpTpMultiServicesImpl.java b/spring-boot-starters/wx-java-cp-tp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/service/WxCpTpMultiServicesImpl.java
new file mode 100644
index 0000000000..84b381230c
--- /dev/null
+++ b/spring-boot-starters/wx-java-cp-tp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/service/WxCpTpMultiServicesImpl.java
@@ -0,0 +1,44 @@
+package com.binarywang.spring.starter.wxjava.cp.service;
+
+
+import me.chanjar.weixin.cp.tp.service.WxCpTpService;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * 企业微信 {@link WxCpTpMultiServices} 默认实现
+ *
+ * @author yl
+ * created on 2023/10/16
+ */
+public class WxCpTpMultiServicesImpl implements WxCpTpMultiServices {
+ private final Map+ * 使用单个 WxMaService 实例管理多个租户配置,通过 switchover 切换租户。 + * 相比 {@link WxMaMultiServicesImpl},此实现共享 HTTP 客户端,节省资源。 + *
+ *+ * 注意:由于使用 ThreadLocal 切换配置,在异步或多线程场景需要特别注意线程上下文切换。 + *
+ * + * @author Binary Wang + * created on 2026/1/9 + */ +@RequiredArgsConstructor +public class WxMaMultiServicesSharedImpl implements WxMaMultiServices { + private final WxMaService sharedWxMaService; + + @Override + public WxMaService getWxMaService(String tenantId) { + if (tenantId == null) { + return null; + } + // 使用 switchover 检查配置是否存在,保持与隔离模式 API 行为一致(不存在时返回 null) + if (!sharedWxMaService.switchover(tenantId)) { + return null; + } + return sharedWxMaService; + } + + @Override + public void removeWxMaService(String tenantId) { + if (tenantId != null) { + sharedWxMaService.removeConfig(tenantId); + } + } + + /** + * 添加租户配置到共享的 WxMaService 实例 + * + * @param tenantId 租户 ID + * @param wxMaService 要添加配置的 WxMaService(仅使用其配置,不使用其实例) + */ + public void addWxMaService(String tenantId, WxMaService wxMaService) { + if (tenantId != null && wxMaService != null) { + sharedWxMaService.addConfig(tenantId, wxMaService.getWxMaConfig()); + } + } +} diff --git a/spring-boot-starters/wx-java-miniapp-spring-boot-starter/pom.xml b/spring-boot-starters/wx-java-miniapp-spring-boot-starter/pom.xml index c38db4802a..bcc61b0309 100644 --- a/spring-boot-starters/wx-java-miniapp-spring-boot-starter/pom.xml +++ b/spring-boot-starters/wx-java-miniapp-spring-boot-starter/pom.xml @@ -4,7 +4,7 @@+ * 使用单个 WxMpService 实例管理多个租户配置,通过 switchover 切换租户。 + * 相比 {@link WxMpMultiServicesImpl},此实现共享 HTTP 客户端,节省资源。 + *
+ *+ * 注意:由于使用 ThreadLocal 切换配置,在异步或多线程场景需要特别注意线程上下文切换。 + *
+ * + * @author Binary Wang + * created on 2026/1/9 + */ +@RequiredArgsConstructor +public class WxMpMultiServicesSharedImpl implements WxMpMultiServices { + private final WxMpService sharedWxMpService; + + @Override + public WxMpService getWxMpService(String tenantId) { + if (tenantId == null) { + return null; + } + // 使用 switchover 检查配置是否存在,保持与隔离模式 API 行为一致(不存在时返回 null) + if (!sharedWxMpService.switchover(tenantId)) { + return null; + } + return sharedWxMpService; + } + + @Override + public void removeWxMpService(String tenantId) { + if (tenantId != null) { + sharedWxMpService.removeConfigStorage(tenantId); + } + } + + /** + * 添加租户配置到共享的 WxMpService 实例 + * + * @param tenantId 租户 ID + * @param wxMpService 要添加配置的 WxMpService(仅使用其配置,不使用其实例) + */ + public void addWxMpService(String tenantId, WxMpService wxMpService) { + if (tenantId != null && wxMpService != null) { + sharedWxMpService.addConfigStorage(tenantId, wxMpService.getWxMpConfigStorage()); + } + } +} diff --git a/spring-boot-starters/wx-java-mp-spring-boot-starter/README.md b/spring-boot-starters/wx-java-mp-spring-boot-starter/README.md index 3e14f499d9..091912cfad 100644 --- a/spring-boot-starters/wx-java-mp-spring-boot-starter/README.md +++ b/spring-boot-starters/wx-java-mp-spring-boot-starter/README.md @@ -27,7 +27,7 @@ #wx.mp.config-storage.redis.sentinel-ips=127.0.0.1:16379,127.0.0.1:26379 #wx.mp.config-storage.redis.sentinel-name=mymaster # http客户端配置 - wx.mp.config-storage.http-client-type=httpclient # http客户端类型: HttpClient(默认), OkHttp, JoddHttp + wx.mp.config-storage.http-client-type=HttpComponents # http客户端类型: HttpComponents(Apache HttpClient 5.x,推荐), HttpClient(Apache HttpClient 4.x), OkHttp, JoddHttp wx.mp.config-storage.http-proxy-host= wx.mp.config-storage.http-proxy-port= wx.mp.config-storage.http-proxy-username= diff --git a/spring-boot-starters/wx-java-mp-spring-boot-starter/pom.xml b/spring-boot-starters/wx-java-mp-spring-boot-starter/pom.xml index d87b662007..38e484b450 100644 --- a/spring-boot-starters/wx-java-mp-spring-boot-starter/pom.xml +++ b/spring-boot-starters/wx-java-mp-spring-boot-starter/pom.xml @@ -5,7 +5,7 @@
+ * {@link me.chanjar.weixin.mp.api.impl.BaseWxMpServiceImpl#setMaxRetryTimes(int)}
+ * {@link cn.binarywang.wx.miniapp.api.impl.BaseWxMaServiceImpl#setMaxRetryTimes(int)}
+ *
+ */
+ private int maxRetryTimes = 5;
+
+ /**
+ * http 请求重试间隔
+ *
+ * {@link me.chanjar.weixin.mp.api.impl.BaseWxMpServiceImpl#setRetrySleepMillis(int)}
+ * {@link cn.binarywang.wx.miniapp.api.impl.BaseWxMaServiceImpl#setRetrySleepMillis(int)}
+ *
+ */
+ private int retrySleepMillis = 1000;
+
+ /**
+ * 连接超时时间,单位毫秒
+ */
+ private int connectionTimeout = 5000;
+
+ /**
+ * 读数据超时时间,即socketTimeout,单位毫秒
+ */
+ private int soTimeout = 5000;
+
+ /**
+ * 从连接池获取链接的超时时间,单位毫秒
+ */
+ private int connectionRequestTimeout = 5000;
+ }
+
+ public enum StorageType {
+ /**
+ * 内存
+ */
+ memory,
+ /**
+ * jedis
+ */
+ jedis,
+ /**
+ * redisson
+ */
+ redisson,
+ /**
+ * redisTemplate
+ */
+ redistemplate
+ }
+
+}
diff --git a/spring-boot-starters/wx-java-open-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/open/properties/WxOpenMultiRedisProperties.java b/spring-boot-starters/wx-java-open-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/open/properties/WxOpenMultiRedisProperties.java
new file mode 100644
index 0000000000..ae6d5368d7
--- /dev/null
+++ b/spring-boot-starters/wx-java-open-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/open/properties/WxOpenMultiRedisProperties.java
@@ -0,0 +1,57 @@
+package com.binarywang.spring.starter.wxjava.open.properties;
+
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+
+/**
+ * 微信开放平台多账号Redis配置.
+ *
+ * @author Binary Wang
+ */
+@Data
+@NoArgsConstructor
+public class WxOpenMultiRedisProperties implements Serializable {
+ private static final long serialVersionUID = -5924815351660074401L;
+
+ /**
+ * 主机地址.
+ */
+ private String host = "127.0.0.1";
+
+ /**
+ * 端口号.
+ */
+ private int port = 6379;
+
+ /**
+ * 密码.
+ */
+ private String password;
+
+ /**
+ * 超时.
+ */
+ private int timeout = 2000;
+
+ /**
+ * 数据库.
+ */
+ private int database = 0;
+
+ /**
+ * sentinel ips
+ */
+ private String sentinelIps;
+
+ /**
+ * sentinel name
+ */
+ private String sentinelName;
+
+ private Integer maxActive;
+ private Integer maxIdle;
+ private Integer maxWaitMillis;
+ private Integer minIdle;
+}
diff --git a/spring-boot-starters/wx-java-open-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/open/properties/WxOpenSingleProperties.java b/spring-boot-starters/wx-java-open-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/open/properties/WxOpenSingleProperties.java
new file mode 100644
index 0000000000..116da323dc
--- /dev/null
+++ b/spring-boot-starters/wx-java-open-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/open/properties/WxOpenSingleProperties.java
@@ -0,0 +1,49 @@
+package com.binarywang.spring.starter.wxjava.open.properties;
+
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+
+/**
+ * 微信开放平台单个应用配置.
+ *
+ * @author Binary Wang
+ */
+@Data
+@NoArgsConstructor
+public class WxOpenSingleProperties implements Serializable {
+ private static final long serialVersionUID = 1980986361098922525L;
+
+ /**
+ * 设置微信开放平台的appid.
+ */
+ private String appId;
+
+ /**
+ * 设置微信开放平台的app secret.
+ */
+ private String secret;
+
+ /**
+ * 设置微信开放平台的token.
+ */
+ private String token;
+
+ /**
+ * 设置微信开放平台的EncodingAESKey.
+ */
+ private String aesKey;
+
+ /**
+ * 自定义API主机地址,用于替换默认的 https://api.weixin.qq.com
+ * 例如:http://proxy.company.com:8080
+ */
+ private String apiHostUrl;
+
+ /**
+ * 自定义获取AccessToken地址,用于向自定义统一服务获取AccessToken
+ * 例如:http://proxy.company.com:8080/oauth/token
+ */
+ private String accessTokenUrl;
+}
diff --git a/spring-boot-starters/wx-java-open-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/open/service/WxOpenMultiServices.java b/spring-boot-starters/wx-java-open-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/open/service/WxOpenMultiServices.java
new file mode 100644
index 0000000000..9228071a10
--- /dev/null
+++ b/spring-boot-starters/wx-java-open-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/open/service/WxOpenMultiServices.java
@@ -0,0 +1,26 @@
+package com.binarywang.spring.starter.wxjava.open.service;
+
+
+import me.chanjar.weixin.open.api.WxOpenService;
+
+/**
+ * 微信开放平台 {@link WxOpenService} 所有实例存放类.
+ *
+ * @author binarywang
+ */
+public interface WxOpenMultiServices {
+ /**
+ * 通过租户 Id 获取 WxOpenService
+ *
+ * @param tenantId 租户 Id
+ * @return WxOpenService
+ */
+ WxOpenService getWxOpenService(String tenantId);
+
+ /**
+ * 根据租户 Id,从列表中移除一个 WxOpenService 实例
+ *
+ * @param tenantId 租户 Id
+ */
+ void removeWxOpenService(String tenantId);
+}
diff --git a/spring-boot-starters/wx-java-open-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/open/service/WxOpenMultiServicesImpl.java b/spring-boot-starters/wx-java-open-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/open/service/WxOpenMultiServicesImpl.java
new file mode 100644
index 0000000000..76fb139e6c
--- /dev/null
+++ b/spring-boot-starters/wx-java-open-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/open/service/WxOpenMultiServicesImpl.java
@@ -0,0 +1,35 @@
+package com.binarywang.spring.starter.wxjava.open.service;
+
+import me.chanjar.weixin.open.api.WxOpenService;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * 微信开放平台 {@link WxOpenMultiServices} 默认实现
+ *
+ * @author Binary Wang
+ */
+public class WxOpenMultiServicesImpl implements WxOpenMultiServices {
+ private final Map+ * 注意:configKey 是配置文件中定义的 key(如 wx.pay.configs.<configKey>.xxx), + * 而不是 appId。如果使用 appId 作为配置 key,则可以直接传入 appId。 + *
+ * + * @param configKey 配置标识(配置文件中 wx.pay.configs 下的 key) + * @return WxPayService + */ + WxPayService getWxPayService(String configKey); + + /** + * 根据配置标识,从列表中移除一个 WxPayService 实例. + *+ * 注意:configKey 是配置文件中定义的 key(如 wx.pay.configs.<configKey>.xxx), + * 而不是 appId。如果使用 appId 作为配置 key,则可以直接传入 appId。 + *
+ * + * @param configKey 配置标识(配置文件中 wx.pay.configs 下的 key) + */ + void removeWxPayService(String configKey); +} diff --git a/spring-boot-starters/wx-java-pay-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/pay/service/WxPayMultiServicesImpl.java b/spring-boot-starters/wx-java-pay-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/pay/service/WxPayMultiServicesImpl.java new file mode 100644 index 0000000000..459fe3b6c0 --- /dev/null +++ b/spring-boot-starters/wx-java-pay-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/pay/service/WxPayMultiServicesImpl.java @@ -0,0 +1,92 @@ +package com.binarywang.spring.starter.wxjava.pay.service; + +import com.binarywang.spring.starter.wxjava.pay.properties.WxPayMultiProperties; +import com.binarywang.spring.starter.wxjava.pay.properties.WxPaySingleProperties; +import com.github.binarywang.wxpay.config.WxPayConfig; +import com.github.binarywang.wxpay.service.WxPayService; +import com.github.binarywang.wxpay.service.impl.WxPayServiceImpl; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * 微信支付多服务管理实现类. + * + * @author Binary Wang + */ +@Slf4j +public class WxPayMultiServicesImpl implements WxPayMultiServices { + private final Map+ * 本示例展示了如何使用 wx-java-pay-multi-spring-boot-starter 来管理多个公众号的支付配置。 + *
+ * + * @author Binary Wang + */ +@Slf4j +@Service +public class WxPayMultiExample { + + @Autowired + private WxPayMultiServices wxPayMultiServices; + + /** + * 示例1:根据appId创建支付订单. + *+ * 适用场景:系统需要支持多个公众号,根据用户所在的公众号动态选择支付配置 + *
+ * + * @param appId 公众号appId + * @param openId 用户的openId + * @param totalFee 支付金额(分) + * @param body 商品描述 + * @return JSAPI支付参数 + */ + public WxPayUnifiedOrderV3Result.JsapiResult createJsapiOrder(String appId, String openId, + Integer totalFee, String body) { + try { + // 根据appId获取对应的WxPayService + WxPayService wxPayService = wxPayMultiServices.getWxPayService(appId); + + if (wxPayService == null) { + log.error("未找到appId对应的微信支付配置: {}", appId); + throw new IllegalArgumentException("未找到appId对应的微信支付配置"); + } + + // 构建支付请求 + WxPayUnifiedOrderV3Request request = new WxPayUnifiedOrderV3Request(); + request.setOutTradeNo(generateOutTradeNo()); + request.setDescription(body); + request.setAmount(new WxPayUnifiedOrderV3Request.Amount().setTotal(totalFee)); + request.setPayer(new WxPayUnifiedOrderV3Request.Payer().setOpenid(openId)); + request.setNotifyUrl(wxPayService.getConfig().getNotifyUrl()); + + // 调用微信支付API创建订单 + WxPayUnifiedOrderV3Result.JsapiResult result = + wxPayService.createOrderV3(TradeTypeEnum.JSAPI, request); + + log.info("创建JSAPI支付订单成功,appId: {}, outTradeNo: {}", appId, request.getOutTradeNo()); + return result; + + } catch (Exception e) { + log.error("创建JSAPI支付订单失败,appId: {}", appId, e); + throw new RuntimeException("创建支付订单失败", e); + } + } + + /** + * 示例2:服务商模式 - 为不同子商户创建订单. + *+ * 适用场景:服务商为多个子商户提供支付服务 + *
+ * + * @param configKey 配置标识(在配置文件中定义) + * @param subOpenId 子商户用户的openId + * @param totalFee 支付金额(分) + * @param body 商品描述 + * @return JSAPI支付参数 + */ + public WxPayUnifiedOrderV3Result.JsapiResult createPartnerOrder(String configKey, String subOpenId, + Integer totalFee, String body) { + try { + // 根据配置标识获取WxPayService + WxPayService wxPayService = wxPayMultiServices.getWxPayService(configKey); + + if (wxPayService == null) { + log.error("未找到配置: {}", configKey); + throw new IllegalArgumentException("未找到配置"); + } + + // 获取子商户信息 + String subAppId = wxPayService.getConfig().getSubAppId(); + String subMchId = wxPayService.getConfig().getSubMchId(); + log.info("使用服务商模式,子商户appId: {}, 子商户号: {}", subAppId, subMchId); + + // 构建支付请求 + WxPayUnifiedOrderV3Request request = new WxPayUnifiedOrderV3Request(); + request.setOutTradeNo(generateOutTradeNo()); + request.setDescription(body); + request.setAmount(new WxPayUnifiedOrderV3Request.Amount().setTotal(totalFee)); + request.setPayer(new WxPayUnifiedOrderV3Request.Payer().setOpenid(subOpenId)); + request.setNotifyUrl(wxPayService.getConfig().getNotifyUrl()); + + // 调用微信支付API创建订单 + WxPayUnifiedOrderV3Result.JsapiResult result = + wxPayService.createOrderV3(TradeTypeEnum.JSAPI, request); + + log.info("创建服务商支付订单成功,配置: {}, outTradeNo: {}", configKey, request.getOutTradeNo()); + return result; + + } catch (Exception e) { + log.error("创建服务商支付订单失败,配置: {}", configKey, e); + throw new RuntimeException("创建支付订单失败", e); + } + } + + /** + * 示例3:查询订单状态. + *+ * 适用场景:查询不同公众号的订单支付状态 + *
+ * + * @param appId 公众号appId + * @param outTradeNo 商户订单号 + * @return 订单状态 + */ + public String queryOrderStatus(String appId, String outTradeNo) { + try { + WxPayService wxPayService = wxPayMultiServices.getWxPayService(appId); + + if (wxPayService == null) { + log.error("未找到appId对应的微信支付配置: {}", appId); + throw new IllegalArgumentException("未找到appId对应的微信支付配置"); + } + + // 查询订单 + WxPayOrderQueryV3Result result = wxPayService.queryOrderV3(null, outTradeNo); + String tradeState = result.getTradeState(); + + log.info("查询订单状态成功,appId: {}, outTradeNo: {}, 状态: {}", appId, outTradeNo, tradeState); + return tradeState; + + } catch (Exception e) { + log.error("查询订单状态失败,appId: {}, outTradeNo: {}", appId, outTradeNo, e); + throw new RuntimeException("查询订单失败", e); + } + } + + /** + * 示例4:申请退款. + *+ * 适用场景:为不同公众号的订单申请退款 + *
+ * + * @param appId 公众号appId + * @param outTradeNo 商户订单号 + * @param refundFee 退款金额(分) + * @param totalFee 订单总金额(分) + * @param reason 退款原因 + * @return 退款单号 + */ + public String refund(String appId, String outTradeNo, Integer refundFee, + Integer totalFee, String reason) { + try { + WxPayService wxPayService = wxPayMultiServices.getWxPayService(appId); + + if (wxPayService == null) { + log.error("未找到appId对应的微信支付配置: {}", appId); + throw new IllegalArgumentException("未找到appId对应的微信支付配置"); + } + + // 构建退款请求 + com.github.binarywang.wxpay.bean.request.WxPayRefundV3Request request = + new com.github.binarywang.wxpay.bean.request.WxPayRefundV3Request(); + request.setOutTradeNo(outTradeNo); + request.setOutRefundNo(generateRefundNo()); + request.setReason(reason); + request.setNotifyUrl(wxPayService.getConfig().getRefundNotifyUrl()); + + com.github.binarywang.wxpay.bean.request.WxPayRefundV3Request.Amount amount = + new com.github.binarywang.wxpay.bean.request.WxPayRefundV3Request.Amount(); + amount.setRefund(refundFee); + amount.setTotal(totalFee); + amount.setCurrency("CNY"); + request.setAmount(amount); + + // 调用微信支付API申请退款 + WxPayRefundV3Result result = wxPayService.refundV3(request); + + log.info("申请退款成功,appId: {}, outTradeNo: {}, outRefundNo: {}", + appId, outTradeNo, request.getOutRefundNo()); + return request.getOutRefundNo(); + + } catch (Exception e) { + log.error("申请退款失败,appId: {}, outTradeNo: {}", appId, outTradeNo, e); + throw new RuntimeException("申请退款失败", e); + } + } + + /** + * 示例5:动态管理配置. + *+ * 适用场景:需要在运行时更新配置(如证书更新后需要重新加载) + *
+ * + * @param configKey 配置标识 + */ + public void reloadConfig(String configKey) { + try { + // 移除缓存的WxPayService实例 + wxPayMultiServices.removeWxPayService(configKey); + log.info("移除配置成功,下次获取时将重新创建: {}", configKey); + + // 下次调用 getWxPayService 时会重新创建实例 + WxPayService wxPayService = wxPayMultiServices.getWxPayService(configKey); + if (wxPayService != null) { + log.info("重新加载配置成功: {}", configKey); + } + + } catch (Exception e) { + log.error("重新加载配置失败: {}", configKey, e); + throw new RuntimeException("重新加载配置失败", e); + } + } + + /** + * 生成商户订单号. + * + * @return 商户订单号 + */ + private String generateOutTradeNo() { + return "ORDER_" + System.currentTimeMillis(); + } + + /** + * 生成商户退款单号. + * + * @return 商户退款单号 + */ + private String generateRefundNo() { + return "REFUND_" + System.currentTimeMillis(); + } +} diff --git a/spring-boot-starters/wx-java-pay-spring-boot-starter/pom.xml b/spring-boot-starters/wx-java-pay-spring-boot-starter/pom.xml index 91a92769c8..8b67ade1ea 100644 --- a/spring-boot-starters/wx-java-pay-spring-boot-starter/pom.xml +++ b/spring-boot-starters/wx-java-pay-spring-boot-starter/pom.xml @@ -5,7 +5,7 @@+ * 参考:{@code https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140842} + *
* * @author Daniel Qian */ @@ -36,8 +39,10 @@ public class WxOAuth2AccessToken implements Serializable { private Integer snapshotUser; /** - * https://mp.weixin.qq.com/cgi-bin/announce?action=getannouncement&announce_id=11513156443eZYea&version=&lang=zh_CN. * 本接口在scope参数为snsapi_base时不再提供unionID字段。 + *+ * 参考:{@code https://mp.weixin.qq.com/cgi-bin/announce?action=getannouncement&announce_id=11513156443eZYea&version=&lang=zh_CN} + *
*/ @SerializedName("unionid") private String unionId; diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/result/WxMinishopImageUploadCustomizeResult.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/result/WxMinishopImageUploadCustomizeResult.java index cd700be7c1..5427d5cada 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/result/WxMinishopImageUploadCustomizeResult.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/result/WxMinishopImageUploadCustomizeResult.java @@ -16,7 +16,7 @@ public class WxMinishopImageUploadCustomizeResult implements Serializable { private WxMinishopPicFileCustomizeResult imgInfo; public static WxMinishopImageUploadCustomizeResult fromJson(String json) { - JsonObject jsonObject = new JsonParser().parse(json).getAsJsonObject(); + JsonObject jsonObject = JsonParser.parseString(json).getAsJsonObject(); WxMinishopImageUploadCustomizeResult result = new WxMinishopImageUploadCustomizeResult(); result.setErrcode(jsonObject.get(WxConsts.ERR_CODE).getAsNumber().toString()); if (result.getErrcode().equals("0")) { diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/result/WxMinishopImageUploadResult.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/result/WxMinishopImageUploadResult.java index 324232d0ee..9c2cbaf3ba 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/result/WxMinishopImageUploadResult.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/result/WxMinishopImageUploadResult.java @@ -21,7 +21,7 @@ public class WxMinishopImageUploadResult implements Serializable { public static WxMinishopImageUploadResult fromJson(String json) { - JsonObject jsonObject = new JsonParser().parse(json).getAsJsonObject(); + JsonObject jsonObject = JsonParser.parseString(json).getAsJsonObject(); WxMinishopImageUploadResult result = new WxMinishopImageUploadResult(); result.setErrcode(jsonObject.get(WxConsts.ERR_CODE).getAsNumber().toString()); if (result.getErrcode().equals("0")) { diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/error/WxCpErrorMsgEnum.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/error/WxCpErrorMsgEnum.java index ea1e9e7c68..356d1dbbf9 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/error/WxCpErrorMsgEnum.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/error/WxCpErrorMsgEnum.java @@ -453,7 +453,7 @@ public enum WxCpErrorMsgEnum { */ CODE_60008(60008, "部门已存在;部门ID或者部门名称已存在"), /** - * 部门名称含有非法字符;不能含有 \\:?*“< >| 等字符. + * {@code 部门名称含有非法字符;不能含有 \\:?*"< >| 等字符.} */ CODE_60009(60009, "部门名称含有非法字符;不能含有 \\ :?*“< >| 等字符"), /** @@ -521,7 +521,7 @@ public enum WxCpErrorMsgEnum { */ CODE_60124(60124, "无效的父部门id;父部门不存在通讯录中"), /** - * 非法部门名字;不能为空,且不能超过64字节,且不能含有\\:*?”< >|等字符. + * {@code 非法部门名字;不能为空,且不能超过64字节,且不能含有\\:*?"< >|等字符.} */ CODE_60125(60125, "非法部门名字;不能为空,且不能超过64字节,且不能含有\\:*?”< >|等字符"), /** diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/error/WxError.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/error/WxError.java index b45fba3411..1aab7f1f20 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/error/WxError.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/error/WxError.java @@ -12,11 +12,13 @@ /** * 微信错误码. + ** 请阅读: * 公众平台:全局返回码说明 * 企业微信:全局错误码 + *
* - * @author Daniel Qian & Binary Wang + * @author Daniel Qian, Binary Wang */ @Data @NoArgsConstructor diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/error/WxMaErrorMsgEnum.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/error/WxMaErrorMsgEnum.java index 1bb3f6472b..ffe9b5e3ea 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/error/WxMaErrorMsgEnum.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/error/WxMaErrorMsgEnum.java @@ -46,23 +46,23 @@ public enum WxMaErrorMsgEnum { */ CODE_40003(40003, "openid 不正确"), /** - *
* 无效媒体文件类型
- * 对应操作:uploadTempMedia
+ *
+ * 对应操作:{@code uploadTempMedia}
* 对应地址:
- * POST https://api.weixin.qq.com/cgi-bin/media/upload?access_token=ACCESS_TOKEN&type=TYPE
+ * {@code POST https://api.weixin.qq.com/cgi-bin/media/upload?access_token=ACCESS_TOKEN&type=TYPE}
* 参考文档地址: https://developers.weixin.qq.com/miniprogram/dev/api/open-api/customer-message/uploadTempMedia.html
- *
+ *
*/
CODE_40004(40004, "无效媒体文件类型"),
/**
- *
* 无效媒体文件 ID.
- * 对应操作:getTempMedia
+ *
+ * 对应操作:{@code getTempMedia}
* 对应地址:
- * GET https://api.weixin.qq.com/cgi-bin/media/get?access_token=ACCESS_TOKEN&media_id=MEDIA_ID
+ * {@code GET https://api.weixin.qq.com/cgi-bin/media/get?access_token=ACCESS_TOKEN&media_id=MEDIA_ID}
* 参考文档地址: https://developers.weixin.qq.com/miniprogram/dev/api/open-api/customer-message/getTempMedia.html
- *
+ *
*/
CODE_40007(40007, "无效媒体文件 ID"),
/**
@@ -99,29 +99,29 @@ public enum WxMaErrorMsgEnum {
*/
CODE_41028(41028, "form_id 不正确,或者过期"),
/**
- *
* code 或 template_id 不正确.
- * 对应操作:code2Session, sendUniformMessage, sendTemplateMessage
+ *
+ * 对应操作:{@code code2Session}, {@code sendUniformMessage}, {@code sendTemplateMessage}
* 对应地址:
- * GET https://api.weixin.qq.com/sns/jscode2session?appid=APPID&secret=SECRET&js_code=JSCODE&grant_type=authorization_code
+ * {@code GET https://api.weixin.qq.com/sns/jscode2session?appid=APPID&secret=SECRET&js_code=JSCODE&grant_type=authorization_code}
* POST https://api.weixin.qq.com/cgi-bin/message/wxopen/template/uniform_send?access_token=ACCESS_TOKEN
* POST https://api.weixin.qq.com/cgi-bin/message/wxopen/template/send?access_token=ACCESS_TOKEN
* 参考文档地址: https://developers.weixin.qq.com/miniprogram/dev/api/open-api/login/code2Session.html
* https://developers.weixin.qq.com/miniprogram/dev/api/open-api/uniform-message/sendUniformMessage.html
* https://developers.weixin.qq.com/miniprogram/dev/api/open-api/template-message/sendTemplateMessage.html
- *
+ *
*/
CODE_41029(41029, "请求的参数不正确"),
/**
- *
* form_id 已被使用,或者所传page页面不存在,或者小程序没有发布
- * 对应操作:sendUniformMessage, getWXACodeUnlimit
+ *
+ * 对应操作:{@code sendUniformMessage}, {@code getWXACodeUnlimit}
* 对应地址:
* POST https://api.weixin.qq.com/cgi-bin/message/wxopen/template/uniform_send?access_token=ACCESS_TOKEN
* POST https://api.weixin.qq.com/wxa/getwxacodeunlimit?access_token=ACCESS_TOKEN
* 参考文档地址: https://developers.weixin.qq.com/miniprogram/dev/api/open-api/uniform-message/sendUniformMessage.html
- * https://developers.weixin.qq.com/miniprogram/dev/api/open-api/qr-code/getWXACodeUnlimit.html
- *
+ * https://developers.weixin.qq.com/miniprogram/dev/api/open-api/qr-code/getWXACodeUnlimit.html
+ *
*/
CODE_41030(41030, "请求的参数不正确"),
/**
@@ -138,13 +138,13 @@ public enum WxMaErrorMsgEnum {
*/
CODE_45009(45009, "调用分钟频率受限"),
/**
- *
* 频率限制,每个用户每分钟100次.
- * 对应操作:code2Session
+ *
+ * 对应操作:{@code code2Session}
* 对应地址:
- * GET https://api.weixin.qq.com/sns/jscode2session?appid=APPID&secret=SECRET&js_code=JSCODE&grant_type=authorization_code
+ * {@code GET https://api.weixin.qq.com/sns/jscode2session?appid=APPID&secret=SECRET&js_code=JSCODE&grant_type=authorization_code}
* 参考文档地址: https://developers.weixin.qq.com/miniprogram/dev/api/open-api/login/code2Session.html
- *
+ *
*/
CODE_45011(45011, "频率限制,每个用户每分钟100次"),
/**
@@ -190,12 +190,13 @@ public enum WxMaErrorMsgEnum {
*/
CODE_45072(45072, "command字段取值不对"),
/**
- *
* 下发输入状态,需要之前30秒内跟用户有过消息交互.
- * 对应操作:customerTyping
+ *
+ * 对应操作:{@code customerTyping}
* 对应地址:
* POST https://api.weixin.qq.com/cgi-bin/message/custom/typing?access_token=ACCESS_TOKEN
* 参考文档地址: https://developers.weixin.qq.com/miniprogram/dev/api/open-api/customer-message/customerTyping.html
+ *
*/
CODE_45080(45080, "下发输入状态,需要之前30秒内跟用户有过消息交互"),
/**
@@ -686,7 +687,7 @@ public enum WxMaErrorMsgEnum {
/**
* 89252
- * 法人&企业信息一致性校验中 front checking
+ * {@code 法人&企业信息一致性校验中 front checking}
*/
CODE_89252(89252, "法人&企业信息一致性校验中"),
diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/error/WxOpenErrorMsgEnum.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/error/WxOpenErrorMsgEnum.java
index 28fb5de8ad..ba910e988b 100644
--- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/error/WxOpenErrorMsgEnum.java
+++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/error/WxOpenErrorMsgEnum.java
@@ -527,7 +527,7 @@ public enum WxOpenErrorMsgEnum {
CODE_40099(40099, "invalid code, this code has consumed."),
/**
- * invalid DateInfo, Make Sure OldDateInfoType==NewDateInfoType && NewBeginTime<=OldBeginTime && OldEndTime<= NewEndTime
+ * {@code invalid DateInfo, Make Sure OldDateInfoType==NewDateInfoType && NewBeginTime<=OldBeginTime && OldEndTime<= NewEndTime}
*/
CODE_40100(40100, "invalid DateInfo, Make Sure OldDateInfoType==NewDateInfoType && NewBeginTime<=OldBeginTime && OldEndTime<= NewEndTime"),
@@ -572,7 +572,7 @@ public enum WxOpenErrorMsgEnum {
CODE_40108(40108, "invalid client version"),
/**
- * too many code size, must <= 100
+ * {@code too many code size, must <= 100}
*/
CODE_40109(40109, "too many code size, must <= 100"),
@@ -702,7 +702,7 @@ public enum WxOpenErrorMsgEnum {
CODE_40135(40135, "invalid not supply bonus, can not change card_id which supply bonus to be not supply"),
/**
- * invalid use DepositCodeMode, make sure sku.quantity>DepositCode.quantity
+ * {@code invalid use DepositCodeMode, make sure sku.quantity>DepositCode.quantity}
*/
CODE_40136(40136, "invalid use DepositCodeMode, make sure sku.quantity>DepositCode.quantity"),
@@ -1082,7 +1082,7 @@ public enum WxOpenErrorMsgEnum {
CODE_40211(40211, "invalid scope_data"),
/**
- * paegs 当中存在不合法的query,query格式遵循URL标准,即k1=v1&k2=v2 invalid query
+ * {@code paegs 当中存在不合法的query,query格式遵循URL标准,即k1=v1&k2=v2 invalid query}
*/
CODE_40212(40212, "paegs 当中存在不合法的query,query格式遵循URL标准,即k1=v1&k2=v2"),
@@ -4242,7 +4242,7 @@ public enum WxOpenErrorMsgEnum {
CODE_71005(71005, "limit exe count"),
/**
- * limit coin count, 1 <= coin_count <= 100000
+ * {@code limit coin count, 1 <= coin_count <= 100000}
*/
CODE_71006(71006, "limit coin count, 1 <= coin_count <= 100000"),
@@ -4347,7 +4347,7 @@ public enum WxOpenErrorMsgEnum {
CODE_72018(72018, "duplicate order id, invoice had inserted to user"),
/**
- * limit msg operation card list size, must <= 5
+ * {@code limit msg operation card list size, must <= 5}
*/
CODE_72019(72019, "limit msg operation card list size, must <= 5"),
@@ -6432,7 +6432,7 @@ public enum WxOpenErrorMsgEnum {
CODE_88009(88009, "reply is not exists"),
/**
- * count range error. cout <= 0 or count > 50
+ * {@code count range error. cout <= 0 or count > 50}
*/
CODE_88010(88010, "count range error. cout <= 0 or count > 50"),
@@ -6682,7 +6682,7 @@ public enum WxOpenErrorMsgEnum {
CODE_89251(89251, "模板消息已下发,待法人人脸核身校验"),
/**
- * 法人&企业信息一致性校验中 front checking
+ * {@code 法人&企业信息一致性校验中 front checking}
*/
CODE_89253(89253, "法人&企业信息一致性校验中"),
@@ -7257,7 +7257,7 @@ public enum WxOpenErrorMsgEnum {
CODE_200021(200021, "场景描述 sceneDesc 参数错误"),
/**
- * 禁止创建/更新商品(如商品创建功能被封禁) 或 禁止编辑&更新房间
+ * {@code 禁止创建/更新商品(如商品创建功能被封禁) 或 禁止编辑&更新房间}
*/
CODE_300001(300001, "禁止创建/更新商品(如商品创建功能被封禁) 或 禁止编辑&更新房间"),
@@ -8382,7 +8382,7 @@ public enum WxOpenErrorMsgEnum {
CODE_9300003(9300003, "begin_time must less than end_time"),
/**
- * end_time - begin_time > 1year
+ * {@code end_time - begin_time > 1year}
*/
CODE_9300004(9300004, "end_time - begin_time > 1year"),
@@ -8397,7 +8397,7 @@ public enum WxOpenErrorMsgEnum {
CODE_9300006(9300006, "invalid activity status"),
/**
- * gift_num must >0 and <=15
+ * {@code gift_num must >0 and <=15}
*/
CODE_9300007(9300007, "gift_num must >0 and <=15"),
@@ -8412,7 +8412,7 @@ public enum WxOpenErrorMsgEnum {
CODE_9300009(9300009, "activity can not finish"),
/**
- * card_info_list must >= 2
+ * {@code card_info_list must >= 2}
*/
CODE_9300010(9300010, "card_info_list must >= 2"),
diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/executor/CommonUploadRequestExecutor.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/executor/CommonUploadRequestExecutor.java
index 2c9a4d7526..a93cbe1e99 100644
--- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/executor/CommonUploadRequestExecutor.java
+++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/executor/CommonUploadRequestExecutor.java
@@ -1,11 +1,15 @@
package me.chanjar.weixin.common.executor;
+import jodd.http.HttpConnectionProvider;
+import jodd.http.ProxyInfo;
import me.chanjar.weixin.common.bean.CommonUploadParam;
import me.chanjar.weixin.common.enums.WxType;
import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.common.util.http.RequestExecutor;
import me.chanjar.weixin.common.util.http.RequestHttp;
import me.chanjar.weixin.common.util.http.ResponseHandler;
+import me.chanjar.weixin.common.util.http.okhttp.OkHttpProxyInfo;
+import okhttp3.OkHttpClient;
import java.io.IOException;
@@ -34,15 +38,19 @@ public void execute(String uri, CommonUploadParam data, ResponseHandler
* @param requestHttp 请求信息
* @return 执行器
*/
- @SuppressWarnings({"rawtypes", "unchecked"})
- public static RequestExecutor create(RequestHttp requestHttp) {
+ @SuppressWarnings("unchecked")
+ public static RequestExecutor create(RequestHttp, ?> requestHttp) {
switch (requestHttp.getRequestType()) {
case APACHE_HTTP:
- return new CommonUploadRequestExecutorApacheImpl(requestHttp);
+ return new CommonUploadRequestExecutorApacheImpl(
+ (RequestHttp) requestHttp);
case JODD_HTTP:
- return new CommonUploadRequestExecutorJoddHttpImpl(requestHttp);
+ return new CommonUploadRequestExecutorJoddHttpImpl((RequestHttp) requestHttp);
case OK_HTTP:
- return new CommonUploadRequestExecutorOkHttpImpl(requestHttp);
+ return new CommonUploadRequestExecutorOkHttpImpl((RequestHttp) requestHttp);
+ case HTTP_COMPONENTS:
+ return new CommonUploadRequestExecutorHttpComponentsImpl(
+ (RequestHttp) requestHttp);
default:
throw new IllegalArgumentException("不支持的http执行器类型:" + requestHttp.getRequestType());
}
diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/executor/CommonUploadRequestExecutorApacheImpl.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/executor/CommonUploadRequestExecutorApacheImpl.java
index f37cb805da..7f19241cdb 100644
--- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/executor/CommonUploadRequestExecutorApacheImpl.java
+++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/executor/CommonUploadRequestExecutorApacheImpl.java
@@ -28,8 +28,7 @@
* @author 广州跨界
* created on 2024/01/11
*/
-public class CommonUploadRequestExecutorApacheImpl
- extends CommonUploadRequestExecutor {
+public class CommonUploadRequestExecutorApacheImpl extends CommonUploadRequestExecutor {
public CommonUploadRequestExecutorApacheImpl(RequestHttp requestHttp) {
super(requestHttp);
diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/executor/CommonUploadRequestExecutorHttpComponentsImpl.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/executor/CommonUploadRequestExecutorHttpComponentsImpl.java
new file mode 100644
index 0000000000..f79eaa49b8
--- /dev/null
+++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/executor/CommonUploadRequestExecutorHttpComponentsImpl.java
@@ -0,0 +1,75 @@
+package me.chanjar.weixin.common.executor;
+
+import lombok.Getter;
+import me.chanjar.weixin.common.bean.CommonUploadData;
+import me.chanjar.weixin.common.bean.CommonUploadParam;
+import me.chanjar.weixin.common.enums.WxType;
+import me.chanjar.weixin.common.error.WxError;
+import me.chanjar.weixin.common.error.WxErrorException;
+import me.chanjar.weixin.common.util.http.RequestHttp;
+import me.chanjar.weixin.common.util.http.hc.Utf8ResponseHandler;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.hc.client5.http.classic.methods.HttpPost;
+import org.apache.hc.client5.http.config.RequestConfig;
+import org.apache.hc.client5.http.entity.mime.HttpMultipartMode;
+import org.apache.hc.client5.http.entity.mime.InputStreamBody;
+import org.apache.hc.client5.http.entity.mime.MultipartEntityBuilder;
+import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
+import org.apache.hc.core5.http.ContentType;
+import org.apache.hc.core5.http.HttpEntity;
+import org.apache.hc.core5.http.HttpHost;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Apache HttpComponents 通用文件上传器
+ */
+public class CommonUploadRequestExecutorHttpComponentsImpl extends CommonUploadRequestExecutor {
+
+ public CommonUploadRequestExecutorHttpComponentsImpl(RequestHttp requestHttp) {
+ super(requestHttp);
+ }
+
+ @Override
+ public String execute(String uri, CommonUploadParam param, WxType wxType) throws WxErrorException, IOException {
+ HttpPost httpPost = new HttpPost(uri);
+ if (requestHttp.getRequestHttpProxy() != null) {
+ RequestConfig config = RequestConfig.custom().setProxy(requestHttp.getRequestHttpProxy()).build();
+ httpPost.setConfig(config);
+ }
+ if (param != null) {
+ CommonUploadData data = param.getData();
+ InnerStreamBody part = new InnerStreamBody(data.getInputStream(), ContentType.DEFAULT_BINARY, data.getFileName(), data.getLength());
+ HttpEntity entity = MultipartEntityBuilder
+ .create()
+ .addPart(param.getName(), part)
+ .setMode(HttpMultipartMode.EXTENDED)
+ .build();
+ httpPost.setEntity(entity);
+ }
+ String responseContent = requestHttp.getRequestHttpClient().execute(httpPost, Utf8ResponseHandler.INSTANCE);
+ if (StringUtils.isEmpty(responseContent)) {
+ throw new WxErrorException(String.format("上传失败,服务器响应空 url:%s param:%s", uri, param));
+ }
+ WxError error = WxError.fromJson(responseContent, wxType);
+ if (error.getErrorCode() != 0) {
+ throw new WxErrorException(error);
+ }
+ return responseContent;
+ }
+
+ /**
+ * 内部流 请求体
+ */
+ @Getter
+ public static class InnerStreamBody extends InputStreamBody {
+
+ private final long contentLength;
+
+ public InnerStreamBody(final InputStream in, final ContentType contentType, final String filename, long contentLength) {
+ super(in, contentType, filename);
+ this.contentLength = contentLength;
+ }
+ }
+}
diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/redis/RedisTemplateWxRedisOps.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/redis/RedisTemplateWxRedisOps.java
index 19d4046c92..d531a2a307 100644
--- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/redis/RedisTemplateWxRedisOps.java
+++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/redis/RedisTemplateWxRedisOps.java
@@ -29,7 +29,7 @@ public void setValue(String key, String value, int expire, TimeUnit timeUnit) {
@Override
public Long getExpire(String key) {
- return redisTemplate.getExpire(key);
+ return redisTemplate.getExpire(key, TimeUnit.SECONDS);
}
@Override
diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/requestexecuter/ocr/OcrDiscernHttpComponentsRequestExecutor.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/requestexecuter/ocr/OcrDiscernHttpComponentsRequestExecutor.java
new file mode 100644
index 0000000000..2d02c965a8
--- /dev/null
+++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/requestexecuter/ocr/OcrDiscernHttpComponentsRequestExecutor.java
@@ -0,0 +1,46 @@
+package me.chanjar.weixin.common.requestexecuter.ocr;
+
+import me.chanjar.weixin.common.enums.WxType;
+import me.chanjar.weixin.common.error.WxError;
+import me.chanjar.weixin.common.error.WxErrorException;
+import me.chanjar.weixin.common.util.http.RequestHttp;
+import me.chanjar.weixin.common.util.http.hc.Utf8ResponseHandler;
+import org.apache.hc.client5.http.classic.methods.HttpPost;
+import org.apache.hc.client5.http.config.RequestConfig;
+import org.apache.hc.client5.http.entity.mime.HttpMultipartMode;
+import org.apache.hc.client5.http.entity.mime.MultipartEntityBuilder;
+import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
+import org.apache.hc.core5.http.HttpEntity;
+import org.apache.hc.core5.http.HttpHost;
+
+import java.io.File;
+import java.io.IOException;
+
+public class OcrDiscernHttpComponentsRequestExecutor extends OcrDiscernRequestExecutor {
+ public OcrDiscernHttpComponentsRequestExecutor(RequestHttp requestHttp) {
+ super(requestHttp);
+ }
+
+ @Override
+ public String execute(String uri, File file, WxType wxType) throws WxErrorException, IOException {
+ HttpPost httpPost = new HttpPost(uri);
+ if (requestHttp.getRequestHttpProxy() != null) {
+ RequestConfig config = RequestConfig.custom().setProxy(requestHttp.getRequestHttpProxy()).build();
+ httpPost.setConfig(config);
+ }
+ if (file != null) {
+ HttpEntity entity = MultipartEntityBuilder
+ .create()
+ .addBinaryBody("file", file)
+ .setMode(HttpMultipartMode.EXTENDED)
+ .build();
+ httpPost.setEntity(entity);
+ }
+ String responseContent = requestHttp.getRequestHttpClient().execute(httpPost, Utf8ResponseHandler.INSTANCE);
+ WxError error = WxError.fromJson(responseContent, wxType);
+ if (error.getErrorCode() != 0) {
+ throw new WxErrorException(error);
+ }
+ return responseContent;
+ }
+}
diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/requestexecuter/ocr/OcrDiscernRequestExecutor.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/requestexecuter/ocr/OcrDiscernRequestExecutor.java
index 58e525bc0e..542ab4a378 100644
--- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/requestexecuter/ocr/OcrDiscernRequestExecutor.java
+++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/requestexecuter/ocr/OcrDiscernRequestExecutor.java
@@ -33,9 +33,13 @@ public void execute(String uri, File data, ResponseHandler handler, WxTy
public static RequestExecutor create(RequestHttp, ?> requestHttp) {
switch (requestHttp.getRequestType()) {
case APACHE_HTTP:
- return new OcrDiscernApacheHttpRequestExecutor((RequestHttp) requestHttp);
+ return new OcrDiscernApacheHttpRequestExecutor(
+ (RequestHttp) requestHttp);
+ case HTTP_COMPONENTS:
+ return new OcrDiscernHttpComponentsRequestExecutor(
+ (RequestHttp) requestHttp);
default:
- return null;
+ throw new IllegalArgumentException("不支持的http执行器类型:" + requestHttp.getRequestType());
}
}
}
diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/service/WxOcrService.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/service/WxOcrService.java
index 39a8a93754..d0aeef8491 100644
--- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/service/WxOcrService.java
+++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/service/WxOcrService.java
@@ -12,7 +12,9 @@
/**
* 基于小程序或 H5 的身份证、银行卡、行驶证 OCR 识别.
- * https://mp.weixin.qq.com/wiki?t=resource/res_main&id=21516712284rHWMX
+ *
+ * 参考:{@code https://mp.weixin.qq.com/wiki?t=resource/res_main&id=21516712284rHWMX}
+ *
*
* @author Binary Wang
* created on 2019-06-22
diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/session/InternalSessionManager.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/session/InternalSessionManager.java
index e3d9ab8351..24ea58ef38 100644
--- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/session/InternalSessionManager.java
+++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/session/InternalSessionManager.java
@@ -7,13 +7,12 @@ public interface InternalSessionManager {
/**
* Return the active Session, associated with this Manager, with the
- * specified session id (if any); otherwise return null.
+ * specified session id (if any); otherwise return {@code null}.
*
* @param id The session id for the session to be returned
+ * @return the session or null
* @throws IllegalStateException if a new session cannot be
* instantiated for any reason
- * @throws java.io.IOException if an input/output error occurs while
- * processing this request
*/
InternalSession findSession(String id);
diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/DataUtils.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/DataUtils.java
index 983d9a668f..b8fb42e0e9 100644
--- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/DataUtils.java
+++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/DataUtils.java
@@ -1,5 +1,6 @@
package me.chanjar.weixin.common.util;
+import org.apache.commons.lang3.RegExUtils;
import org.apache.commons.lang3.StringUtils;
/**
@@ -17,7 +18,7 @@ public class DataUtils {
public static E handleDataWithSecret(E data) {
E dataForLog = data;
if(data instanceof String && StringUtils.contains((String)data, "&secret=")){
- dataForLog = (E) StringUtils.replaceAll((String)data,"&secret=\\w+&","&secret=******&");
+ dataForLog = (E) RegExUtils.replaceAll((String)data,"&secret=\\w+&","&secret=******&");
}
return dataForLog;
}
diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/RandomUtils.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/RandomUtils.java
index bbb11992bc..a9017c0d16 100644
--- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/RandomUtils.java
+++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/RandomUtils.java
@@ -4,12 +4,24 @@ public class RandomUtils {
private static final String RANDOM_STR = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
- private static final java.util.Random RANDOM = new java.util.Random();
+ private static volatile java.util.Random random;
+
+ private static java.util.Random getRandom() {
+ if (random == null) {
+ synchronized (RandomUtils.class) {
+ if (random == null) {
+ random = new java.util.Random();
+ }
+ }
+ }
+ return random;
+ }
public static String getRandomStr() {
StringBuilder sb = new StringBuilder();
+ java.util.Random r = getRandom();
for (int i = 0; i < 16; i++) {
- sb.append(RANDOM_STR.charAt(RANDOM.nextInt(RANDOM_STR.length())));
+ sb.append(RANDOM_STR.charAt(r.nextInt(RANDOM_STR.length())));
}
return sb.toString();
}
diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/SignUtils.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/SignUtils.java
index fc3579d45c..1886209f98 100644
--- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/SignUtils.java
+++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/SignUtils.java
@@ -25,6 +25,7 @@ public class SignUtils {
*
* @param message 签名数据
* @param key 签名密钥
+ * @return 签名结果
*/
public static String createHmacSha256Sign(String message, String key) {
try {
diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/crypto/SHA1.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/crypto/SHA1.java
index 9b9f776768..43cc54b43d 100644
--- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/crypto/SHA1.java
+++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/crypto/SHA1.java
@@ -29,7 +29,10 @@ public static String gen(String... arr) {
}
/**
- * 用&串接arr参数,生成sha1 digest.
+ * {@code 用&串接arr参数,生成sha1 digest.}
+ *
+ * @param arr 参数数组
+ * @return sha1摘要
*/
public static String genWithAmple(String... arr) {
if (StringUtils.isAnyEmpty(arr)) {
diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/crypto/WxCryptUtil.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/crypto/WxCryptUtil.java
index 0a40d0e93c..50362636fc 100755
--- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/crypto/WxCryptUtil.java
+++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/crypto/WxCryptUtil.java
@@ -37,6 +37,19 @@ public class WxCryptUtil {
private static final Base64 BASE64 = new Base64();
private static final Charset CHARSET = StandardCharsets.UTF_8;
+ private static volatile Random random;
+
+ private static Random getRandom() {
+ if (random == null) {
+ synchronized (WxCryptUtil.class) {
+ if (random == null) {
+ random = new Random();
+ }
+ }
+ }
+ return random;
+ }
+
private static final ThreadLocal BUILDER_LOCAL = ThreadLocal.withInitial(() -> {
try {
final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
@@ -109,10 +122,10 @@ private static int bytesNetworkOrder2Number(byte[] bytesInNetworkOrder) {
*/
private static String genRandomStr() {
String base = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
- Random random = new Random();
+ Random r = getRandom();
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 16; i++) {
- int number = random.nextInt(base.length());
+ int number = r.nextInt(base.length());
sb.append(base.charAt(number));
}
return sb.toString();
@@ -184,6 +197,7 @@ public EncryptContext encryptContext(String plainText) {
/**
* 对明文进行加密.
*
+ * @param randomStr 随机字符串
* @param plainText 需要加密的明文
* @return 加密后base64编码的字符串
*/
@@ -320,14 +334,28 @@ public String decrypt(String cipherText) {
byte[] bytes = PKCS7Encoder.decode(original);
// 分离16位随机字符串,网络字节序和AppId
+ if (bytes == null || bytes.length < 20) {
+ throw new WxRuntimeException("解密后数据长度异常,可能为错误的密文或EncodingAESKey");
+ }
byte[] networkOrder = Arrays.copyOfRange(bytes, 16, 20);
int xmlLength = bytesNetworkOrder2Number(networkOrder);
- xmlContent = new String(Arrays.copyOfRange(bytes, 20, 20 + xmlLength), CHARSET);
- fromAppid = new String(Arrays.copyOfRange(bytes, 20 + xmlLength, bytes.length), CHARSET);
+ // 长度边界校验,避免非法长度导致的越界/参数异常
+ int startIndex = 20;
+ int endIndex = startIndex + xmlLength;
+ if (xmlLength < 0 || endIndex > bytes.length) {
+ throw new WxRuntimeException("解密后数据格式非法:消息长度不正确,可能为错误的密文或EncodingAESKey");
+ }
+
+ xmlContent = new String(Arrays.copyOfRange(bytes, startIndex, endIndex), CHARSET);
+ fromAppid = new String(Arrays.copyOfRange(bytes, endIndex, bytes.length), CHARSET);
} catch (Exception e) {
- throw new WxRuntimeException(e);
+ if (e instanceof WxRuntimeException) {
+ throw (WxRuntimeException) e;
+ } else {
+ throw new WxRuntimeException(e);
+ }
}
// appid不相同的情况 暂时忽略这段判断
diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/BaseMediaDownloadRequestExecutor.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/BaseMediaDownloadRequestExecutor.java
index fa60d364b0..8304742524 100644
--- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/BaseMediaDownloadRequestExecutor.java
+++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/BaseMediaDownloadRequestExecutor.java
@@ -1,19 +1,18 @@
package me.chanjar.weixin.common.util.http;
-import java.io.File;
-import java.io.IOException;
-
import jodd.http.HttpConnectionProvider;
import jodd.http.ProxyInfo;
import me.chanjar.weixin.common.enums.WxType;
import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.common.util.http.apache.ApacheMediaDownloadRequestExecutor;
+import me.chanjar.weixin.common.util.http.hc.HttpComponentsMediaDownloadRequestExecutor;
import me.chanjar.weixin.common.util.http.jodd.JoddHttpMediaDownloadRequestExecutor;
import me.chanjar.weixin.common.util.http.okhttp.OkHttpMediaDownloadRequestExecutor;
import me.chanjar.weixin.common.util.http.okhttp.OkHttpProxyInfo;
import okhttp3.OkHttpClient;
-import org.apache.http.HttpHost;
-import org.apache.http.impl.client.CloseableHttpClient;
+
+import java.io.File;
+import java.io.IOException;
/**
* 下载媒体文件请求执行器.
@@ -40,13 +39,17 @@ public void execute(String uri, String data, ResponseHandler handler, WxTy
public static RequestExecutor create(RequestHttp, ?> requestHttp, File tmpDirFile) {
switch (requestHttp.getRequestType()) {
case APACHE_HTTP:
- return new ApacheMediaDownloadRequestExecutor((RequestHttp) requestHttp, tmpDirFile);
+ return new ApacheMediaDownloadRequestExecutor(
+ (RequestHttp) requestHttp, tmpDirFile);
case JODD_HTTP:
return new JoddHttpMediaDownloadRequestExecutor((RequestHttp) requestHttp, tmpDirFile);
case OK_HTTP:
return new OkHttpMediaDownloadRequestExecutor((RequestHttp) requestHttp, tmpDirFile);
+ case HTTP_COMPONENTS:
+ return new HttpComponentsMediaDownloadRequestExecutor(
+ (RequestHttp) requestHttp, tmpDirFile);
default:
- return null;
+ throw new IllegalArgumentException("不支持的http执行器类型:" + requestHttp.getRequestType());
}
}
diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/HttpType.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/HttpClientType.java
similarity index 59%
rename from weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/HttpType.java
rename to weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/HttpClientType.java
index eff5907f7a..a4e22be9b4 100644
--- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/HttpType.java
+++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/HttpClientType.java
@@ -3,17 +3,21 @@
/**
* Created by ecoolper on 2017/4/28.
*/
-public enum HttpType {
+public enum HttpClientType {
/**
* jodd-http.
*/
JODD_HTTP,
/**
- * apache httpclient.
+ * apache httpclient 4.x.
*/
APACHE_HTTP,
/**
* okhttp.
*/
- OK_HTTP
+ OK_HTTP,
+ /**
+ * apache httpclient 5.x.
+ */
+ HTTP_COMPONENTS
}
diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/HttpResponseProxy.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/HttpResponseProxy.java
index 11b1209460..e45294b503 100644
--- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/HttpResponseProxy.java
+++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/HttpResponseProxy.java
@@ -1,10 +1,10 @@
package me.chanjar.weixin.common.util.http;
-import jodd.http.HttpResponse;
import me.chanjar.weixin.common.error.WxErrorException;
-import okhttp3.Response;
-import org.apache.http.Header;
-import org.apache.http.client.methods.CloseableHttpResponse;
+import me.chanjar.weixin.common.util.http.apache.ApacheHttpResponseProxy;
+import me.chanjar.weixin.common.util.http.hc.HttpComponentsResponseProxy;
+import me.chanjar.weixin.common.util.http.jodd.JoddHttpResponseProxy;
+import me.chanjar.weixin.common.util.http.okhttp.OkHttpResponseProxy;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
@@ -14,68 +14,33 @@
/**
*
- * 三种http框架的response代理类,方便提取公共方法
+ * http 框架的 response 代理类,方便提取公共方法
* Created by Binary Wang on 2017-8-3.
*
*
* @author Binary Wang
*/
-public class HttpResponseProxy {
+public interface HttpResponseProxy {
- private CloseableHttpResponse apacheHttpResponse;
- private HttpResponse joddHttpResponse;
- private Response okHttpResponse;
-
- public HttpResponseProxy(CloseableHttpResponse apacheHttpResponse) {
- this.apacheHttpResponse = apacheHttpResponse;
- }
-
- public HttpResponseProxy(HttpResponse joddHttpResponse) {
- this.joddHttpResponse = joddHttpResponse;
+ static ApacheHttpResponseProxy from(org.apache.http.client.methods.CloseableHttpResponse response) {
+ return new ApacheHttpResponseProxy(response);
}
- public HttpResponseProxy(Response okHttpResponse) {
- this.okHttpResponse = okHttpResponse;
- }
-
- public String getFileName() throws WxErrorException {
- //由于对象只能由一个构造方法实现,因此三个response对象必定且只有一个不为空
- if (this.apacheHttpResponse != null) {
- return this.getFileName(this.apacheHttpResponse);
- }
-
- if (this.joddHttpResponse != null) {
- return this.getFileName(this.joddHttpResponse);
- }
-
- if (this.okHttpResponse != null) {
- return this.getFileName(this.okHttpResponse);
- }
-
- //cannot happen
- return null;
+ static HttpComponentsResponseProxy from(org.apache.hc.client5.http.impl.classic.CloseableHttpResponse response) {
+ return new HttpComponentsResponseProxy(response);
}
- private String getFileName(CloseableHttpResponse response) throws WxErrorException {
- Header[] contentDispositionHeader = response.getHeaders("Content-disposition");
- if (contentDispositionHeader == null || contentDispositionHeader.length == 0) {
- throw new WxErrorException("无法获取到文件名,Content-disposition为空");
- }
-
- return extractFileNameFromContentString(contentDispositionHeader[0].getValue());
+ static JoddHttpResponseProxy from(jodd.http.HttpResponse response) {
+ return new JoddHttpResponseProxy(response);
}
- private String getFileName(HttpResponse response) throws WxErrorException {
- String content = response.header("Content-disposition");
- return extractFileNameFromContentString(content);
+ static OkHttpResponseProxy from(okhttp3.Response response) {
+ return new OkHttpResponseProxy(response);
}
- private String getFileName(Response response) throws WxErrorException {
- String content = response.header("Content-disposition");
- return extractFileNameFromContentString(content);
- }
+ String getFileName() throws WxErrorException;
- public static String extractFileNameFromContentString(String content) throws WxErrorException {
+ static String extractFileNameFromContentString(String content) throws WxErrorException {
if (content == null || content.isEmpty()) {
throw new WxErrorException("无法获取到文件名,content为空");
}
diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/InputStreamData.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/InputStreamData.java
index d07873f3c4..f03932984f 100644
--- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/InputStreamData.java
+++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/InputStreamData.java
@@ -10,8 +10,9 @@
/**
* 输入流数据.
- *
+ *
* InputStreamData
+ *
*
* @author zichuan.zhou91@gmail.com
* created on 2022/2/15
diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/MediaInputStreamUploadRequestExecutor.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/MediaInputStreamUploadRequestExecutor.java
index cd92ba3b63..22c426ca54 100644
--- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/MediaInputStreamUploadRequestExecutor.java
+++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/MediaInputStreamUploadRequestExecutor.java
@@ -6,12 +6,11 @@
import me.chanjar.weixin.common.enums.WxType;
import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.common.util.http.apache.ApacheMediaInputStreamUploadRequestExecutor;
+import me.chanjar.weixin.common.util.http.hc.HttpComponentsMediaInputStreamUploadRequestExecutor;
import me.chanjar.weixin.common.util.http.jodd.JoddHttpMediaInputStreamUploadRequestExecutor;
import me.chanjar.weixin.common.util.http.okhttp.OkHttpMediaInputStreamUploadRequestExecutor;
import me.chanjar.weixin.common.util.http.okhttp.OkHttpProxyInfo;
import okhttp3.OkHttpClient;
-import org.apache.http.HttpHost;
-import org.apache.http.impl.client.CloseableHttpClient;
import java.io.IOException;
@@ -33,16 +32,21 @@ public void execute(String uri, InputStreamData data, ResponseHandler create(RequestHttp, ?> requestHttp) {
switch (requestHttp.getRequestType()) {
case APACHE_HTTP:
- return new ApacheMediaInputStreamUploadRequestExecutor((RequestHttp) requestHttp);
+ return new ApacheMediaInputStreamUploadRequestExecutor(
+ (RequestHttp) requestHttp);
case JODD_HTTP:
return new JoddHttpMediaInputStreamUploadRequestExecutor((RequestHttp) requestHttp);
case OK_HTTP:
return new OkHttpMediaInputStreamUploadRequestExecutor((RequestHttp) requestHttp);
+ case HTTP_COMPONENTS:
+ return new HttpComponentsMediaInputStreamUploadRequestExecutor(
+ (RequestHttp) requestHttp);
default:
- return null;
+ throw new IllegalArgumentException("不支持的http执行器类型:" + requestHttp.getRequestType());
}
}
diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/MediaUploadRequestExecutor.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/MediaUploadRequestExecutor.java
index 9b4f2d5571..2d16e714e9 100644
--- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/MediaUploadRequestExecutor.java
+++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/MediaUploadRequestExecutor.java
@@ -8,12 +8,11 @@
import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.common.service.WxService;
import me.chanjar.weixin.common.util.http.apache.ApacheMediaUploadRequestExecutor;
+import me.chanjar.weixin.common.util.http.hc.HttpComponentsMediaUploadRequestExecutor;
import me.chanjar.weixin.common.util.http.jodd.JoddHttpMediaUploadRequestExecutor;
import me.chanjar.weixin.common.util.http.okhttp.OkHttpMediaUploadRequestExecutor;
import me.chanjar.weixin.common.util.http.okhttp.OkHttpProxyInfo;
import okhttp3.OkHttpClient;
-import org.apache.http.HttpHost;
-import org.apache.http.impl.client.CloseableHttpClient;
import java.io.File;
import java.io.IOException;
@@ -40,16 +39,21 @@ public void execute(String uri, File data, ResponseHandler
handler.handle(this.execute(uri, data, wxType));
}
+ @SuppressWarnings("unchecked")
public static RequestExecutor create(RequestHttp, ?> requestHttp) {
switch (requestHttp.getRequestType()) {
case APACHE_HTTP:
- return new ApacheMediaUploadRequestExecutor((RequestHttp) requestHttp);
+ return new ApacheMediaUploadRequestExecutor(
+ (RequestHttp) requestHttp);
case JODD_HTTP:
return new JoddHttpMediaUploadRequestExecutor((RequestHttp) requestHttp);
case OK_HTTP:
return new OkHttpMediaUploadRequestExecutor((RequestHttp) requestHttp);
+ case HTTP_COMPONENTS:
+ return new HttpComponentsMediaUploadRequestExecutor(
+ (RequestHttp) requestHttp);
default:
- return null;
+ throw new IllegalArgumentException("不支持的http执行器类型:" + requestHttp.getRequestType());
}
}
diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/MinishopUploadRequestCustomizeExecutor.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/MinishopUploadRequestCustomizeExecutor.java
index 97d4e1b3b8..0e8684a1db 100644
--- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/MinishopUploadRequestCustomizeExecutor.java
+++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/MinishopUploadRequestCustomizeExecutor.java
@@ -6,12 +6,11 @@
import me.chanjar.weixin.common.enums.WxType;
import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.common.util.http.apache.ApacheMinishopMediaUploadRequestCustomizeExecutor;
+import me.chanjar.weixin.common.util.http.hc.HttpComponentsMinishopMediaUploadRequestCustomizeExecutor;
import me.chanjar.weixin.common.util.http.jodd.JoddHttpMinishopMediaUploadRequestCustomizeExecutor;
import me.chanjar.weixin.common.util.http.okhttp.OkHttpMinishopMediaUploadRequestCustomizeExecutor;
import me.chanjar.weixin.common.util.http.okhttp.OkHttpProxyInfo;
import okhttp3.OkHttpClient;
-import org.apache.http.HttpHost;
-import org.apache.http.impl.client.CloseableHttpClient;
import java.io.File;
import java.io.IOException;
@@ -27,8 +26,7 @@ public MinishopUploadRequestCustomizeExecutor(RequestHttp requestHttp, Str
this.respType = respType;
if (imgUrl == null || imgUrl.isEmpty()) {
this.uploadType = "0";
- }
- else {
+ } else {
this.uploadType = "1";
this.imgUrl = imgUrl;
}
@@ -43,13 +41,17 @@ public void execute(String uri, File data, ResponseHandler create(RequestHttp, ?> requestHttp, String respType, String imgUrl) {
switch (requestHttp.getRequestType()) {
case APACHE_HTTP:
- return new ApacheMinishopMediaUploadRequestCustomizeExecutor((RequestHttp) requestHttp, respType, imgUrl);
+ return new ApacheMinishopMediaUploadRequestCustomizeExecutor(
+ (RequestHttp) requestHttp, respType, imgUrl);
case JODD_HTTP:
return new JoddHttpMinishopMediaUploadRequestCustomizeExecutor((RequestHttp) requestHttp, respType, imgUrl);
case OK_HTTP:
return new OkHttpMinishopMediaUploadRequestCustomizeExecutor((RequestHttp) requestHttp, respType, imgUrl);
+ case HTTP_COMPONENTS:
+ return new HttpComponentsMinishopMediaUploadRequestCustomizeExecutor(
+ (RequestHttp) requestHttp, respType, imgUrl);
default:
- return null;
+ throw new IllegalArgumentException("不支持的http执行器类型:" + requestHttp.getRequestType());
}
}
}
diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/MinishopUploadRequestExecutor.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/MinishopUploadRequestExecutor.java
index 7b7f9ca460..e6018a7791 100644
--- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/MinishopUploadRequestExecutor.java
+++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/MinishopUploadRequestExecutor.java
@@ -6,12 +6,11 @@
import me.chanjar.weixin.common.enums.WxType;
import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.common.util.http.apache.ApacheMinishopMediaUploadRequestExecutor;
+import me.chanjar.weixin.common.util.http.hc.HttpComponentsMinishopMediaUploadRequestExecutor;
import me.chanjar.weixin.common.util.http.jodd.JoddHttpMinishopMediaUploadRequestExecutor;
import me.chanjar.weixin.common.util.http.okhttp.OkHttpMinishopMediaUploadRequestExecutor;
import me.chanjar.weixin.common.util.http.okhttp.OkHttpProxyInfo;
import okhttp3.OkHttpClient;
-import org.apache.http.HttpHost;
-import org.apache.http.impl.client.CloseableHttpClient;
import java.io.File;
import java.io.IOException;
@@ -32,13 +31,17 @@ public void execute(String uri, File data, ResponseHandler create(RequestHttp, ?> requestHttp) {
switch (requestHttp.getRequestType()) {
case APACHE_HTTP:
- return new ApacheMinishopMediaUploadRequestExecutor((RequestHttp) requestHttp);
+ return new ApacheMinishopMediaUploadRequestExecutor(
+ (RequestHttp) requestHttp);
case JODD_HTTP:
return new JoddHttpMinishopMediaUploadRequestExecutor((RequestHttp) requestHttp);
case OK_HTTP:
return new OkHttpMinishopMediaUploadRequestExecutor((RequestHttp) requestHttp);
+ case HTTP_COMPONENTS:
+ return new HttpComponentsMinishopMediaUploadRequestExecutor(
+ (RequestHttp) requestHttp);
default:
- return null;
+ throw new IllegalArgumentException("不支持的http执行器类型:" + requestHttp.getRequestType());
}
}
}
diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/RequestHttp.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/RequestHttp.java
index b7bc850f8f..36be78b8ae 100644
--- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/RequestHttp.java
+++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/RequestHttp.java
@@ -26,6 +26,6 @@ public interface RequestHttp {
*
* @return HttpType
*/
- HttpType getRequestType();
+ HttpClientType getRequestType();
}
diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/SimpleGetRequestExecutor.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/SimpleGetRequestExecutor.java
index 4f2ad64afc..a880a9323c 100644
--- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/SimpleGetRequestExecutor.java
+++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/SimpleGetRequestExecutor.java
@@ -6,12 +6,11 @@
import me.chanjar.weixin.common.error.WxError;
import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.common.util.http.apache.ApacheSimpleGetRequestExecutor;
+import me.chanjar.weixin.common.util.http.hc.HttpComponentsSimpleGetRequestExecutor;
import me.chanjar.weixin.common.util.http.jodd.JoddHttpSimpleGetRequestExecutor;
import me.chanjar.weixin.common.util.http.okhttp.OkHttpProxyInfo;
import me.chanjar.weixin.common.util.http.okhttp.OkHttpSimpleGetRequestExecutor;
import okhttp3.OkHttpClient;
-import org.apache.http.HttpHost;
-import org.apache.http.impl.client.CloseableHttpClient;
import java.io.IOException;
@@ -37,13 +36,17 @@ public void execute(String uri, String data, ResponseHandler handler, Wx
public static RequestExecutor create(RequestHttp, ?> requestHttp) {
switch (requestHttp.getRequestType()) {
case APACHE_HTTP:
- return new ApacheSimpleGetRequestExecutor((RequestHttp< CloseableHttpClient, HttpHost>) requestHttp);
+ return new ApacheSimpleGetRequestExecutor(
+ (RequestHttp) requestHttp);
case JODD_HTTP:
return new JoddHttpSimpleGetRequestExecutor((RequestHttp) requestHttp);
case OK_HTTP:
return new OkHttpSimpleGetRequestExecutor((RequestHttp) requestHttp);
+ case HTTP_COMPONENTS:
+ return new HttpComponentsSimpleGetRequestExecutor(
+ (RequestHttp) requestHttp);
default:
- throw new IllegalArgumentException("非法请求参数");
+ throw new IllegalArgumentException("不支持的http执行器类型:" + requestHttp.getRequestType());
}
}
diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/SimplePostRequestExecutor.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/SimplePostRequestExecutor.java
index 68265ace52..2cc086cd0f 100644
--- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/SimplePostRequestExecutor.java
+++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/SimplePostRequestExecutor.java
@@ -6,12 +6,11 @@
import me.chanjar.weixin.common.error.WxError;
import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.common.util.http.apache.ApacheSimplePostRequestExecutor;
+import me.chanjar.weixin.common.util.http.hc.HttpComponentsSimplePostRequestExecutor;
import me.chanjar.weixin.common.util.http.jodd.JoddHttpSimplePostRequestExecutor;
import me.chanjar.weixin.common.util.http.okhttp.OkHttpProxyInfo;
import me.chanjar.weixin.common.util.http.okhttp.OkHttpSimplePostRequestExecutor;
import okhttp3.OkHttpClient;
-import org.apache.http.HttpHost;
-import org.apache.http.impl.client.CloseableHttpClient;
import org.jetbrains.annotations.NotNull;
import java.io.IOException;
@@ -34,16 +33,21 @@ public void execute(String uri, String data, ResponseHandler handler, Wx
handler.handle(this.execute(uri, data, wxType));
}
+ @SuppressWarnings("unchecked")
public static RequestExecutor create(RequestHttp, ?> requestHttp) {
switch (requestHttp.getRequestType()) {
case APACHE_HTTP:
- return new ApacheSimplePostRequestExecutor((RequestHttp) requestHttp);
+ return new ApacheSimplePostRequestExecutor(
+ (RequestHttp) requestHttp);
case JODD_HTTP:
return new JoddHttpSimplePostRequestExecutor((RequestHttp) requestHttp);
case OK_HTTP:
return new OkHttpSimplePostRequestExecutor((RequestHttp) requestHttp);
+ case HTTP_COMPONENTS:
+ return new HttpComponentsSimplePostRequestExecutor(
+ (RequestHttp) requestHttp);
default:
- throw new IllegalArgumentException("非法请求参数");
+ throw new IllegalArgumentException("不支持的http执行器类型:" + requestHttp.getRequestType());
}
}
diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/ApacheHttpClientBuilder.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/ApacheHttpClientBuilder.java
index 0d5073de14..5b13e7cc17 100644
--- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/ApacheHttpClientBuilder.java
+++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/ApacheHttpClientBuilder.java
@@ -21,36 +21,66 @@ public interface ApacheHttpClientBuilder {
/**
* 代理服务器地址.
+ *
+ * @param httpProxyHost 代理服务器地址
+ * @return ApacheHttpClientBuilder
*/
ApacheHttpClientBuilder httpProxyHost(String httpProxyHost);
/**
* 代理服务器端口.
+ *
+ * @param httpProxyPort 代理服务器端口
+ * @return ApacheHttpClientBuilder
*/
ApacheHttpClientBuilder httpProxyPort(int httpProxyPort);
/**
* 代理服务器用户名.
+ *
+ * @param httpProxyUsername 代理服务器用户名
+ * @return ApacheHttpClientBuilder
*/
ApacheHttpClientBuilder httpProxyUsername(String httpProxyUsername);
/**
* 代理服务器密码.
+ *
+ * @param httpProxyPassword 代理服务器密码
+ * @return ApacheHttpClientBuilder
*/
ApacheHttpClientBuilder httpProxyPassword(String httpProxyPassword);
/**
* 重试策略.
+ *
+ * @param httpRequestRetryHandler 重试处理器
+ * @return ApacheHttpClientBuilder
*/
- ApacheHttpClientBuilder httpRequestRetryHandler(HttpRequestRetryHandler httpRequestRetryHandler );
+ ApacheHttpClientBuilder httpRequestRetryHandler(HttpRequestRetryHandler httpRequestRetryHandler);
/**
* 超时时间.
+ *
+ * @param keepAliveStrategy 保持连接策略
+ * @return ApacheHttpClientBuilder
*/
ApacheHttpClientBuilder keepAliveStrategy(ConnectionKeepAliveStrategy keepAliveStrategy);
/**
* ssl连接socket工厂.
+ *
+ * @param sslConnectionSocketFactory SSL连接Socket工厂
+ * @return ApacheHttpClientBuilder
*/
ApacheHttpClientBuilder sslConnectionSocketFactory(SSLConnectionSocketFactory sslConnectionSocketFactory);
+
+ /**
+ * 支持的TLS协议版本.
+ * Supported TLS protocol versions.
+ *
+ * @param supportedProtocols 支持的协议版本数组
+ * @return ApacheHttpClientBuilder
+ */
+ ApacheHttpClientBuilder supportedProtocols(String[] supportedProtocols);
}
diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/ApacheHttpDnsClientBuilder.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/ApacheHttpDnsClientBuilder.java
index 6a136600e5..b3ebf350be 100644
--- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/ApacheHttpDnsClientBuilder.java
+++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/ApacheHttpDnsClientBuilder.java
@@ -117,6 +117,13 @@ public ApacheHttpClientBuilder sslConnectionSocketFactory(SSLConnectionSocketFac
return this;
}
+ @Override
+ public ApacheHttpClientBuilder supportedProtocols(String[] supportedProtocols) {
+ // This implementation doesn't use the supportedProtocols parameter as it relies on the provided SSLConnectionSocketFactory
+ // Users should configure the SSLConnectionSocketFactory with desired protocols before setting it
+ return this;
+ }
+
/**
* 获取链接的超时时间设置,默认3000ms
*
diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/ApacheHttpResponseProxy.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/ApacheHttpResponseProxy.java
new file mode 100644
index 0000000000..06439d3879
--- /dev/null
+++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/ApacheHttpResponseProxy.java
@@ -0,0 +1,25 @@
+package me.chanjar.weixin.common.util.http.apache;
+
+import me.chanjar.weixin.common.error.WxErrorException;
+import me.chanjar.weixin.common.util.http.HttpResponseProxy;
+import org.apache.http.Header;
+import org.apache.http.client.methods.CloseableHttpResponse;
+
+public class ApacheHttpResponseProxy implements HttpResponseProxy {
+
+ private final CloseableHttpResponse httpResponse;
+
+ public ApacheHttpResponseProxy(CloseableHttpResponse closeableHttpResponse) {
+ this.httpResponse = closeableHttpResponse;
+ }
+
+ @Override
+ public String getFileName() throws WxErrorException {
+ Header[] contentDispositionHeader = this.httpResponse.getHeaders("Content-disposition");
+ if (contentDispositionHeader == null || contentDispositionHeader.length == 0) {
+ throw new WxErrorException("无法获取到文件名,Content-disposition为空");
+ }
+
+ return HttpResponseProxy.extractFileNameFromContentString(contentDispositionHeader[0].getValue());
+ }
+}
diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/ApacheMediaDownloadRequestExecutor.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/ApacheMediaDownloadRequestExecutor.java
index b2d07d2779..554dc8df7b 100644
--- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/ApacheMediaDownloadRequestExecutor.java
+++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/ApacheMediaDownloadRequestExecutor.java
@@ -58,7 +58,7 @@ public File execute(String uri, String queryParam, WxType wxType) throws WxError
}
}
- String fileName = new HttpResponseProxy(response).getFileName();
+ String fileName = HttpResponseProxy.from(response).getFileName();
if (StringUtils.isBlank(fileName)) {
fileName = String.valueOf(System.currentTimeMillis());
}
diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/ApacheSimplePostRequestExecutor.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/ApacheSimplePostRequestExecutor.java
index 410e30d5d7..65af81690d 100644
--- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/ApacheSimplePostRequestExecutor.java
+++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/ApacheSimplePostRequestExecutor.java
@@ -4,14 +4,15 @@
import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.common.util.http.RequestHttp;
import me.chanjar.weixin.common.util.http.SimplePostRequestExecutor;
-import org.apache.http.Consts;
import org.apache.http.HttpHost;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.HttpPost;
+import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import java.io.IOException;
+import java.nio.charset.StandardCharsets;
/**
* .
@@ -33,8 +34,7 @@ public String execute(String uri, String postEntity, WxType wxType) throws WxErr
}
if (postEntity != null) {
- StringEntity entity = new StringEntity(postEntity, Consts.UTF_8);
- entity.setContentType("application/json; charset=utf-8");
+ StringEntity entity = new StringEntity(postEntity, ContentType.APPLICATION_JSON.withCharset(StandardCharsets.UTF_8));
httpPost.setEntity(entity);
}
diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/DefaultApacheHttpClientBuilder.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/DefaultApacheHttpClientBuilder.java
index d071dc97d2..ef7120b768 100644
--- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/DefaultApacheHttpClientBuilder.java
+++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/DefaultApacheHttpClientBuilder.java
@@ -93,6 +93,12 @@ public class DefaultApacheHttpClientBuilder implements ApacheHttpClientBuilder {
*/
private String userAgent;
+ /**
+ * 支持的TLS协议版本,默认支持现代TLS版本
+ * Supported TLS protocol versions, defaults to modern TLS versions
+ */
+ private String[] supportedProtocols = {"TLSv1.2", "TLSv1.3", "TLSv1.1", "TLSv1"};
+
/**
* 自定义请求拦截器
*/
@@ -179,6 +185,12 @@ public ApacheHttpClientBuilder sslConnectionSocketFactory(SSLConnectionSocketFac
return this;
}
+ @Override
+ public ApacheHttpClientBuilder supportedProtocols(String[] supportedProtocols) {
+ this.supportedProtocols = supportedProtocols;
+ return this;
+ }
+
public IdleConnectionMonitorThread getIdleConnectionMonitorThread() {
return this.idleConnectionMonitorThread;
}
@@ -257,7 +269,7 @@ private SSLConnectionSocketFactory buildSSLConnectionSocketFactory() {
return new SSLConnectionSocketFactory(
sslcontext,
- new String[]{"TLSv1"},
+ this.supportedProtocols,
null,
SSLConnectionSocketFactory.getDefaultHostnameVerifier());
} catch (NoSuchAlgorithmException | KeyManagementException | KeyStoreException e) {
diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/hc/BasicResponseHandler.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/hc/BasicResponseHandler.java
new file mode 100644
index 0000000000..f69e14a240
--- /dev/null
+++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/hc/BasicResponseHandler.java
@@ -0,0 +1,14 @@
+package me.chanjar.weixin.common.util.http.hc;
+
+import org.apache.hc.client5.http.impl.classic.BasicHttpClientResponseHandler;
+
+/**
+ * ApacheBasicResponseHandler
+ *
+ * @author altusea
+ */
+public class BasicResponseHandler extends BasicHttpClientResponseHandler {
+
+ public static final BasicHttpClientResponseHandler INSTANCE = new BasicHttpClientResponseHandler();
+
+}
diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/hc/ByteArrayResponseHandler.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/hc/ByteArrayResponseHandler.java
new file mode 100644
index 0000000000..e4a314f866
--- /dev/null
+++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/hc/ByteArrayResponseHandler.java
@@ -0,0 +1,22 @@
+package me.chanjar.weixin.common.util.http.hc;
+
+import org.apache.hc.client5.http.impl.classic.AbstractHttpClientResponseHandler;
+import org.apache.hc.core5.http.HttpEntity;
+import org.apache.hc.core5.http.io.entity.EntityUtils;
+
+import java.io.IOException;
+
+/**
+ * ByteArrayResponseHandler
+ *
+ * @author altusea
+ */
+public class ByteArrayResponseHandler extends AbstractHttpClientResponseHandler {
+
+ public static final ByteArrayResponseHandler INSTANCE = new ByteArrayResponseHandler();
+
+ @Override
+ public byte[] handleEntity(HttpEntity entity) throws IOException {
+ return EntityUtils.toByteArray(entity);
+ }
+}
diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/hc/DefaultHttpComponentsClientBuilder.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/hc/DefaultHttpComponentsClientBuilder.java
new file mode 100644
index 0000000000..4915e31a16
--- /dev/null
+++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/hc/DefaultHttpComponentsClientBuilder.java
@@ -0,0 +1,249 @@
+package me.chanjar.weixin.common.util.http.hc;
+
+import lombok.Data;
+import lombok.extern.slf4j.Slf4j;
+import me.chanjar.weixin.common.util.http.apache.ApacheHttpClientBuilder;
+import me.chanjar.weixin.common.util.http.apache.DefaultApacheHttpClientBuilder;
+import org.apache.commons.lang3.ObjectUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.hc.client5.http.ConnectionKeepAliveStrategy;
+import org.apache.hc.client5.http.HttpRequestRetryStrategy;
+import org.apache.hc.client5.http.auth.AuthScope;
+import org.apache.hc.client5.http.auth.UsernamePasswordCredentials;
+import org.apache.hc.client5.http.config.ConnectionConfig;
+import org.apache.hc.client5.http.config.RequestConfig;
+import org.apache.hc.client5.http.impl.auth.BasicCredentialsProvider;
+import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
+import org.apache.hc.client5.http.impl.classic.HttpClientBuilder;
+import org.apache.hc.client5.http.impl.classic.HttpClients;
+import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager;
+import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuilder;
+import org.apache.hc.client5.http.ssl.DefaultClientTlsStrategy;
+import org.apache.hc.client5.http.ssl.NoopHostnameVerifier;
+import org.apache.hc.client5.http.ssl.TrustAllStrategy;
+import org.apache.hc.core5.http.HttpHost;
+import org.apache.hc.core5.http.HttpRequestInterceptor;
+import org.apache.hc.core5.http.HttpResponseInterceptor;
+import org.apache.hc.core5.http.io.SocketConfig;
+import org.apache.hc.core5.ssl.SSLContexts;
+
+import javax.annotation.concurrent.NotThreadSafe;
+import javax.net.ssl.SSLContext;
+import java.security.KeyManagementException;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * DefaultApacheHttpClientBuilder
+ *
+ * @author altusea
+ */
+@Slf4j
+@Data
+@NotThreadSafe
+public class DefaultHttpComponentsClientBuilder implements HttpComponentsClientBuilder {
+
+ private final AtomicBoolean prepared = new AtomicBoolean(false);
+
+ /**
+ * 获取链接的超时时间设置
+ *
+ * 设置为零时不超时,一直等待.
+ * 设置为负数是使用系统默认设置(非3000ms的默认值,而是httpClient的默认设置).
+ *
+ */
+ private int connectionRequestTimeout = 3000;
+
+ /**
+ * 建立链接的超时时间,默认为5000ms.由于是在链接池获取链接,此设置应该并不起什么作用
+ *
+ * 设置为零时不超时,一直等待.
+ * 设置为负数是使用系统默认设置(非上述的5000ms的默认值,而是httpclient的默认设置).
+ *
+ */
+ private int connectionTimeout = 5000;
+ /**
+ * 默认NIO的socket超时设置,默认5000ms.
+ */
+ private int soTimeout = 5000;
+ /**
+ * 空闲链接的超时时间,默认60000ms.
+ *
+ * 超时的链接将在下一次空闲链接检查是被销毁
+ *
+ */
+ private int idleConnTimeout = 60000;
+ /**
+ * 检查空间链接的间隔周期,默认60000ms.
+ */
+ private int checkWaitTime = 60000;
+ /**
+ * 每路的最大链接数,默认10
+ */
+ private int maxConnPerHost = 10;
+ /**
+ * 最大总连接数,默认50
+ */
+ private int maxTotalConn = 50;
+ /**
+ * 自定义httpclient的User Agent
+ */
+ private String userAgent;
+
+ /**
+ * 自定义请求拦截器
+ */
+ private List requestInterceptors = new ArrayList<>();
+
+ /**
+ * 自定义响应拦截器
+ */
+ private List responseInterceptors = new ArrayList<>();
+
+ /**
+ * 自定义重试策略
+ */
+ private HttpRequestRetryStrategy httpRequestRetryStrategy;
+
+ /**
+ * 自定义KeepAlive策略
+ */
+ private ConnectionKeepAliveStrategy connectionKeepAliveStrategy;
+
+ private String httpProxyHost;
+ private int httpProxyPort;
+ private String httpProxyUsername;
+ private char[] httpProxyPassword;
+ /**
+ * 持有client对象,仅初始化一次,避免多service实例的时候造成重复初始化的问题
+ */
+ private CloseableHttpClient closeableHttpClient;
+
+ private DefaultHttpComponentsClientBuilder() {
+ }
+
+ public static DefaultHttpComponentsClientBuilder get() {
+ return SingletonHolder.INSTANCE;
+ }
+
+ @Override
+ public HttpComponentsClientBuilder httpProxyHost(String httpProxyHost) {
+ this.httpProxyHost = httpProxyHost;
+ return this;
+ }
+
+ @Override
+ public HttpComponentsClientBuilder httpProxyPort(int httpProxyPort) {
+ this.httpProxyPort = httpProxyPort;
+ return this;
+ }
+
+ @Override
+ public HttpComponentsClientBuilder httpProxyUsername(String httpProxyUsername) {
+ this.httpProxyUsername = httpProxyUsername;
+ return this;
+ }
+
+ @Override
+ public HttpComponentsClientBuilder httpProxyPassword(char[] httpProxyPassword) {
+ this.httpProxyPassword = httpProxyPassword;
+ return this;
+ }
+
+ @Override
+ public HttpComponentsClientBuilder httpRequestRetryStrategy(HttpRequestRetryStrategy httpRequestRetryStrategy) {
+ this.httpRequestRetryStrategy = httpRequestRetryStrategy;
+ return this;
+ }
+
+ @Override
+ public HttpComponentsClientBuilder keepAliveStrategy(ConnectionKeepAliveStrategy keepAliveStrategy) {
+ this.connectionKeepAliveStrategy = keepAliveStrategy;
+ return this;
+ }
+
+ private synchronized void prepare() {
+ if (prepared.get()) {
+ return;
+ }
+
+ SSLContext sslcontext;
+ try {
+ sslcontext = SSLContexts.custom()
+ .loadTrustMaterial(TrustAllStrategy.INSTANCE) // 忽略对服务器端证书的校验
+ .build();
+ } catch (NoSuchAlgorithmException | KeyManagementException | KeyStoreException e) {
+ log.error("构建 SSLContext 时发生异常!", e);
+ throw new RuntimeException(e);
+ }
+
+ PoolingHttpClientConnectionManager connManager = PoolingHttpClientConnectionManagerBuilder.create()
+ .setTlsSocketStrategy(new DefaultClientTlsStrategy(sslcontext, NoopHostnameVerifier.INSTANCE))
+ .setMaxConnTotal(this.maxTotalConn)
+ .setMaxConnPerRoute(this.maxConnPerHost)
+ .setDefaultSocketConfig(SocketConfig.custom()
+ .setSoTimeout(this.soTimeout, TimeUnit.MILLISECONDS)
+ .build())
+ .setDefaultConnectionConfig(ConnectionConfig.custom()
+ .setConnectTimeout(this.connectionTimeout, TimeUnit.MILLISECONDS)
+ .build())
+ .build();
+
+ HttpClientBuilder httpClientBuilder = HttpClients.custom()
+ .setConnectionManager(connManager)
+ .setConnectionManagerShared(true)
+ .setDefaultRequestConfig(RequestConfig.custom()
+ .setConnectionRequestTimeout(this.connectionRequestTimeout, TimeUnit.MILLISECONDS)
+ .build()
+ );
+
+ // 设置重试策略,没有则使用默认
+ httpClientBuilder.setRetryStrategy(ObjectUtils.defaultIfNull(httpRequestRetryStrategy, NoopRetryStrategy.INSTANCE));
+
+ // 设置KeepAliveStrategy,没有使用默认
+ if (connectionKeepAliveStrategy != null) {
+ httpClientBuilder.setKeepAliveStrategy(connectionKeepAliveStrategy);
+ }
+
+ if (StringUtils.isNotBlank(this.httpProxyHost) && StringUtils.isNotBlank(this.httpProxyUsername)) {
+ // 使用代理服务器 需要用户认证的代理服务器
+ BasicCredentialsProvider provider = new BasicCredentialsProvider();
+ provider.setCredentials(new AuthScope(this.httpProxyHost, this.httpProxyPort),
+ new UsernamePasswordCredentials(this.httpProxyUsername, this.httpProxyPassword));
+ httpClientBuilder.setDefaultCredentialsProvider(provider);
+ httpClientBuilder.setProxy(new HttpHost(this.httpProxyHost, this.httpProxyPort));
+ }
+
+ if (StringUtils.isNotBlank(this.userAgent)) {
+ httpClientBuilder.setUserAgent(this.userAgent);
+ }
+
+ //添加自定义的请求拦截器
+ requestInterceptors.forEach(httpClientBuilder::addRequestInterceptorFirst);
+
+ //添加自定义的响应拦截器
+ responseInterceptors.forEach(httpClientBuilder::addResponseInterceptorLast);
+
+ this.closeableHttpClient = httpClientBuilder.build();
+ prepared.set(true);
+ }
+
+ @Override
+ public CloseableHttpClient build() {
+ if (!prepared.get()) {
+ prepare();
+ }
+ return this.closeableHttpClient;
+ }
+
+ /**
+ * DefaultApacheHttpClientBuilder 改为单例模式,并持有唯一的CloseableHttpClient(仅首次调用创建)
+ */
+ private static class SingletonHolder {
+ private static final DefaultHttpComponentsClientBuilder INSTANCE = new DefaultHttpComponentsClientBuilder();
+ }
+}
diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/hc/HttpComponentsClientBuilder.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/hc/HttpComponentsClientBuilder.java
new file mode 100644
index 0000000000..66cd58e15f
--- /dev/null
+++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/hc/HttpComponentsClientBuilder.java
@@ -0,0 +1,51 @@
+package me.chanjar.weixin.common.util.http.hc;
+
+import me.chanjar.weixin.common.util.http.apache.ApacheHttpClientBuilder;
+import org.apache.hc.client5.http.ConnectionKeepAliveStrategy;
+import org.apache.hc.client5.http.HttpRequestRetryStrategy;
+import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
+
+/**
+ * httpclient build interface.
+ *
+ * @author altusea
+ */
+public interface HttpComponentsClientBuilder {
+
+ /**
+ * 构建httpclient实例.
+ *
+ * @return new instance of CloseableHttpClient
+ */
+ CloseableHttpClient build();
+
+ /**
+ * 代理服务器地址.
+ */
+ HttpComponentsClientBuilder httpProxyHost(String httpProxyHost);
+
+ /**
+ * 代理服务器端口.
+ */
+ HttpComponentsClientBuilder httpProxyPort(int httpProxyPort);
+
+ /**
+ * 代理服务器用户名.
+ */
+ HttpComponentsClientBuilder httpProxyUsername(String httpProxyUsername);
+
+ /**
+ * 代理服务器密码.
+ */
+ HttpComponentsClientBuilder httpProxyPassword(char[] httpProxyPassword);
+
+ /**
+ * 重试策略.
+ */
+ HttpComponentsClientBuilder httpRequestRetryStrategy(HttpRequestRetryStrategy httpRequestRetryStrategy);
+
+ /**
+ * 超时时间.
+ */
+ HttpComponentsClientBuilder keepAliveStrategy(ConnectionKeepAliveStrategy keepAliveStrategy);
+}
diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/hc/HttpComponentsMediaDownloadRequestExecutor.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/hc/HttpComponentsMediaDownloadRequestExecutor.java
new file mode 100644
index 0000000000..26fbed93f3
--- /dev/null
+++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/hc/HttpComponentsMediaDownloadRequestExecutor.java
@@ -0,0 +1,79 @@
+package me.chanjar.weixin.common.util.http.hc;
+
+import me.chanjar.weixin.common.enums.WxType;
+import me.chanjar.weixin.common.error.WxError;
+import me.chanjar.weixin.common.error.WxErrorException;
+import me.chanjar.weixin.common.util.fs.FileUtils;
+import me.chanjar.weixin.common.util.http.BaseMediaDownloadRequestExecutor;
+import me.chanjar.weixin.common.util.http.HttpResponseProxy;
+import me.chanjar.weixin.common.util.http.RequestHttp;
+import org.apache.commons.io.FilenameUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.hc.client5.http.ClientProtocolException;
+import org.apache.hc.client5.http.classic.methods.HttpGet;
+import org.apache.hc.client5.http.config.RequestConfig;
+import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
+import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse;
+import org.apache.hc.core5.http.ContentType;
+import org.apache.hc.core5.http.Header;
+import org.apache.hc.core5.http.HttpException;
+import org.apache.hc.core5.http.HttpHost;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * ApacheMediaDownloadRequestExecutor
+ *
+ * @author altusea
+ */
+public class HttpComponentsMediaDownloadRequestExecutor extends BaseMediaDownloadRequestExecutor {
+
+ public HttpComponentsMediaDownloadRequestExecutor(RequestHttp requestHttp, File tmpDirFile) {
+ super(requestHttp, tmpDirFile);
+ }
+
+ @Override
+ public File execute(String uri, String queryParam, WxType wxType) throws WxErrorException, IOException {
+ if (queryParam != null) {
+ if (uri.indexOf('?') == -1) {
+ uri += '?';
+ }
+ uri += uri.endsWith("?") ? queryParam : '&' + queryParam;
+ }
+
+ HttpGet httpGet = new HttpGet(uri);
+ if (requestHttp.getRequestHttpProxy() != null) {
+ RequestConfig config = RequestConfig.custom().setProxy(requestHttp.getRequestHttpProxy()).build();
+ httpGet.setConfig(config);
+ }
+
+ try (CloseableHttpResponse response = requestHttp.getRequestHttpClient().execute(httpGet);
+ InputStream inputStream = InputStreamResponseHandler.INSTANCE.handleResponse(response)) {
+ Header[] contentTypeHeader = response.getHeaders("Content-Type");
+ if (contentTypeHeader != null && contentTypeHeader.length > 0) {
+ if (contentTypeHeader[0].getValue().startsWith(ContentType.APPLICATION_JSON.getMimeType())) {
+ // application/json; encoding=utf-8 下载媒体文件出错
+ String responseContent = Utf8ResponseHandler.INSTANCE.handleResponse(response);
+ throw new WxErrorException(WxError.fromJson(responseContent, wxType));
+ }
+ }
+
+ String fileName = HttpResponseProxy.from(response).getFileName();
+ if (StringUtils.isBlank(fileName)) {
+ fileName = String.valueOf(System.currentTimeMillis());
+ }
+
+ String baseName = FilenameUtils.getBaseName(fileName);
+ if (StringUtils.isBlank(fileName) || baseName.length() < 3) {
+ baseName = String.valueOf(System.currentTimeMillis());
+ }
+
+ return FileUtils.createTmpFile(inputStream, baseName, FilenameUtils.getExtension(fileName), super.tmpDirFile);
+ } catch (HttpException httpException) {
+ throw new ClientProtocolException(httpException.getMessage(), httpException);
+ }
+ }
+
+}
diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/hc/HttpComponentsMediaInputStreamUploadRequestExecutor.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/hc/HttpComponentsMediaInputStreamUploadRequestExecutor.java
new file mode 100644
index 0000000000..4853b1572b
--- /dev/null
+++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/hc/HttpComponentsMediaInputStreamUploadRequestExecutor.java
@@ -0,0 +1,54 @@
+package me.chanjar.weixin.common.util.http.hc;
+
+import me.chanjar.weixin.common.bean.result.WxMediaUploadResult;
+import me.chanjar.weixin.common.enums.WxType;
+import me.chanjar.weixin.common.error.WxError;
+import me.chanjar.weixin.common.error.WxErrorException;
+import me.chanjar.weixin.common.util.http.InputStreamData;
+import me.chanjar.weixin.common.util.http.MediaInputStreamUploadRequestExecutor;
+import me.chanjar.weixin.common.util.http.RequestHttp;
+import org.apache.hc.client5.http.classic.methods.HttpPost;
+import org.apache.hc.client5.http.config.RequestConfig;
+import org.apache.hc.client5.http.entity.mime.HttpMultipartMode;
+import org.apache.hc.client5.http.entity.mime.MultipartEntityBuilder;
+import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
+import org.apache.hc.core5.http.ContentType;
+import org.apache.hc.core5.http.HttpEntity;
+import org.apache.hc.core5.http.HttpHost;
+
+import java.io.IOException;
+
+/**
+ * 文件输入流上传.
+ *
+ * @author altusea
+ */
+public class HttpComponentsMediaInputStreamUploadRequestExecutor extends MediaInputStreamUploadRequestExecutor {
+
+ public HttpComponentsMediaInputStreamUploadRequestExecutor(RequestHttp requestHttp) {
+ super(requestHttp);
+ }
+
+ @Override
+ public WxMediaUploadResult execute(String uri, InputStreamData data, WxType wxType) throws WxErrorException, IOException {
+ HttpPost httpPost = new HttpPost(uri);
+ if (requestHttp.getRequestHttpProxy() != null) {
+ RequestConfig config = RequestConfig.custom().setProxy(requestHttp.getRequestHttpProxy()).build();
+ httpPost.setConfig(config);
+ }
+ if (data != null) {
+ HttpEntity entity = MultipartEntityBuilder
+ .create()
+ .addBinaryBody("media", data.getInputStream(), ContentType.DEFAULT_BINARY, data.getFilename())
+ .setMode(HttpMultipartMode.EXTENDED)
+ .build();
+ httpPost.setEntity(entity);
+ }
+ String responseContent = requestHttp.getRequestHttpClient().execute(httpPost, Utf8ResponseHandler.INSTANCE);
+ WxError error = WxError.fromJson(responseContent, wxType);
+ if (error.getErrorCode() != 0) {
+ throw new WxErrorException(error);
+ }
+ return WxMediaUploadResult.fromJson(responseContent);
+ }
+}
diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/hc/HttpComponentsMediaUploadRequestExecutor.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/hc/HttpComponentsMediaUploadRequestExecutor.java
new file mode 100644
index 0000000000..e65d855d52
--- /dev/null
+++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/hc/HttpComponentsMediaUploadRequestExecutor.java
@@ -0,0 +1,53 @@
+package me.chanjar.weixin.common.util.http.hc;
+
+import me.chanjar.weixin.common.bean.result.WxMediaUploadResult;
+import me.chanjar.weixin.common.enums.WxType;
+import me.chanjar.weixin.common.error.WxError;
+import me.chanjar.weixin.common.error.WxErrorException;
+import me.chanjar.weixin.common.util.http.MediaUploadRequestExecutor;
+import me.chanjar.weixin.common.util.http.RequestHttp;
+import org.apache.hc.client5.http.classic.methods.HttpPost;
+import org.apache.hc.client5.http.config.RequestConfig;
+import org.apache.hc.client5.http.entity.mime.HttpMultipartMode;
+import org.apache.hc.client5.http.entity.mime.MultipartEntityBuilder;
+import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
+import org.apache.hc.core5.http.HttpEntity;
+import org.apache.hc.core5.http.HttpHost;
+
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * ApacheMediaUploadRequestExecutor
+ *
+ * @author altusea
+ */
+public class HttpComponentsMediaUploadRequestExecutor extends MediaUploadRequestExecutor {
+
+ public HttpComponentsMediaUploadRequestExecutor(RequestHttp requestHttp) {
+ super(requestHttp);
+ }
+
+ @Override
+ public WxMediaUploadResult execute(String uri, File file, WxType wxType) throws WxErrorException, IOException {
+ HttpPost httpPost = new HttpPost(uri);
+ if (requestHttp.getRequestHttpProxy() != null) {
+ RequestConfig config = RequestConfig.custom().setProxy(requestHttp.getRequestHttpProxy()).build();
+ httpPost.setConfig(config);
+ }
+ if (file != null) {
+ HttpEntity entity = MultipartEntityBuilder
+ .create()
+ .addBinaryBody("media", file)
+ .setMode(HttpMultipartMode.EXTENDED)
+ .build();
+ httpPost.setEntity(entity);
+ }
+ String responseContent = requestHttp.getRequestHttpClient().execute(httpPost, Utf8ResponseHandler.INSTANCE);
+ WxError error = WxError.fromJson(responseContent, wxType);
+ if (error.getErrorCode() != 0) {
+ throw new WxErrorException(error);
+ }
+ return WxMediaUploadResult.fromJson(responseContent);
+ }
+}
diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/hc/HttpComponentsMinishopMediaUploadRequestCustomizeExecutor.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/hc/HttpComponentsMinishopMediaUploadRequestCustomizeExecutor.java
new file mode 100644
index 0000000000..711f538309
--- /dev/null
+++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/hc/HttpComponentsMinishopMediaUploadRequestCustomizeExecutor.java
@@ -0,0 +1,71 @@
+package me.chanjar.weixin.common.util.http.hc;
+
+import lombok.extern.slf4j.Slf4j;
+import me.chanjar.weixin.common.bean.result.WxMinishopImageUploadCustomizeResult;
+import me.chanjar.weixin.common.enums.WxType;
+import me.chanjar.weixin.common.error.WxError;
+import me.chanjar.weixin.common.error.WxErrorException;
+import me.chanjar.weixin.common.util.http.MinishopUploadRequestCustomizeExecutor;
+import me.chanjar.weixin.common.util.http.RequestHttp;
+import org.apache.hc.client5.http.classic.methods.HttpPost;
+import org.apache.hc.client5.http.config.RequestConfig;
+import org.apache.hc.client5.http.entity.mime.HttpMultipartMode;
+import org.apache.hc.client5.http.entity.mime.MultipartEntityBuilder;
+import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
+import org.apache.hc.core5.http.HttpEntity;
+import org.apache.hc.core5.http.HttpHost;
+
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * ApacheMinishopMediaUploadRequestCustomizeExecutor
+ *
+ * @author altusea
+ */
+@Slf4j
+public class HttpComponentsMinishopMediaUploadRequestCustomizeExecutor extends MinishopUploadRequestCustomizeExecutor {
+
+ public HttpComponentsMinishopMediaUploadRequestCustomizeExecutor(RequestHttp requestHttp, String respType, String imgUrl) {
+ super(requestHttp, respType, imgUrl);
+ }
+
+ @Override
+ public WxMinishopImageUploadCustomizeResult execute(String uri, File file, WxType wxType) throws WxErrorException, IOException {
+ HttpPost httpPost = new HttpPost(uri);
+ if (requestHttp.getRequestHttpProxy() != null) {
+ RequestConfig config = RequestConfig.custom().setProxy(requestHttp.getRequestHttpProxy()).build();
+ httpPost.setConfig(config);
+ }
+ if (this.uploadType.equals("0")) {
+ if (file == null) {
+ throw new WxErrorException("上传文件为空");
+ }
+ HttpEntity entity = MultipartEntityBuilder
+ .create()
+ .addBinaryBody("media", file)
+ .addTextBody("resp_type", this.respType)
+ .addTextBody("upload_type", this.uploadType)
+ .setMode(HttpMultipartMode.EXTENDED)
+ .build();
+ httpPost.setEntity(entity);
+ }
+ else {
+ HttpEntity entity = MultipartEntityBuilder
+ .create()
+ .addTextBody("resp_type", this.respType)
+ .addTextBody("upload_type", this.uploadType)
+ .addTextBody("img_url", this.imgUrl)
+ .setMode(org.apache.hc.client5.http.entity.mime.HttpMultipartMode.EXTENDED)
+ .build();
+ httpPost.setEntity(entity);
+ }
+ String responseContent = requestHttp.getRequestHttpClient().execute(httpPost, Utf8ResponseHandler.INSTANCE);
+ WxError error = WxError.fromJson(responseContent, wxType);
+ if (error.getErrorCode() != 0) {
+ throw new WxErrorException(error);
+ }
+ log.info("responseContent: {}", responseContent);
+ return WxMinishopImageUploadCustomizeResult.fromJson(responseContent);
+ }
+}
diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/hc/HttpComponentsMinishopMediaUploadRequestExecutor.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/hc/HttpComponentsMinishopMediaUploadRequestExecutor.java
new file mode 100644
index 0000000000..72c1f2765f
--- /dev/null
+++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/hc/HttpComponentsMinishopMediaUploadRequestExecutor.java
@@ -0,0 +1,56 @@
+package me.chanjar.weixin.common.util.http.hc;
+
+import lombok.extern.slf4j.Slf4j;
+import me.chanjar.weixin.common.bean.result.WxMinishopImageUploadResult;
+import me.chanjar.weixin.common.enums.WxType;
+import me.chanjar.weixin.common.error.WxError;
+import me.chanjar.weixin.common.error.WxErrorException;
+import me.chanjar.weixin.common.util.http.MinishopUploadRequestExecutor;
+import me.chanjar.weixin.common.util.http.RequestHttp;
+import org.apache.hc.client5.http.classic.methods.HttpPost;
+import org.apache.hc.client5.http.config.RequestConfig;
+import org.apache.hc.client5.http.entity.mime.HttpMultipartMode;
+import org.apache.hc.client5.http.entity.mime.MultipartEntityBuilder;
+import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
+import org.apache.hc.core5.http.HttpEntity;
+import org.apache.hc.core5.http.HttpHost;
+
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * ApacheMinishopMediaUploadRequestExecutor
+ *
+ * @author altusea
+ */
+@Slf4j
+public class HttpComponentsMinishopMediaUploadRequestExecutor extends MinishopUploadRequestExecutor {
+
+ public HttpComponentsMinishopMediaUploadRequestExecutor(RequestHttp requestHttp) {
+ super(requestHttp);
+ }
+
+ @Override
+ public WxMinishopImageUploadResult execute(String uri, File file, WxType wxType) throws WxErrorException, IOException {
+ HttpPost httpPost = new HttpPost(uri);
+ if (requestHttp.getRequestHttpProxy() != null) {
+ RequestConfig config = RequestConfig.custom().setProxy(requestHttp.getRequestHttpProxy()).build();
+ httpPost.setConfig(config);
+ }
+ if (file != null) {
+ HttpEntity entity = MultipartEntityBuilder
+ .create()
+ .addBinaryBody("media", file)
+ .setMode(HttpMultipartMode.EXTENDED)
+ .build();
+ httpPost.setEntity(entity);
+ }
+ String responseContent = requestHttp.getRequestHttpClient().execute(httpPost, Utf8ResponseHandler.INSTANCE);
+ WxError error = WxError.fromJson(responseContent, wxType);
+ if (error.getErrorCode() != 0) {
+ throw new WxErrorException(error);
+ }
+ log.info("responseContent: {}", responseContent);
+ return WxMinishopImageUploadResult.fromJson(responseContent);
+ }
+}
diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/hc/HttpComponentsResponseProxy.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/hc/HttpComponentsResponseProxy.java
new file mode 100644
index 0000000000..d55ff0735f
--- /dev/null
+++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/hc/HttpComponentsResponseProxy.java
@@ -0,0 +1,25 @@
+package me.chanjar.weixin.common.util.http.hc;
+
+import me.chanjar.weixin.common.error.WxErrorException;
+import me.chanjar.weixin.common.util.http.HttpResponseProxy;
+import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse;
+import org.apache.hc.core5.http.Header;
+
+public class HttpComponentsResponseProxy implements HttpResponseProxy {
+
+ private final CloseableHttpResponse response;
+
+ public HttpComponentsResponseProxy(CloseableHttpResponse closeableHttpResponse) {
+ this.response = closeableHttpResponse;
+ }
+
+ @Override
+ public String getFileName() throws WxErrorException {
+ Header[] contentDispositionHeader = this.response.getHeaders("Content-disposition");
+ if (contentDispositionHeader == null || contentDispositionHeader.length == 0) {
+ throw new WxErrorException("无法获取到文件名,Content-disposition为空");
+ }
+
+ return HttpResponseProxy.extractFileNameFromContentString(contentDispositionHeader[0].getValue());
+ }
+}
diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/hc/HttpComponentsSimpleGetRequestExecutor.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/hc/HttpComponentsSimpleGetRequestExecutor.java
new file mode 100644
index 0000000000..0d212fe7e2
--- /dev/null
+++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/hc/HttpComponentsSimpleGetRequestExecutor.java
@@ -0,0 +1,43 @@
+package me.chanjar.weixin.common.util.http.hc;
+
+import me.chanjar.weixin.common.enums.WxType;
+import me.chanjar.weixin.common.error.WxErrorException;
+import me.chanjar.weixin.common.util.http.RequestHttp;
+import me.chanjar.weixin.common.util.http.SimpleGetRequestExecutor;
+import org.apache.hc.client5.http.classic.methods.HttpGet;
+import org.apache.hc.client5.http.config.RequestConfig;
+import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
+import org.apache.hc.core5.http.HttpHost;
+
+import java.io.IOException;
+
+/**
+ * ApacheSimpleGetRequestExecutor
+ *
+ * @author altusea
+ */
+public class HttpComponentsSimpleGetRequestExecutor extends SimpleGetRequestExecutor {
+
+ public HttpComponentsSimpleGetRequestExecutor(RequestHttp requestHttp) {
+ super(requestHttp);
+ }
+
+ @Override
+ public String execute(String uri, String queryParam, WxType wxType) throws WxErrorException, IOException {
+ if (queryParam != null) {
+ if (uri.indexOf('?') == -1) {
+ uri += '?';
+ }
+ uri += uri.endsWith("?") ? queryParam : '&' + queryParam;
+ }
+ HttpGet httpGet = new HttpGet(uri);
+ if (requestHttp.getRequestHttpProxy() != null) {
+ RequestConfig config = RequestConfig.custom().setProxy(requestHttp.getRequestHttpProxy()).build();
+ httpGet.setConfig(config);
+ }
+
+ String responseContent = requestHttp.getRequestHttpClient().execute(httpGet, Utf8ResponseHandler.INSTANCE);
+ return handleResponse(wxType, responseContent);
+ }
+
+}
diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/hc/HttpComponentsSimplePostRequestExecutor.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/hc/HttpComponentsSimplePostRequestExecutor.java
new file mode 100644
index 0000000000..45d2ca9f6e
--- /dev/null
+++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/hc/HttpComponentsSimplePostRequestExecutor.java
@@ -0,0 +1,45 @@
+package me.chanjar.weixin.common.util.http.hc;
+
+import me.chanjar.weixin.common.enums.WxType;
+import me.chanjar.weixin.common.error.WxErrorException;
+import me.chanjar.weixin.common.util.http.RequestHttp;
+import me.chanjar.weixin.common.util.http.SimplePostRequestExecutor;
+import org.apache.hc.client5.http.classic.methods.HttpPost;
+import org.apache.hc.client5.http.config.RequestConfig;
+import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
+import org.apache.hc.core5.http.ContentType;
+import org.apache.hc.core5.http.HttpHost;
+import org.apache.hc.core5.http.io.entity.StringEntity;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+
+/**
+ * ApacheSimplePostRequestExecutor
+ *
+ * @author altusea
+ */
+public class HttpComponentsSimplePostRequestExecutor extends SimplePostRequestExecutor {
+
+ public HttpComponentsSimplePostRequestExecutor(RequestHttp requestHttp) {
+ super(requestHttp);
+ }
+
+ @Override
+ public String execute(String uri, String postEntity, WxType wxType) throws WxErrorException, IOException {
+ HttpPost httpPost = new HttpPost(uri);
+ if (requestHttp.getRequestHttpProxy() != null) {
+ RequestConfig config = RequestConfig.custom().setProxy(requestHttp.getRequestHttpProxy()).build();
+ httpPost.setConfig(config);
+ }
+
+ if (postEntity != null) {
+ StringEntity entity = new StringEntity(postEntity, ContentType.APPLICATION_JSON.withCharset(StandardCharsets.UTF_8));
+ httpPost.setEntity(entity);
+ }
+
+ String responseContent = requestHttp.getRequestHttpClient().execute(httpPost, Utf8ResponseHandler.INSTANCE);
+ return this.handleResponse(wxType, responseContent);
+ }
+
+}
diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/hc/InputStreamResponseHandler.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/hc/InputStreamResponseHandler.java
new file mode 100644
index 0000000000..27308151f7
--- /dev/null
+++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/hc/InputStreamResponseHandler.java
@@ -0,0 +1,23 @@
+package me.chanjar.weixin.common.util.http.hc;
+
+import org.apache.hc.client5.http.impl.classic.AbstractHttpClientResponseHandler;
+import org.apache.hc.core5.http.HttpEntity;
+import org.apache.hc.core5.http.io.HttpClientResponseHandler;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * InputStreamResponseHandler
+ *
+ * @author altusea
+ */
+public class InputStreamResponseHandler extends AbstractHttpClientResponseHandler {
+
+ public static final HttpClientResponseHandler INSTANCE = new InputStreamResponseHandler();
+
+ @Override
+ public InputStream handleEntity(HttpEntity entity) throws IOException {
+ return entity.getContent();
+ }
+}
diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/hc/NoopRetryStrategy.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/hc/NoopRetryStrategy.java
new file mode 100644
index 0000000000..9b4e3bc384
--- /dev/null
+++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/hc/NoopRetryStrategy.java
@@ -0,0 +1,34 @@
+package me.chanjar.weixin.common.util.http.hc;
+
+import org.apache.hc.client5.http.HttpRequestRetryStrategy;
+import org.apache.hc.core5.http.HttpRequest;
+import org.apache.hc.core5.http.HttpResponse;
+import org.apache.hc.core5.http.protocol.HttpContext;
+import org.apache.hc.core5.util.TimeValue;
+
+import java.io.IOException;
+
+/**
+ * NoopRetryStrategy
+ *
+ * @author altusea
+ */
+public class NoopRetryStrategy implements HttpRequestRetryStrategy {
+
+ public static final HttpRequestRetryStrategy INSTANCE = new NoopRetryStrategy();
+
+ @Override
+ public boolean retryRequest(HttpRequest request, IOException exception, int execCount, HttpContext context) {
+ return false;
+ }
+
+ @Override
+ public boolean retryRequest(HttpResponse response, int execCount, HttpContext context) {
+ return false;
+ }
+
+ @Override
+ public TimeValue getRetryInterval(HttpResponse response, int execCount, HttpContext context) {
+ return TimeValue.ZERO_MILLISECONDS;
+ }
+}
diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/hc/Utf8ResponseHandler.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/hc/Utf8ResponseHandler.java
new file mode 100644
index 0000000000..81699ef57b
--- /dev/null
+++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/hc/Utf8ResponseHandler.java
@@ -0,0 +1,30 @@
+package me.chanjar.weixin.common.util.http.hc;
+
+import org.apache.hc.client5.http.ClientProtocolException;
+import org.apache.hc.client5.http.impl.classic.AbstractHttpClientResponseHandler;
+import org.apache.hc.core5.http.HttpEntity;
+import org.apache.hc.core5.http.ParseException;
+import org.apache.hc.core5.http.io.HttpClientResponseHandler;
+import org.apache.hc.core5.http.io.entity.EntityUtils;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+
+/**
+ * Utf8ResponseHandler
+ *
+ * @author altusea
+ */
+public class Utf8ResponseHandler extends AbstractHttpClientResponseHandler {
+
+ public static final HttpClientResponseHandler INSTANCE = new Utf8ResponseHandler();
+
+ @Override
+ public String handleEntity(HttpEntity entity) throws IOException {
+ try {
+ return EntityUtils.toString(entity, StandardCharsets.UTF_8);
+ } catch (final ParseException ex) {
+ throw new ClientProtocolException(ex);
+ }
+ }
+}
diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/jodd/JoddHttpMediaDownloadRequestExecutor.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/jodd/JoddHttpMediaDownloadRequestExecutor.java
index 5d09ee7e1c..bc2fbc17f3 100644
--- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/jodd/JoddHttpMediaDownloadRequestExecutor.java
+++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/jodd/JoddHttpMediaDownloadRequestExecutor.java
@@ -55,7 +55,7 @@ public File execute(String uri, String queryParam, WxType wxType) throws WxError
throw new WxErrorException(WxError.fromJson(response.bodyText(), wxType));
}
- String fileName = new HttpResponseProxy(response).getFileName();
+ String fileName = HttpResponseProxy.from(response).getFileName();
if (StringUtils.isBlank(fileName)) {
return null;
}
diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/jodd/JoddHttpResponseProxy.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/jodd/JoddHttpResponseProxy.java
new file mode 100644
index 0000000000..1bda38a497
--- /dev/null
+++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/jodd/JoddHttpResponseProxy.java
@@ -0,0 +1,20 @@
+package me.chanjar.weixin.common.util.http.jodd;
+
+import jodd.http.HttpResponse;
+import me.chanjar.weixin.common.error.WxErrorException;
+import me.chanjar.weixin.common.util.http.HttpResponseProxy;
+
+public class JoddHttpResponseProxy implements HttpResponseProxy {
+
+ private final HttpResponse response;
+
+ public JoddHttpResponseProxy(HttpResponse httpResponse) {
+ this.response = httpResponse;
+ }
+
+ @Override
+ public String getFileName() throws WxErrorException {
+ String content = response.header("Content-disposition");
+ return HttpResponseProxy.extractFileNameFromContentString(content);
+ }
+}
diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/okhttp/OkHttpMediaDownloadRequestExecutor.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/okhttp/OkHttpMediaDownloadRequestExecutor.java
index 0e9d15f43d..0610d3f51c 100644
--- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/okhttp/OkHttpMediaDownloadRequestExecutor.java
+++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/okhttp/OkHttpMediaDownloadRequestExecutor.java
@@ -51,7 +51,7 @@ public File execute(String uri, String queryParam, WxType wxType) throws WxError
throw new WxErrorException(WxError.fromJson(response.body().string(), wxType));
}
- String fileName = new HttpResponseProxy(response).getFileName();
+ String fileName = HttpResponseProxy.from(response).getFileName();
if (StringUtils.isBlank(fileName)) {
return null;
}
diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/okhttp/OkHttpResponseProxy.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/okhttp/OkHttpResponseProxy.java
new file mode 100644
index 0000000000..95c290735c
--- /dev/null
+++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/okhttp/OkHttpResponseProxy.java
@@ -0,0 +1,20 @@
+package me.chanjar.weixin.common.util.http.okhttp;
+
+import me.chanjar.weixin.common.error.WxErrorException;
+import me.chanjar.weixin.common.util.http.HttpResponseProxy;
+import okhttp3.Response;
+
+public class OkHttpResponseProxy implements HttpResponseProxy {
+
+ private final Response response;
+
+ public OkHttpResponseProxy(Response response) {
+ this.response = response;
+ }
+
+ @Override
+ public String getFileName() throws WxErrorException {
+ String content = this.response.header("Content-disposition");
+ return HttpResponseProxy.extractFileNameFromContentString(content);
+ }
+}
diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/json/GsonParser.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/json/GsonParser.java
index 061a3cb2ee..caa07d0eaf 100644
--- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/json/GsonParser.java
+++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/json/GsonParser.java
@@ -10,17 +10,16 @@
* @author niefy
*/
public class GsonParser {
- private static final JsonParser JSON_PARSER = new JsonParser();
public static JsonObject parse(String json) {
- return JSON_PARSER.parse(json).getAsJsonObject();
+ return new JsonParser().parse(json).getAsJsonObject();
}
public static JsonObject parse(Reader json) {
- return JSON_PARSER.parse(json).getAsJsonObject();
+ return new JsonParser().parse(json).getAsJsonObject();
}
public static JsonObject parse(JsonReader json) {
- return JSON_PARSER.parse(json).getAsJsonObject();
+ return new JsonParser().parse(json).getAsJsonObject();
}
}
diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/json/WxGsonBuilder.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/json/WxGsonBuilder.java
index ff260c16fb..8f3dafe48a 100644
--- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/json/WxGsonBuilder.java
+++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/json/WxGsonBuilder.java
@@ -1,5 +1,7 @@
package me.chanjar.weixin.common.util.json;
+import com.google.gson.ExclusionStrategy;
+import com.google.gson.FieldAttributes;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import me.chanjar.weixin.common.bean.WxAccessToken;
@@ -7,6 +9,9 @@
import me.chanjar.weixin.common.bean.menu.WxMenu;
import me.chanjar.weixin.common.error.WxError;
import me.chanjar.weixin.common.bean.result.WxMediaUploadResult;
+import me.chanjar.weixin.common.util.http.apache.ApacheHttpClientBuilder;
+
+import java.io.File;
import java.util.Objects;
/**
@@ -25,8 +30,24 @@ public class WxGsonBuilder {
INSTANCE.registerTypeAdapter(WxMediaUploadResult.class, new WxMediaUploadResultAdapter());
INSTANCE.registerTypeAdapter(WxNetCheckResult.class, new WxNetCheckResultGsonAdapter());
+ INSTANCE.setExclusionStrategies(new ExclusionStrategy() {
+ @Override
+ public boolean shouldSkipField(FieldAttributes fieldAttributes) {
+ return false;
+ }
+
+ @Override
+ public boolean shouldSkipClass(Class> aClass) {
+ return aClass == File.class || aClass == ApacheHttpClientBuilder.class;
+ }
+ });
}
+ /**
+ * 创建Gson实例
+ *
+ * @return Gson实例
+ */
public static Gson create() {
if (Objects.isNull(GSON_INSTANCE)) {
synchronized (INSTANCE) {
diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/json/WxMenuGsonAdapter.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/json/WxMenuGsonAdapter.java
index 6e12aa5022..5e7f9b41d9 100644
--- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/json/WxMenuGsonAdapter.java
+++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/json/WxMenuGsonAdapter.java
@@ -1,11 +1,3 @@
-/*
- * 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.common.util.json;
import com.google.gson.*;
@@ -14,6 +6,7 @@
import me.chanjar.weixin.common.bean.menu.WxMenuRule;
import java.lang.reflect.Type;
+import java.util.Optional;
/**
@@ -21,96 +14,111 @@
*/
public class WxMenuGsonAdapter implements JsonSerializer, JsonDeserializer {
+ // JSON字段常量定义
+ private static final String FIELD_BUTTON = "button";
+ private static final String FIELD_MATCH_RULE = "matchrule";
+ private static final String FIELD_SUB_BUTTON = "sub_button";
+ private static final String FIELD_MENU = "menu";
+
+ // 菜单按钮字段常量
+ private static final String FIELD_TYPE = "type";
+ private static final String FIELD_NAME = "name";
+ private static final String FIELD_KEY = "key";
+ private static final String FIELD_URL = "url";
+ private static final String FIELD_MEDIA_ID = "media_id";
+ private static final String FIELD_ARTICLE_ID = "article_id";
+ private static final String FIELD_APP_ID = "appid";
+ private static final String FIELD_PAGE_PATH = "pagepath";
+
+ // 菜单规则字段常量
+ private static final String FIELD_TAG_ID = "tag_id";
+ private static final String FIELD_SEX = "sex";
+ private static final String FIELD_COUNTRY = "country";
+ private static final String FIELD_PROVINCE = "province";
+ private static final String FIELD_CITY = "city";
+ private static final String FIELD_CLIENT_PLATFORM_TYPE = "client_platform_type";
+ private static final String FIELD_LANGUAGE = "language";
+
@Override
public JsonElement serialize(WxMenu menu, Type typeOfSrc, JsonSerializationContext context) {
JsonObject json = new JsonObject();
-
JsonArray buttonArray = new JsonArray();
- for (WxMenuButton button : menu.getButtons()) {
- JsonObject buttonJson = convertToJson(button);
- buttonArray.add(buttonJson);
- }
- json.add("button", buttonArray);
-
+ Optional.ofNullable(menu.getButtons())
+ .ifPresent(buttons -> buttons.stream()
+ .map(this::convertToJson)
+ .forEach(buttonArray::add));
+ json.add(FIELD_BUTTON, buttonArray);
if (menu.getMatchRule() != null) {
- json.add("matchrule", convertToJson(menu.getMatchRule()));
+ json.add(FIELD_MATCH_RULE, convertToJson(menu.getMatchRule()));
}
-
return json;
}
protected JsonObject convertToJson(WxMenuButton button) {
JsonObject buttonJson = new JsonObject();
- buttonJson.addProperty("type", button.getType());
- buttonJson.addProperty("name", button.getName());
- buttonJson.addProperty("key", button.getKey());
- buttonJson.addProperty("url", button.getUrl());
- buttonJson.addProperty("media_id", button.getMediaId());
- buttonJson.addProperty("article_id", button.getArticleId());
- buttonJson.addProperty("appid", button.getAppId());
- buttonJson.addProperty("pagepath", button.getPagePath());
+ addPropertyIfNotNull(buttonJson, FIELD_TYPE, button.getType());
+ addPropertyIfNotNull(buttonJson, FIELD_NAME, button.getName());
+ addPropertyIfNotNull(buttonJson, FIELD_KEY, button.getKey());
+ addPropertyIfNotNull(buttonJson, FIELD_URL, button.getUrl());
+ addPropertyIfNotNull(buttonJson, FIELD_MEDIA_ID, button.getMediaId());
+ addPropertyIfNotNull(buttonJson, FIELD_ARTICLE_ID, button.getArticleId());
+ addPropertyIfNotNull(buttonJson, FIELD_APP_ID, button.getAppId());
+ addPropertyIfNotNull(buttonJson, FIELD_PAGE_PATH, button.getPagePath());
if (button.getSubButtons() != null && !button.getSubButtons().isEmpty()) {
JsonArray buttonArray = new JsonArray();
- for (WxMenuButton sub_button : button.getSubButtons()) {
- buttonArray.add(convertToJson(sub_button));
- }
- buttonJson.add("sub_button", buttonArray);
+ button.getSubButtons().stream()
+ .map(this::convertToJson)
+ .forEach(buttonArray::add);
+ buttonJson.add(FIELD_SUB_BUTTON, buttonArray);
}
return buttonJson;
}
protected JsonObject convertToJson(WxMenuRule menuRule) {
JsonObject matchRule = new JsonObject();
- matchRule.addProperty("tag_id", menuRule.getTagId());
- matchRule.addProperty("sex", menuRule.getSex());
- matchRule.addProperty("country", menuRule.getCountry());
- matchRule.addProperty("province", menuRule.getProvince());
- matchRule.addProperty("city", menuRule.getCity());
- matchRule.addProperty("client_platform_type", menuRule.getClientPlatformType());
- matchRule.addProperty("language", menuRule.getLanguage());
+ addPropertyIfNotNull(matchRule, FIELD_TAG_ID, menuRule.getTagId());
+ addPropertyIfNotNull(matchRule, FIELD_SEX, menuRule.getSex());
+ addPropertyIfNotNull(matchRule, FIELD_COUNTRY, menuRule.getCountry());
+ addPropertyIfNotNull(matchRule, FIELD_PROVINCE, menuRule.getProvince());
+ addPropertyIfNotNull(matchRule, FIELD_CITY, menuRule.getCity());
+ addPropertyIfNotNull(matchRule, FIELD_CLIENT_PLATFORM_TYPE, menuRule.getClientPlatformType());
+ addPropertyIfNotNull(matchRule, FIELD_LANGUAGE, menuRule.getLanguage());
return matchRule;
}
- @Deprecated
- private WxMenuRule convertToRule(JsonObject json) {
- WxMenuRule menuRule = new WxMenuRule();
- //变态的微信接口,这里居然反人类的使用和序列化时不一样的名字
- //menuRule.setTagId(GsonHelper.getString(json,"tag_id"));
- menuRule.setTagId(GsonHelper.getString(json, "group_id"));
- menuRule.setSex(GsonHelper.getString(json, "sex"));
- menuRule.setCountry(GsonHelper.getString(json, "country"));
- menuRule.setProvince(GsonHelper.getString(json, "province"));
- menuRule.setCity(GsonHelper.getString(json, "city"));
- menuRule.setClientPlatformType(GsonHelper.getString(json, "client_platform_type"));
- menuRule.setLanguage(GsonHelper.getString(json, "language"));
- return menuRule;
+ private void addPropertyIfNotNull(JsonObject obj, String key, String value) {
+ if (value != null) {
+ obj.addProperty(key, value);
+ }
}
@Override
public WxMenu deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
- /*
- * 操蛋的微信
- * 创建菜单时是 { button : ... }
- * 查询菜单时是 { menu : { button : ... } }
- * 现在企业号升级为企业微信后,没有此问题,因此需要单独处理
- */
- JsonArray buttonsJson = json.getAsJsonObject().get("menu").getAsJsonObject().get("button").getAsJsonArray();
- return this.buildMenuFromJson(buttonsJson);
+ JsonObject root = json.getAsJsonObject();
+ JsonArray buttonsJson = null;
+ if (root.has(FIELD_MENU)) {
+ JsonObject menuObj = root.getAsJsonObject(FIELD_MENU);
+ buttonsJson = menuObj.getAsJsonArray(FIELD_BUTTON);
+ } else if (root.has(FIELD_BUTTON)) {
+ buttonsJson = root.getAsJsonArray(FIELD_BUTTON);
+ }
+ if (buttonsJson == null) {
+ throw new JsonParseException("No button array found in menu JSON");
+ }
+ return buildMenuFromJson(buttonsJson);
}
protected WxMenu buildMenuFromJson(JsonArray buttonsJson) {
WxMenu menu = new WxMenu();
- for (int i = 0; i < buttonsJson.size(); i++) {
- JsonObject buttonJson = buttonsJson.get(i).getAsJsonObject();
+ for (JsonElement btnElem : buttonsJson) {
+ JsonObject buttonJson = btnElem.getAsJsonObject();
WxMenuButton button = convertFromJson(buttonJson);
menu.getButtons().add(button);
- if (buttonJson.get("sub_button") == null || buttonJson.get("sub_button").isJsonNull()) {
- continue;
- }
- JsonArray sub_buttonsJson = buttonJson.get("sub_button").getAsJsonArray();
- for (int j = 0; j < sub_buttonsJson.size(); j++) {
- JsonObject sub_buttonJson = sub_buttonsJson.get(j).getAsJsonObject();
- button.getSubButtons().add(convertFromJson(sub_buttonJson));
+ if (buttonJson.has(FIELD_SUB_BUTTON) && buttonJson.get(FIELD_SUB_BUTTON).isJsonArray()) {
+ JsonArray sub_buttonsJson = buttonJson.getAsJsonArray(FIELD_SUB_BUTTON);
+ for (JsonElement subBtnElem : sub_buttonsJson) {
+ button.getSubButtons().add(convertFromJson(subBtnElem.getAsJsonObject()));
+ }
}
}
return menu;
@@ -118,14 +126,14 @@ protected WxMenu buildMenuFromJson(JsonArray buttonsJson) {
protected WxMenuButton convertFromJson(JsonObject json) {
WxMenuButton button = new WxMenuButton();
- button.setName(GsonHelper.getString(json, "name"));
- button.setKey(GsonHelper.getString(json, "key"));
- button.setUrl(GsonHelper.getString(json, "url"));
- button.setType(GsonHelper.getString(json, "type"));
- button.setMediaId(GsonHelper.getString(json, "media_id"));
- button.setArticleId(GsonHelper.getString(json, "article_id"));
- button.setAppId(GsonHelper.getString(json, "appid"));
- button.setPagePath(GsonHelper.getString(json, "pagepath"));
+ button.setName(GsonHelper.getString(json, FIELD_NAME));
+ button.setKey(GsonHelper.getString(json, FIELD_KEY));
+ button.setUrl(GsonHelper.getString(json, FIELD_URL));
+ button.setType(GsonHelper.getString(json, FIELD_TYPE));
+ button.setMediaId(GsonHelper.getString(json, FIELD_MEDIA_ID));
+ button.setArticleId(GsonHelper.getString(json, FIELD_ARTICLE_ID));
+ button.setAppId(GsonHelper.getString(json, FIELD_APP_ID));
+ button.setPagePath(GsonHelper.getString(json, FIELD_PAGE_PATH));
return button;
}
diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/locks/RedisTemplateSimpleDistributedLock.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/locks/RedisTemplateSimpleDistributedLock.java
index b2d2481efe..3f5ce4d692 100644
--- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/locks/RedisTemplateSimpleDistributedLock.java
+++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/locks/RedisTemplateSimpleDistributedLock.java
@@ -1,16 +1,11 @@
package me.chanjar.weixin.common.util.locks;
import lombok.Getter;
-import org.springframework.data.redis.connection.RedisStringCommands;
-import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.script.RedisScript;
-import org.springframework.data.redis.core.types.Expiration;
-import java.nio.charset.StandardCharsets;
import java.util.Collections;
-import java.util.List;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
@@ -70,15 +65,16 @@ public boolean tryLock() {
value = UUID.randomUUID().toString();
valueThreadLocal.set(value);
}
- final byte[] keyBytes = key.getBytes(StandardCharsets.UTF_8);
- final byte[] valueBytes = value.getBytes(StandardCharsets.UTF_8);
- List
+ * 获取应用管理员列表 + * 第三方服务商可以用此接口获取授权企业中某个第三方应用或者代开发应用的管理员列表(不包括外部管理员), + * 以便服务商在用户进入应用主页之后根据是否管理员身份做权限的区分。 + * 详情请见: 文档 + *+ * + * @param agentId 应用id + * @return admin list + * @throws WxErrorException the wx error exception + */ + WxCpTpAdmin getAdminList(Integer agentId) throws WxErrorException; + } diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpCorpGroupService.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpCorpGroupService.java index 4da13d3fde..69aea4bca7 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpCorpGroupService.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpCorpGroupService.java @@ -9,7 +9,7 @@ * 企业互联相关接口 * * @author libo <422423229@qq.com> - * Created on 27/2/2023 9:57 PM + * @since 2023-02-27 9:57 PM */ public interface WxCpCorpGroupService { /** diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpExportService.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpExportService.java index 24c6ea9dc1..a2c7adabea 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpExportService.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpExportService.java @@ -85,7 +85,7 @@ public interface WxCpExportService { * 获取导出结果 * * 请求方式:GET(HTTPS) - * 请求地址:https://qyapi.weixin.qq.com/cgi-bin/export/get_result?access_token=ACCESS_TOKEN&jobid=jobid_xxxxxxxxxxxxxxx + * {@code 请求地址:https://qyapi.weixin.qq.com/cgi-bin/export/get_result?access_token=ACCESS_TOKEN&jobid=jobid_xxxxxxxxxxxxxxx} * * 文档地址:https://developer.work.weixin.qq.com/document/path/94854 * 返回的url文件下载解密可参考 CSDN diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpExternalContactService.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpExternalContactService.java index 7f3cdeab7c..6de9f9226d 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpExternalContactService.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpExternalContactService.java @@ -146,7 +146,7 @@ public interface WxCpExternalContactService { * 企业可通过此接口,根据外部联系人的userid(如何获取?),拉取客户详情。 * * 请求方式:GET(HTTPS) - * 请求地址:https://qyapi.weixin.qq.com/cgi-bin/externalcontact/get?access_token=ACCESS_TOKEN&external_userid=EXTERNAL_USERID + * {@code 请求地址:https://qyapi.weixin.qq.com/cgi-bin/externalcontact/get?access_token=ACCESS_TOKEN&external_userid=EXTERNAL_USERID} * * 权限说明: * @@ -252,9 +252,9 @@ public interface WxCpExternalContactService { * * 权限说明: * - * 该企业授权了该服务商第三方应用,且授权的第三方应用具备“企业客户权限->客户基础信息”权限 + * {@code 该企业授权了该服务商第三方应用,且授权的第三方应用具备“企业客户权限->客户基础信息”权限} * 该客户的跟进人必须在应用的可见范围之内 - * 应用需具备“企业客户权限->客户基础信息”权限 + * {@code 应用需具备“企业客户权限->客户基础信息”权限} * * * @param externalUserid 代开发自建应用获取到的外部联系人ID @@ -276,8 +276,8 @@ public interface WxCpExternalContactService { * * @param externalUserid 服务商主体的external_userid,必须是source_agentid对应的应用所获取 * @param sourceAgentId 企业授权的代开发自建应用或第三方应用的agentid - * @return - * @throws WxErrorException + * @return 企业的external_userid + * @throws WxErrorException 微信错误异常 */ String fromServiceExternalUserid(String externalUserid, String sourceAgentId) throws WxErrorException; @@ -362,7 +362,7 @@ public interface WxCpExternalContactService { * 权限说明: * * 企业需要使用“客户联系”secret或配置到“可调用应用”列表中的自建应用secret所获取的accesstoken来调用(accesstoken如何获取?) - * 第三方应用需具有“企业客户权限->客户基础信息”权限 + * {@code 第三方应用需具有“企业客户权限->客户基础信息”权限} * 对于第三方/自建应用,群主必须在应用的可见范围 * 仅支持企业服务人员创建的客户群 * 仅可转换出自己企业下的客户群chat_id @@ -410,11 +410,12 @@ WxCpExternalContactBatchInfo getContactDetailBatch(String[] userIdList, String c * 文档地址: https://developer.work.weixin.qq.com/document/path/99434 * * + * 注意:企业可通过外部联系人临时ID排除重复数据,外部联系人临时ID有效期为4小时。 + * * @param cursor the cursor * @param limit the limit * @return 已服务的外部联系人列表 * @throws WxErrorException . - * @apiNote 企业可通过外部联系人临时ID排除重复数据,外部联系人临时ID有效期为4小时。 */ WxCpExternalContactListInfo getContactList(String cursor, Integer limit) throws WxErrorException; @@ -438,7 +439,7 @@ WxCpExternalContactBatchInfo getContactDetailBatch(String[] userIdList, String c * 企业可通过此接口获取指定成员添加的客户列表。客户是指配置了客户联系功能的成员所添加的外部联系人。没有配置客户联系功能的成员,所添加的外部联系人将不会作为客户返回。 * * 请求方式:GET(HTTPS) - * 请求地址:https://qyapi.weixin.qq.com/cgi-bin/externalcontact/list?access_token=ACCESS_TOKEN&userid=USERID + * {@code 请求地址:https://qyapi.weixin.qq.com/cgi-bin/externalcontact/list?access_token=ACCESS_TOKEN&userid=USERID} * * 权限说明: * @@ -469,7 +470,8 @@ WxCpExternalContactBatchInfo getContactDetailBatch(String[] userIdList, String c /** * 获取待分配的离职成员列表 * 企业和第三方可通过此接口,获取所有离职成员的客户列表,并可进一步调用分配离职成员的客户接口将这些客户重新分配给其他企业成员。 - *
+
+ *
* 请求方式:POST(HTTPS)
* 请求地址:https://qyapi.weixin.qq.com/cgi-bin/externalcontact/get_unassigned_list?access_token=ACCESS_TOKEN
*
@@ -496,17 +498,17 @@ WxCpExternalContactBatchInfo getContactDetailBatch(String[] userIdList, String c
/**
* 企业可通过此接口,转接在职成员的客户给其他成员。
- *
+ *
* 权限说明:
- * * 企业需要使用“客户联系”secret或配置到“可调用应用”列表中的自建应用secret所获取的accesstoken来调用(accesstoken如何获取?)。
- * 第三方应用需拥有“企业客户权限->客户联系->在职继承”权限
+ * 企业需要使用“客户联系”secret或配置到“可调用应用”列表中的自建应用secret所获取的accesstoken来调用(accesstoken如何获取?)。
+ * {@code 第三方应用需拥有“企业客户权限->客户联系->在职继承”权限}
* 接替成员必须在此第三方应用或自建应用的可见范围内。
* 接替成员需要配置了客户联系功能。
* 接替成员需要在企业微信激活且已经过实名认证。
- *
+ *
* 企业需要使用“客户联系”secret或配置到“可调用应用”列表中的自建应用secret所获取的accesstoken来调用(accesstoken如何获取?)。
- * 第三方应用需拥有“企业客户权限->客户联系->在职继承”权限
+ * {@code 第三方应用需拥有“企业客户权限->客户联系->在职继承”权限}
* 接替成员必须在此第三方应用或自建应用的可见范围内。
- *
+
+ *
* 权限说明:
- *
+
+ *
* 企业需要使用“客户联系”secret或配置到“可调用应用”列表中的自建应用secret所获取的accesstoken来调用(accesstoken如何获取?)。
- * 第三方应用需拥有“企业客户权限->客户联系->离职分配”权限
+ * {@code 第三方应用需拥有“企业客户权限->客户联系->离职分配”权限}
* 接替成员必须在此第三方应用或自建应用的可见范围内。
* 接替成员需要配置了客户联系功能。
* 接替成员需要在企业微信激活且已经过实名认证。
- *
+
+ *
* 企业需要使用“客户联系”secret或配置到“可调用应用”列表中的自建应用secret所获取的accesstoken来调用(accesstoken如何获取?)。
- * 第三方应用需拥有“企业客户权限->客户联系->在职继承”权限
+ * {@code 第三方应用需拥有“企业客户权限->客户联系->在职继承”权限}
* 接替成员必须在此第三方应用或自建应用的可见范围内。
- *
+
+ *
* 群主离职了的客户群,才可继承
* 继承给的新群主,必须是配置了客户联系功能的成员
* 继承给的新群主,必须有设置实名
* 继承给的新群主,必须有激活企业微信
* 同一个人的群,限制每天最多分配300个给新群主
- *
+
+ *
* 权限说明:
- *
+
+ *
* 企业需要使用“客户联系”secret或配置到“可调用应用”列表中的自建应用secret所获取的accesstoken来调用(accesstoken如何获取?)。
- * 第三方应用需拥有“企业客户权限->客户联系->分配离职成员的客户群”权限
+ * {@code 第三方应用需拥有“企业客户权限->客户联系->分配离职成员的客户群”权限}
* 对于第三方/自建应用,群主必须在应用的可见范围。
- *
+
+ *
* 请求方式: POST(HTTP)
- *
+
+ *
* 请求地址:https://qyapi.weixin.qq.com/cgi-bin/externalcontact/add_msg_template?access_token=ACCESS_TOKEN
- *
+
+ *
* 文档地址
*
* @param wxCpMsgTemplate the wx cp msg template
@@ -733,15 +744,18 @@ WxCpUserExternalGroupChatStatistic getGroupChatStatistic(Date startTime, Integer
/**
* 提醒成员群发
* 企业和第三方应用可调用此接口,重新触发群发通知,提醒成员完成群发任务,24小时内每个群发最多触发三次提醒。
- *
+
+ *
* 请求方式: POST(HTTPS)
- *
+
+ *
* 请求地址:https://qyapi.weixin.qq.com/cgi-bin/externalcontact/remind_groupmsg_send?access_token=ACCESS_TOKEN
- *
+ *
* 文档地址
*
* @param msgId 群发消息的id,通过获取群发记录列表接口返回
* @return the wx cp msg template add result
+ * @throws WxErrorException 微信错误异常
*/
WxCpBaseResp remindGroupMsgSend(String msgId) throws WxErrorException;
@@ -753,11 +767,12 @@ WxCpUserExternalGroupChatStatistic getGroupChatStatistic(Date startTime, Integer
* 请求方式: POST(HTTPS)
*
* 请求地址:https://qyapi.weixin.qq.com/cgi-bin/externalcontact/cancel_groupmsg_send?access_token=ACCESS_TOKEN
- *
+ *
* 文档地址
*
* @param msgId 群发消息的id,通过获取群发记录列表接口返回
* @return the wx cp msg template add result
+ * @throws WxErrorException 微信错误异常
*/
WxCpBaseResp cancelGroupMsgSend(String msgId) throws WxErrorException;
@@ -1002,7 +1017,7 @@ WxCpGroupMsgListResult getGroupMsgListV2(String chatType, Date startTime, Date e
/**
*
+ * 注意:
+ * 根据上面返回的文件类型,拼接好存放文件的绝对路径即可。此时绝对路径写入文件流,来达到获取媒体文件的目的。
+ * 详情可以看官方文档,亦可阅读此接口源码。
+ *
+ * @param sdkfileid 消息体内容中的sdkfileid信息
+ * @param proxy 使用代理的请求,需要传入代理的链接。如:socks5://10.0.0.1:8081 或者 http://10.0.0.1:8081,如果没有传null
+ * @param passwd 代理账号密码,需要传入代理的账号密码。如 user_name:passwd_123,如果没有传null
+ * @param timeout 超时时间,分片数据需累加到文件存储。单次最大返回512K字节,如果文件比较大,自行设置长一点,比如timeout=10000
+ * @param targetFilePath 目标文件绝对路径+实际文件名,比如:/usr/local/file/20220114/474f866b39d10718810d55262af82662.gif
+ * @throws WxErrorException the wx error exception
+ */
+ void downloadMediaFile(@NonNull String sdkfileid, String proxy, String passwd, @NonNull long timeout,
+ @NonNull String targetFilePath) throws WxErrorException;
+
/**
* 获取媒体文件 传入一个lambda,each所有的数据分片byte[],更加灵活
* 针对图片、文件等媒体数据,提供sdk接口拉取数据内容。
@@ -85,10 +152,29 @@ void getMediaFile(@NonNull long sdk, @NonNull String sdkfileid, String proxy, St
* @param timeout 超时时间,分片数据需累加到文件存储。单次最大返回512K字节,如果文件比较大,自行设置长一点,比如timeout=10000
* @param action 传入一个lambda,each所有的数据分片
* @throws WxErrorException the wx error exception
+ * @deprecated 请使用 {@link #downloadMediaFile(String, String, String, long, Consumer)} 代替,
+ * 该方法需要传入SDK,容易导致SDK生命周期管理混乱,引发JVM崩溃
*/
+ @Deprecated
void getMediaFile(@NonNull long sdk, @NonNull String sdkfileid, String proxy, String passwd, @NonNull long timeout,
@NonNull Consumer
+ *
+ *
+ * 注意:
+ * 1.批量更新请求中的各个操作会逐个按顺序执行,直到全部执行完成则请求返回,或者其中一个操作报错则不再继续执行后续的操作
+ * 2.每一个更新操作在执行之前都会做请求校验(包括权限校验、参数校验等等),如果校验未通过则该更新操作会报错并返回,不再执行后续操作
+ * 3.单次批量更新请求的操作数量 <= 5
+ *
+ * 请求方式:POST(HTTPS)
+ * 请求地址: https://qyapi.weixin.qq.com/cgi-bin/wedoc/spreadsheet/batch_update?access_token=ACCESS_TOKEN
+ *
+ * @param request 编辑表格内容请求参数
+ * @return 编辑表格内容批量更新的响应结果
+ * @throws WxErrorException the wx error exception
+ */
+ WxCpDocSheetBatchUpdateResponse docBatchUpdate(@NonNull WxCpDocSheetBatchUpdateRequest request) throws WxErrorException;
+
+ /**
+ * 获取表格行列信息
+ * 该接口用于获取在线表格的工作表、行数、列数等。
+ *
+ * 请求方式:POST(HTTPS)
+ * 请求地址: https://qyapi.weixin.qq.com/cgi-bin/wedoc/spreadsheet/get_sheet_properties?access_token=ACCESS_TOKEN
+ *
+ * @param docId 在线表格的docid
+ * @return 返回表格行列信息
+ * @throws WxErrorException
+ */
+ WxCpDocSheetProperties getSheetProperties(@NonNull String docId) throws WxErrorException;
+
+
+ /**
+ * 本接口用于获取指定范围内的在线表格信息,单次查询的范围大小需满足以下限制:
+ *
+ * 查询范围行数 <=1000
+ * 查询范围列数 <=200
+ * 范围内的总单元格数量 <=10000
+ *
+ * 请求方式:POST(HTTPS)
+ * 请求地址: https://qyapi.weixin.qq.com/cgi-bin/wedoc/spreadsheet/get_sheet_range_data?access_token=ACCESS_TOKEN
+ *
+ * @param request 获取指定范围内的在线表格信息请求参数
+ * @return 返回指定范围内的在线表格信息
+ * @throws WxErrorException
+ */
+ WxCpDocSheetData getSheetRangeData(@NonNull WxCpDocSheetGetDataRequest request) throws WxErrorException;
+
}
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpOaWeDriveService.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpOaWeDriveService.java
index 8c3efbc1ab..e7217616b8 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpOaWeDriveService.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpOaWeDriveService.java
@@ -48,12 +48,11 @@ public interface WxCpOaWeDriveService {
* 请求方式:POST(HTTPS)
* 请求地址: ...
*
- * @param userId the user id
* @param spaceId the space id
* @return wx cp base resp
* @throws WxErrorException the wx error exception
*/
- WxCpBaseResp spaceDismiss(@NonNull String userId, @NonNull String spaceId) throws WxErrorException;
+ WxCpBaseResp spaceDismiss(@NonNull String spaceId) throws WxErrorException;
/**
* 获取空间信息
@@ -62,12 +61,11 @@ public interface WxCpOaWeDriveService {
* 请求方式:POST(HTTPS)
* 请求地址: ...
*
- * @param userId the user id
* @param spaceId the space id
* @return wx cp space info
* @throws WxErrorException the wx error exception
*/
- WxCpSpaceInfo spaceInfo(@NonNull String userId, @NonNull String spaceId) throws WxErrorException;
+ WxCpSpaceInfo spaceInfo(@NonNull String spaceId) throws WxErrorException;
/**
* 添加成员/部门
@@ -115,12 +113,11 @@ public interface WxCpOaWeDriveService {
* 请求方式:POST(HTTPS)
* 请求地址: ...
*
- * @param userId the user id
* @param spaceId the space id
* @return wx cp space share
* @throws WxErrorException the wx error exception
*/
- WxCpSpaceShare spaceShare(@NonNull String userId, @NonNull String spaceId) throws WxErrorException;
+ WxCpSpaceShare spaceShare(@NonNull String spaceId) throws WxErrorException;
/**
* 获取文件列表
@@ -155,18 +152,18 @@ public interface WxCpOaWeDriveService {
* 请求方式:POST(HTTPS)
* 请求地址: ...
*
- * @param fileId 文件fileid(只支持下载普通文件,不支持下载文件夹或微文档)
+ * @param fileId 文件fileid(只支持下载普通文件,不支持下载文件夹或微文档)
* @param selectedTicket 微盘和文件选择器jsapi返回的selectedTicket。若填此参数,则不需要填fileid。
* @return {
- * "errcode": 0,
- * "errmsg": "ok",
- * "download_url": "DOWNLOAD_URL",
- * "cookie_name": "COOKIE_NAME",
- * "cookie_value": "COOKIE_VALUE"
+ * "errcode": 0,
+ * "errmsg": "ok",
+ * "download_url": "DOWNLOAD_URL",
+ * "cookie_name": "COOKIE_NAME",
+ * "cookie_value": "COOKIE_VALUE"
* }
* @throws WxErrorException the wx error exception
*/
- WxCpFileDownload fileDownload( String fileId, String selectedTicket) throws WxErrorException;
+ WxCpFileDownload fileDownload(String fileId, String selectedTicket) throws WxErrorException;
/**
* 重命名文件
@@ -271,14 +268,13 @@ WxCpFileCreate fileCreate(@NonNull String spaceId, @NonNull String fatherId, @No
* 请求方式:POST(HTTPS)
* 请求地址: ...
*
- * @param userId the user id
* @param fileId the file id
* @param authScope the auth scope
* @param auth the auth
* @return wx cp base resp
* @throws WxErrorException the wx error exception
*/
- WxCpBaseResp fileSetting(@NonNull String userId, @NonNull String fileId, @NonNull Integer authScope, Integer auth) throws WxErrorException;
+ WxCpBaseResp fileSetting(@NonNull String fileId, @NonNull Integer authScope, Integer auth) throws WxErrorException;
/**
* 获取分享链接
@@ -287,11 +283,10 @@ WxCpFileCreate fileCreate(@NonNull String spaceId, @NonNull String fatherId, @No
* 请求方式:POST(HTTPS)
* 请求地址: ...
*
- * @param userId the user id
* @param fileId the file id
* @return wx cp file share
* @throws WxErrorException the wx error exception
*/
- WxCpFileShare fileShare(@NonNull String userId, @NonNull String fileId) throws WxErrorException;
+ WxCpFileShare fileShare(@NonNull String fileId) throws WxErrorException;
}
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpSchoolService.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpSchoolService.java
index 56687c9cb1..5f1d41c197 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpSchoolService.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpSchoolService.java
@@ -80,9 +80,10 @@ public interface WxCpSchoolService {
/**
* 获取直播详情
+ *
+ *
+ *
+ *
* 请求方式:GET(HTTPS)
- * 请求地址:https://qyapi.weixin.qq.com/cgi-bin/externalcontact/get?access_token=ACCESS_TOKEN&external_userid
- * =EXTERNAL_USERID
+ * {@code 请求地址:https://qyapi.weixin.qq.com/cgi-bin/externalcontact/get?access_token=ACCESS_TOKEN&external_userid=EXTERNAL_USERID}
*
* @param externalUserId 外部联系人的userid,注意不是学校成员的帐号
* @return external contact
@@ -306,9 +315,9 @@ WxCpBaseResp updateStudent(@NonNull String studentUserId, String newStudentUserI
/**
* 获取可使用的家长范围
* 获取可在微信「学校通知-学校应用」使用该应用的家长范围,以学生或部门列表的形式返回。应用只能给该列表下的家长发送「学校通知」。注意该范围只能由学校的系统管理员在「管理端-家校沟通-配置」配置。
- *
+ *
* 请求方式:GET(HTTPS)
- * 请求地址:https://qyapi.weixin.qq.com/cgi-bin/school/agent/get_allow_scope?access_token=ACCESS_TOKEN&agentid=AGENTID
+ * {@code 请求地址:https://qyapi.weixin.qq.com/cgi-bin/school/agent/get_allow_scope?access_token=ACCESS_TOKEN&agentid=AGENTID}
*
* @param agentId the agent id
* @return allow scope
@@ -332,7 +341,7 @@ WxCpBaseResp updateStudent(@NonNull String studentUserId, String newStudentUserI
/**
* 获取部门列表
* 请求方式:GET(HTTPS)
- * 请求地址:https://qyapi.weixin.qq.com/cgi-bin/school/department/list?access_token=ACCESS_TOKEN&id=ID
+ * {@code 请求地址:https://qyapi.weixin.qq.com/cgi-bin/school/department/list?access_token=ACCESS_TOKEN&id=ID}
*
* @param id 部门id。获取指定部门及其下的子部门。 如果不填,默认获取全量组织架构
* @return wx cp department list
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpService.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpService.java
index 9bcb161534..76012a2812 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpService.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpService.java
@@ -584,7 +584,14 @@ public interface WxCpService extends WxService {
/**
* 企业互联的服务类对象
*
- * @return
+ * @return 企业互联服务对象
*/
WxCpCorpGroupService getCorpGroupService();
+
+ /**
+ * 获取智能机器人服务
+ *
+ * @return 智能机器人服务 intelligent robot service
+ */
+ WxCpIntelligentRobotService getIntelligentRobotService();
}
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpUserService.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpUserService.java
index 2368386b23..7a7b5f40a8 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpUserService.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpUserService.java
@@ -38,7 +38,7 @@ public interface WxCpUserService {
*
- * Created by songfan on 2020/7/14.
*
- * @author songfan & Mr.Pan
+ * @author songfan, Mr.Pan
+ * @since 2020/7/14
*/
@Data
@Builder
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/external/WxCpUserExternalUnassignList.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/external/WxCpUserExternalUnassignList.java
index 8605760fa7..f3fdd96ce7 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/external/WxCpUserExternalUnassignList.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/external/WxCpUserExternalUnassignList.java
@@ -12,7 +12,8 @@
/**
* 离职员工外部联系人列表
*
- * @author yqx & Wang_Wong created on 2020/3/15
+ * @author yqx, Wang_Wong
+ * @since 2020/3/15
*/
@Getter
@Setter
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/external/acquisition/WxCpCustomerAcquisitionStatistic.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/external/acquisition/WxCpCustomerAcquisitionStatistic.java
index bb02b039bd..87e3d5580a 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/external/acquisition/WxCpCustomerAcquisitionStatistic.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/external/acquisition/WxCpCustomerAcquisitionStatistic.java
@@ -10,7 +10,7 @@
* 获客链接的使用详情
*
* @author Hugo
- * @date 2023/12/11 10:31
+ * @since 2023/12/11 10:31
*/
@Data
@EqualsAndHashCode(callSuper = true)
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/external/interceptrule/WxCpInterceptRuleInfo.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/external/interceptrule/WxCpInterceptRuleInfo.java
index 20d6b32442..23bb70a240 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/external/interceptrule/WxCpInterceptRuleInfo.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/external/interceptrule/WxCpInterceptRuleInfo.java
@@ -10,9 +10,10 @@
import java.util.List;
/**
- * @Date: 2024-03-07 17:02
- * @Author: shenliuming
- * @return:
+ * 防骚扰规则详情
+ *
+ * @author shenliuming
+ * @since 2024-03-07 17:02
*/
@Data
@EqualsAndHashCode(callSuper = true)
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/external/interceptrule/WxCpInterceptRuleList.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/external/interceptrule/WxCpInterceptRuleList.java
index 6826413e13..543d32fcb9 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/external/interceptrule/WxCpInterceptRuleList.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/external/interceptrule/WxCpInterceptRuleList.java
@@ -9,9 +9,10 @@
import java.util.List;
/**
- * @Date: 2024-03-07 15:54
- * @Author: shenliuming
- * @return:
+ * 防骚扰规则列表
+ *
+ * @author shenliuming
+ * @since 2024-03-07 15:54
*/
@Data
@EqualsAndHashCode(callSuper = true)
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/external/msg/Attachment.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/external/msg/Attachment.java
index be9dcc9dd0..1fff457f97 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/external/msg/Attachment.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/external/msg/Attachment.java
@@ -33,6 +33,7 @@ public class Attachment implements Serializable {
* Sets image.
*
* @param image the image
+ * @return this
*/
public Attachment setImage(Image image) {
this.image = image;
@@ -44,6 +45,7 @@ public Attachment setImage(Image image) {
* Sets link.
*
* @param link the link
+ * @return this
*/
public Attachment setLink(Link link) {
this.link = link;
@@ -55,6 +57,7 @@ public Attachment setLink(Link link) {
* Sets mini program.
*
* @param miniProgram the mini program
+ * @return this
*/
public Attachment setMiniProgram(MiniProgram miniProgram) {
this.miniProgram = miniProgram;
@@ -66,6 +69,7 @@ public Attachment setMiniProgram(MiniProgram miniProgram) {
* Sets video.
*
* @param video the video
+ * @return this
*/
public Attachment setVideo(Video video) {
this.video = video;
@@ -77,6 +81,7 @@ public Attachment setVideo(Video video) {
* Sets file.
*
* @param file the file
+ * @return this
*/
public Attachment setFile(File file) {
this.file = file;
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/intelligentrobot/WxCpIntelligentRobot.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/intelligentrobot/WxCpIntelligentRobot.java
new file mode 100644
index 0000000000..60d518cac3
--- /dev/null
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/intelligentrobot/WxCpIntelligentRobot.java
@@ -0,0 +1,77 @@
+package me.chanjar.weixin.cp.bean.intelligentrobot;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import me.chanjar.weixin.cp.bean.WxCpBaseResp;
+import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder;
+
+import java.io.Serializable;
+
+/**
+ * 智能机器人信息
+ *
+ * @author Binary Wang
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class WxCpIntelligentRobot extends WxCpBaseResp implements Serializable {
+ private static final long serialVersionUID = -1L;
+
+ /**
+ * 机器人ID
+ */
+ @SerializedName("robot_id")
+ private String robotId;
+
+ /**
+ * 机器人名称
+ */
+ @SerializedName("name")
+ private String name;
+
+ /**
+ * 机器人描述
+ */
+ @SerializedName("description")
+ private String description;
+
+ /**
+ * 机器人头像
+ */
+ @SerializedName("avatar")
+ private String avatar;
+
+ /**
+ * 机器人状态 0:停用 1:启用
+ */
+ @SerializedName("status")
+ private Integer status;
+
+ /**
+ * 创建时间
+ */
+ @SerializedName("create_time")
+ private Long createTime;
+
+ /**
+ * 更新时间
+ */
+ @SerializedName("update_time")
+ private Long updateTime;
+
+ /**
+ * From json wx cp intelligent robot.
+ *
+ * @param json the json
+ * @return the wx cp intelligent robot
+ */
+ public static WxCpIntelligentRobot fromJson(String json) {
+ return WxCpGsonBuilder.create().fromJson(json, WxCpIntelligentRobot.class);
+ }
+
+ public String toJson() {
+ return WxCpGsonBuilder.create().toJson(this);
+ }
+
+}
\ No newline at end of file
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/intelligentrobot/WxCpIntelligentRobotChatRequest.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/intelligentrobot/WxCpIntelligentRobotChatRequest.java
new file mode 100644
index 0000000000..d94ea93623
--- /dev/null
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/intelligentrobot/WxCpIntelligentRobotChatRequest.java
@@ -0,0 +1,49 @@
+package me.chanjar.weixin.cp.bean.intelligentrobot;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder;
+
+import java.io.Serializable;
+
+/**
+ * 智能机器人聊天请求
+ *
+ * @author Binary Wang
+ */
+@Data
+public class WxCpIntelligentRobotChatRequest implements Serializable {
+ private static final long serialVersionUID = -1L;
+
+ /**
+ * 机器人ID
+ */
+ @SerializedName("robot_id")
+ private String robotId;
+
+ /**
+ * 用户ID
+ */
+ @SerializedName("userid")
+ private String userid;
+
+ /**
+ * 消息内容
+ */
+ @SerializedName("message")
+ private String message;
+
+ /**
+ * 会话ID,可选,用于保持会话连续性
+ */
+ @SerializedName("session_id")
+ private String sessionId;
+
+ public String toJson() {
+ return WxCpGsonBuilder.create().toJson(this);
+ }
+
+ public static WxCpIntelligentRobotChatRequest fromJson(String json) {
+ return WxCpGsonBuilder.create().fromJson(json, WxCpIntelligentRobotChatRequest.class);
+ }
+}
\ No newline at end of file
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/intelligentrobot/WxCpIntelligentRobotChatResponse.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/intelligentrobot/WxCpIntelligentRobotChatResponse.java
new file mode 100644
index 0000000000..c7e56a4d03
--- /dev/null
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/intelligentrobot/WxCpIntelligentRobotChatResponse.java
@@ -0,0 +1,46 @@
+package me.chanjar.weixin.cp.bean.intelligentrobot;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import me.chanjar.weixin.cp.bean.WxCpBaseResp;
+import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder;
+
+import java.io.Serializable;
+
+/**
+ * 智能机器人聊天响应
+ *
+ * @author Binary Wang
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class WxCpIntelligentRobotChatResponse extends WxCpBaseResp implements Serializable {
+ private static final long serialVersionUID = -1L;
+
+ /**
+ * 机器人回复内容
+ */
+ @SerializedName("reply")
+ private String reply;
+
+ /**
+ * 会话ID
+ */
+ @SerializedName("session_id")
+ private String sessionId;
+
+ /**
+ * 消息ID
+ */
+ @SerializedName("msg_id")
+ private String msgId;
+
+ public static WxCpIntelligentRobotChatResponse fromJson(String json) {
+ return WxCpGsonBuilder.create().fromJson(json, WxCpIntelligentRobotChatResponse.class);
+ }
+
+ public String toJson() {
+ return WxCpGsonBuilder.create().toJson(this);
+ }
+}
\ No newline at end of file
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/intelligentrobot/WxCpIntelligentRobotCreateRequest.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/intelligentrobot/WxCpIntelligentRobotCreateRequest.java
new file mode 100644
index 0000000000..46dd5f609b
--- /dev/null
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/intelligentrobot/WxCpIntelligentRobotCreateRequest.java
@@ -0,0 +1,43 @@
+package me.chanjar.weixin.cp.bean.intelligentrobot;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder;
+
+import java.io.Serializable;
+
+/**
+ * 创建智能机器人请求
+ *
+ * @author Binary Wang
+ */
+@Data
+public class WxCpIntelligentRobotCreateRequest implements Serializable {
+ private static final long serialVersionUID = -1L;
+
+ /**
+ * 机器人名称
+ */
+ @SerializedName("name")
+ private String name;
+
+ /**
+ * 机器人描述
+ */
+ @SerializedName("description")
+ private String description;
+
+ /**
+ * 机器人头像
+ */
+ @SerializedName("avatar")
+ private String avatar;
+
+ public String toJson() {
+ return WxCpGsonBuilder.create().toJson(this);
+ }
+
+ public static WxCpIntelligentRobotCreateRequest fromJson(String json) {
+ return WxCpGsonBuilder.create().fromJson(json, WxCpIntelligentRobotCreateRequest.class);
+ }
+}
\ No newline at end of file
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/intelligentrobot/WxCpIntelligentRobotCreateResponse.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/intelligentrobot/WxCpIntelligentRobotCreateResponse.java
new file mode 100644
index 0000000000..449d91f7d3
--- /dev/null
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/intelligentrobot/WxCpIntelligentRobotCreateResponse.java
@@ -0,0 +1,34 @@
+package me.chanjar.weixin.cp.bean.intelligentrobot;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import me.chanjar.weixin.cp.bean.WxCpBaseResp;
+import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder;
+
+import java.io.Serializable;
+
+/**
+ * 创建智能机器人响应
+ *
+ * @author Binary Wang
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class WxCpIntelligentRobotCreateResponse extends WxCpBaseResp implements Serializable {
+ private static final long serialVersionUID = -1L;
+
+ /**
+ * 机器人ID
+ */
+ @SerializedName("robot_id")
+ private String robotId;
+
+ public static WxCpIntelligentRobotCreateResponse fromJson(String json) {
+ return WxCpGsonBuilder.create().fromJson(json, WxCpIntelligentRobotCreateResponse.class);
+ }
+
+ public String toJson() {
+ return WxCpGsonBuilder.create().toJson(this);
+ }
+}
\ No newline at end of file
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/intelligentrobot/WxCpIntelligentRobotSendMessageRequest.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/intelligentrobot/WxCpIntelligentRobotSendMessageRequest.java
new file mode 100644
index 0000000000..405c67daff
--- /dev/null
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/intelligentrobot/WxCpIntelligentRobotSendMessageRequest.java
@@ -0,0 +1,56 @@
+package me.chanjar.weixin.cp.bean.intelligentrobot;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder;
+
+import java.io.Serializable;
+
+/**
+ * 智能机器人发送消息请求
+ * 官方文档: https://developer.work.weixin.qq.com/document/path/100719
+ *
+ * @author Binary Wang
+ */
+@Data
+public class WxCpIntelligentRobotSendMessageRequest implements Serializable {
+ private static final long serialVersionUID = -1L;
+
+ /**
+ * 机器人ID,必填
+ */
+ @SerializedName("robot_id")
+ private String robotId;
+
+ /**
+ * 接收消息的用户ID,必填
+ */
+ @SerializedName("userid")
+ private String userid;
+
+ /**
+ * 消息内容,必填
+ */
+ @SerializedName("message")
+ private String message;
+
+ /**
+ * 会话ID,可选,用于保持会话连续性
+ */
+ @SerializedName("session_id")
+ private String sessionId;
+
+ /**
+ * 消息ID,可选
+ */
+ @SerializedName("msg_id")
+ private String msgId;
+
+ public String toJson() {
+ return WxCpGsonBuilder.create().toJson(this);
+ }
+
+ public static WxCpIntelligentRobotSendMessageRequest fromJson(String json) {
+ return WxCpGsonBuilder.create().fromJson(json, WxCpIntelligentRobotSendMessageRequest.class);
+ }
+}
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/intelligentrobot/WxCpIntelligentRobotSendMessageResponse.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/intelligentrobot/WxCpIntelligentRobotSendMessageResponse.java
new file mode 100644
index 0000000000..8098d2037e
--- /dev/null
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/intelligentrobot/WxCpIntelligentRobotSendMessageResponse.java
@@ -0,0 +1,42 @@
+package me.chanjar.weixin.cp.bean.intelligentrobot;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import me.chanjar.weixin.cp.bean.WxCpBaseResp;
+import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder;
+
+import java.io.Serializable;
+
+/**
+ * 智能机器人发送消息响应
+ * 官方文档: https://developer.work.weixin.qq.com/document/path/100719
+ *
+ * @author Binary Wang
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class WxCpIntelligentRobotSendMessageResponse extends WxCpBaseResp implements Serializable {
+ private static final long serialVersionUID = -1L;
+
+ /**
+ * 消息ID
+ */
+ @SerializedName("msg_id")
+ private String msgId;
+
+ /**
+ * 会话ID
+ */
+ @SerializedName("session_id")
+ private String sessionId;
+
+ public static WxCpIntelligentRobotSendMessageResponse fromJson(String json) {
+ return WxCpGsonBuilder.create().fromJson(json, WxCpIntelligentRobotSendMessageResponse.class);
+ }
+
+ @Override
+ public String toJson() {
+ return WxCpGsonBuilder.create().toJson(this);
+ }
+}
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/intelligentrobot/WxCpIntelligentRobotUpdateRequest.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/intelligentrobot/WxCpIntelligentRobotUpdateRequest.java
new file mode 100644
index 0000000000..027812a7e9
--- /dev/null
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/intelligentrobot/WxCpIntelligentRobotUpdateRequest.java
@@ -0,0 +1,55 @@
+package me.chanjar.weixin.cp.bean.intelligentrobot;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder;
+
+import java.io.Serializable;
+
+/**
+ * 更新智能机器人请求
+ *
+ * @author Binary Wang
+ */
+@Data
+public class WxCpIntelligentRobotUpdateRequest implements Serializable {
+ private static final long serialVersionUID = -1L;
+
+ /**
+ * 机器人ID
+ */
+ @SerializedName("robot_id")
+ private String robotId;
+
+ /**
+ * 机器人名称
+ */
+ @SerializedName("name")
+ private String name;
+
+ /**
+ * 机器人描述
+ */
+ @SerializedName("description")
+ private String description;
+
+ /**
+ * 机器人头像
+ */
+ @SerializedName("avatar")
+ private String avatar;
+
+ /**
+ * 机器人状态 0:停用 1:启用
+ */
+ @SerializedName("status")
+ private Integer status;
+
+ public String toJson() {
+ return WxCpGsonBuilder.create().toJson(this);
+ }
+
+ public static WxCpIntelligentRobotUpdateRequest fromJson(String json) {
+ return WxCpGsonBuilder.create().fromJson(json, WxCpIntelligentRobotUpdateRequest.class);
+ }
+}
\ No newline at end of file
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/kf/WxCpKfAccountLink.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/kf/WxCpKfAccountLink.java
index a903d0fa54..38d25e61cd 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/kf/WxCpKfAccountLink.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/kf/WxCpKfAccountLink.java
@@ -27,10 +27,10 @@ public class WxCpKfAccountLink implements Serializable {
* 场景值,字符串类型,由开发者自定义。
* 不多于32字节
* 字符串取值范围(正则表达式):[0-9a-zA-Z_-]*
- *
+ *
* 1. 若scene非空,返回的客服链接开发者可拼接scene_param=SCENE_PARAM参数使用,用户进入会话事件会将SCENE_PARAM原样返回。
* 其中SCENE_PARAM需要urlencode,且长度不能超过128字节。
- * 如 https://work.weixin.qq.com/kf/kfcbf8f8d07ac7215f?enc_scene=ENCGFSDF567DF&scene_param=a%3D1%26b%3D2
+ * {@code 如 https://work.weixin.qq.com/kf/kfcbf8f8d07ac7215f?enc_scene=ENCGFSDF567DF&scene_param=a%3D1%26b%3D2}
* 2. 历史调用接口返回的客服链接(包含encScene=XXX参数),不支持scene_param参数。
* 3. 返回的客服链接,不能修改或复制参数到其他链接使用。否则进入会话事件参数校验不通过,导致无法回调。
*/
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/media/MediaUploadByUrlReq.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/media/MediaUploadByUrlReq.java
index c5cb21bde5..0149a426f6 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/media/MediaUploadByUrlReq.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/media/MediaUploadByUrlReq.java
@@ -5,8 +5,9 @@
/**
* 生成异步上传任务
+ *
* @author imyzt
- * @date 2025/04/27
+ * @since 2025/04/27
*/
@Data
public class MediaUploadByUrlReq {
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/media/MediaUploadByUrlResult.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/media/MediaUploadByUrlResult.java
index cc931eed39..595f85dffa 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/media/MediaUploadByUrlResult.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/media/MediaUploadByUrlResult.java
@@ -10,8 +10,9 @@
/**
* 异步上传企微素材
+ *
* @author imyzt
- * @date 2025/4/27
+ * @since 2025/4/27
*/
@EqualsAndHashCode(callSuper = true)
@Data
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/message/WxCpGroupRobotMessage.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/message/WxCpGroupRobotMessage.java
index d115245e04..97beeec189 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/message/WxCpGroupRobotMessage.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/message/WxCpGroupRobotMessage.java
@@ -252,6 +252,12 @@ public String toJson() {
messageJson.add("markdown", text);
break;
}
+ case MARKDOWN_V2: {
+ JsonObject text = new JsonObject();
+ text.addProperty("content", this.getContent());
+ messageJson.add("markdown_v2", text);
+ break;
+ }
case IMAGE: {
JsonObject text = new JsonObject();
text.addProperty("base64", this.getBase64());
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/message/WxCpLinkedCorpMessage.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/message/WxCpLinkedCorpMessage.java
index 92209fd4e5..7e777384eb 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/message/WxCpLinkedCorpMessage.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/message/WxCpLinkedCorpMessage.java
@@ -85,13 +85,13 @@ public class WxCpLinkedCorpMessage implements Serializable {
/**
*
* 文档地址
- *
* 文档地址
- *
* external_userid必须是handover_userid的客户(即配置了客户联系功能的成员所添加的联系人)。
* 在职成员的每位客户最多被分配2次。客户被转接成功后,将有90个自然日的服务关系保护期,保护期内的客户无法再次被分配。
- *
* 权限说明:
- *
* handover_userid必须是已离职用户。
* external_userid必须是handover_userid的客户(即配置了客户联系功能的成员所添加的联系人)。
* 在职成员的每位客户最多被分配2次。客户被转接成功后,将有90个自然日的服务关系保护期,保护期内的客户无法再次被分配。
- *
* 权限说明:
- *
* 注意::
- *
* 注意:
* 继承给的新群主,必须是配置了客户联系功能的成员
* 继承给的新群主,必须有设置实名
@@ -716,11 +724,14 @@ WxCpUserExternalGroupChatStatistic getGroupChatStatistic(Date startTime, Integer
* 企业可通过此接口添加企业群发消息的任务并通知客服人员发送给相关客户或客户群。(注:企业微信终端需升级到2.7.5版本及以上)
* 注意:调用该接口并不会直接发送消息给客户/客户群,需要相关的客服人员操作以后才会实际发送(客服人员的企业微信需要升级到2.7.5及以上版本)
* 同一个企业每个自然月内仅可针对一个客户/客户群发送4条消息,超过限制的用户将会被忽略。
- *
*
* @param mediaId 媒体id
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpMessageService.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpMessageService.java
index e49a36ba50..534cc89b36 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpMessageService.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpMessageService.java
@@ -72,8 +72,9 @@ public interface WxCpMessageService {
* 请求地址: https://qyapi.weixin.qq.com/cgi-bin/message/recall?access_token=ACCESS_TOKEN
* 文档地址: https://developer.work.weixin.qq.com/document/path/94867
*
+ *
* @param msgId 消息id
- * @throws WxErrorException
+ * @throws WxErrorException 异常
*/
void recall(String msgId) throws WxErrorException;
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpMsgAuditService.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpMsgAuditService.java
index 221caf2e70..b754e32b7e 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpMsgAuditService.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpMsgAuditService.java
@@ -28,9 +28,26 @@ public interface WxCpMsgAuditService {
* @param timeout 超时时间,根据实际需要填写
* @return 返回是否调用成功 chat datas
* @throws Exception the exception
+ * @deprecated 请使用 {@link #getChatRecords(long, long, String, String, long)} 代替,
+ * 该方法会将SDK暴露给调用方,容易导致SDK生命周期管理混乱,引发JVM崩溃
*/
+ @Deprecated
WxCpChatDatas getChatDatas(long seq, @NonNull long limit, String proxy, String passwd, @NonNull long timeout) throws Exception;
+ /**
+ * 拉取聊天记录函数(推荐使用)
+ * 该方法不会将SDK暴露给调用方,SDK生命周期由框架自动管理,更加安全
+ *
+ * @param seq 从指定的seq开始拉取消息,注意的是返回的消息从seq+1开始返回,seq为之前接口返回的最大seq值。首次使用请使用seq:0
+ * @param limit 一次拉取的消息条数,最大值1000条,超过1000条会返回错误
+ * @param proxy 使用代理的请求,需要传入代理的链接。如:socks5://10.0.0.1:8081 或者 http://10.0.0.1:8081,如果没有传null
+ * @param passwd 代理账号密码,需要传入代理的账号密码。如 user_name:passwd_123,如果没有传null
+ * @param timeout 超时时间,根据实际需要填写
+ * @return 返回聊天记录列表,不包含SDK信息
+ * @throws Exception the exception
+ */
+ List
* 企业和第三方应用可通过此接口获取企业与成员的群发记录。
- * 获取企业群发成员执行结果
+ * 文档地址:https://work.weixin.qq.com/api/doc/90000/90135/93338
*
*
* @param msgid 群发消息的id,通过获取群发记录列表接口返回
@@ -1031,7 +1046,7 @@ WxCpGroupMsgListResult getGroupMsgListV2(String chatType, Date startTime, Date e
/**
*
* 获取群发成员发送任务列表。
- * 获取群发成员发送任务列表
+ * 文档地址:https://work.weixin.qq.com/api/doc/90000/90135/93338
*
*
* @param msgid 群发消息的id,通过获取群发记录列表接口返回
@@ -1045,7 +1060,7 @@ WxCpGroupMsgListResult getGroupMsgListV2(String chatType, Date startTime, Date e
/**
*
* 添加入群欢迎语素材。
- * 添加入群欢迎语素材
+ * 文档地址:https://open.work.weixin.qq.com/api/doc/90000/90135/92366
*
*
* @param template 素材内容
@@ -1057,7 +1072,7 @@ WxCpGroupMsgListResult getGroupMsgListV2(String chatType, Date startTime, Date e
/**
*
* 编辑入群欢迎语素材。
- * 编辑入群欢迎语素材
+ * 文档地址:https://open.work.weixin.qq.com/api/doc/90000/90135/92366
*
*
* @param template the template
@@ -1069,7 +1084,7 @@ WxCpGroupMsgListResult getGroupMsgListV2(String chatType, Date startTime, Date e
/**
*
* 获取入群欢迎语素材。
- * 获取入群欢迎语素材
+ * 文档地址:https://open.work.weixin.qq.com/api/doc/90000/90135/92366
*
*
* @param templateId 群欢迎语的素材id
@@ -1082,7 +1097,7 @@ WxCpGroupMsgListResult getGroupMsgListV2(String chatType, Date startTime, Date e
*
* 删除入群欢迎语素材。
* 企业可通过此API删除入群欢迎语素材,且仅能删除调用方自己创建的入群欢迎语素材。
- * 删除入群欢迎语素材
+ * 文档地址:https://open.work.weixin.qq.com/api/doc/90000/90135/92366
*
*
* @param templateId 群欢迎语的素材id
@@ -1094,8 +1109,8 @@ WxCpGroupMsgListResult getGroupMsgListV2(String chatType, Date startTime, Date e
/**
*
- * 获取商品图册
- * 获取商品图册列表
+ * 获取商品图册列表
+ * 文档地址:https://work.weixin.qq.com/api/doc/90000/90135/95096
*
*
* @param limit 返回的最大记录数,整型,最大值100,默认值50,超过最大值时取默认值
@@ -1108,7 +1123,7 @@ WxCpGroupMsgListResult getGroupMsgListV2(String chatType, Date startTime, Date e
/**
*
* 获取商品图册
- * 获取商品图册
+ * 文档地址:https://work.weixin.qq.com/api/doc/90000/90135/95096
*
*
* @param productId 商品id
@@ -1155,7 +1170,7 @@ WxMediaUploadResult uploadAttachment(String mediaType, Integer attachmentType, F
* 企业和第三方应用可以通过此接口新建敏感词规则
* 请求方式:POST(HTTPS)
* 请求地址:https://qyapi.weixin.qq.com/cgi-bin/externalcontact/add_intercept_rule?access_token=ACCESS_TOKEN
- *
+ *
* @param ruleAddRequest the rule add request
* @return 规则id
* @throws WxErrorException the wx error exception
@@ -1169,7 +1184,7 @@ WxMediaUploadResult uploadAttachment(String mediaType, Integer attachmentType, F
* 企业和第三方应用可以通过此接口修改敏感词规则
* 请求方式:POST(HTTPS)
* 请求地址:https://qyapi.weixin.qq.com/cgi-bin/externalcontact/update_intercept_rule?access_token=ACCESS_TOKEN
- *
+ *
* @param interceptRule the rule
* @throws WxErrorException the wx error exception
*/
@@ -1181,7 +1196,7 @@ WxMediaUploadResult uploadAttachment(String mediaType, Integer attachmentType, F
* 企业和第三方应用可以通过此接口修改敏感词规则
* 请求方式:POST(HTTPS)
* 请求地址
- *
+ *
* @param ruleId 规则id
* @throws WxErrorException the wx error exception
*/
@@ -1220,7 +1235,7 @@ WxMediaUploadResult uploadAttachment(String mediaType, Integer attachmentType, F
* 请求地址:
* https://qyapi.weixin.qq.com/cgi-bin/externalcontact/add_product_album?access_token=ACCESS_TOKEN
* 文档地址
- *
+ *
* @param wxCpProductAlbumInfo 商品图册信息
* @return 商品id string
* @throws WxErrorException the wx error exception
@@ -1235,7 +1250,7 @@ WxMediaUploadResult uploadAttachment(String mediaType, Integer attachmentType, F
* 请求地址:
* https://qyapi.weixin.qq.com/cgi-bin/externalcontact/update_product_album?access_token=ACCESS_TOKEN
* 文档地址
- *
+ *
* @param wxCpProductAlbumInfo 商品图册信息
* @throws WxErrorException the wx error exception
*/
@@ -1250,7 +1265,7 @@ WxMediaUploadResult uploadAttachment(String mediaType, Integer attachmentType, F
* https://qyapi.weixin.qq.com/cgi-bin/externalcontact/delete_product_album?access_token=ACCESS_TOKEN
*
* 文档地址
- *
+ *
* @param productId 商品id
* @throws WxErrorException the wx error exception
*/
@@ -1379,7 +1394,7 @@ WxMediaUploadResult uploadAttachment(String mediaType, Integer attachmentType, F
* 请求地址:https://qyapi.weixin.qq.com/cgi-bin/externalcontact/customer_acquisition/statistic?access_token=ACCESS_TOKEN
*
* @author Hugo
- * @date 2023/12/5 14:34
+ * @since 2023/12/5 14:34
* @param linkId 获客链接的id
* @param startTime 统计起始时间
* @param endTime 统计结束时间
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpGroupRobotService.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpGroupRobotService.java
index e396ed58ac..b8ccea5e50 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpGroupRobotService.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpGroupRobotService.java
@@ -70,6 +70,23 @@ public interface WxCpGroupRobotService {
*/
void sendMarkdown(String webhookUrl, String content) throws WxErrorException;
+ /**
+ * 发送markdown_v2类型的消息
+ *
+ * @param content markdown内容,最长不超过4096个字节,必须是utf8编码
+ * @throws WxErrorException 异常
+ */
+ void sendMarkdownV2(String content) throws WxErrorException;
+
+ /**
+ * 发送markdown_v2类型的消息
+ *
+ * @param webhookUrl webhook地址
+ * @param content markdown内容,最长不超过4096个字节,必须是utf8编码
+ * @throws WxErrorException 异常
+ */
+ void sendMarkdownV2(String webhookUrl, String content) throws WxErrorException;
+
/**
* 发送image类型的消息
*
@@ -109,9 +126,10 @@ public interface WxCpGroupRobotService {
/**
* 发送模板卡片消息
- * @param webhookUrl
- * @param wxCpGroupRobotMessage
- * @throws WxErrorException
+ *
+ * @param webhookUrl webhook地址
+ * @param wxCpGroupRobotMessage 群机器人消息
+ * @throws WxErrorException 异常
*/
void sendTemplateCardMessage(String webhookUrl, WxCpGroupRobotMessage wxCpGroupRobotMessage) throws WxErrorException;
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpIntelligentRobotService.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpIntelligentRobotService.java
new file mode 100644
index 0000000000..bc5f3f1915
--- /dev/null
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpIntelligentRobotService.java
@@ -0,0 +1,77 @@
+package me.chanjar.weixin.cp.api;
+
+import me.chanjar.weixin.common.error.WxErrorException;
+import me.chanjar.weixin.cp.bean.intelligentrobot.*;
+
+/**
+ * 企业微信智能机器人接口
+ * 官方文档: https://developer.work.weixin.qq.com/document/path/101039
+ *
+ * @author Binary Wang
+ */
+public interface WxCpIntelligentRobotService {
+
+ /**
+ * 创建智能机器人
+ *
+ * @param request 创建请求参数
+ * @return 创建结果
+ * @throws WxErrorException 微信接口异常
+ */
+ WxCpIntelligentRobotCreateResponse createRobot(WxCpIntelligentRobotCreateRequest request) throws WxErrorException;
+
+ /**
+ * 删除智能机器人
+ *
+ * @param robotId 机器人ID
+ * @throws WxErrorException 微信接口异常
+ */
+ void deleteRobot(String robotId) throws WxErrorException;
+
+ /**
+ * 更新智能机器人
+ *
+ * @param request 更新请求参数
+ * @throws WxErrorException 微信接口异常
+ */
+ void updateRobot(WxCpIntelligentRobotUpdateRequest request) throws WxErrorException;
+
+ /**
+ * 查询智能机器人
+ *
+ * @param robotId 机器人ID
+ * @return 机器人信息
+ * @throws WxErrorException 微信接口异常
+ */
+ WxCpIntelligentRobot getRobot(String robotId) throws WxErrorException;
+
+ /**
+ * 智能机器人会话
+ *
+ * @param request 聊天请求参数
+ * @return 聊天响应
+ * @throws WxErrorException 微信接口异常
+ */
+ WxCpIntelligentRobotChatResponse chat(WxCpIntelligentRobotChatRequest request) throws WxErrorException;
+
+ /**
+ * 重置智能机器人会话
+ *
+ * @param robotId 机器人ID
+ * @param userid 用户ID
+ * @param sessionId 会话ID
+ * @throws WxErrorException 微信接口异常
+ */
+ void resetSession(String robotId, String userid, String sessionId) throws WxErrorException;
+
+ /**
+ * 智能机器人主动发送消息
+ * 官方文档: https://developer.work.weixin.qq.com/document/path/100719
+ *
+ * @param request 发送消息请求参数
+ * @return 发送消息响应
+ * @throws WxErrorException 微信接口异常
+ */
+ WxCpIntelligentRobotSendMessageResponse sendMessage(WxCpIntelligentRobotSendMessageRequest request) throws WxErrorException;
+
+}
\ No newline at end of file
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpKfService.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpKfService.java
index 5a53829dc0..046cfbc5bb 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpKfService.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpKfService.java
@@ -222,7 +222,7 @@ WxCpKfCustomerBatchGetResp customerBatchGet(List
+ *
* @param request 查询参数
* @return 客户数据统计 -企业汇总数据
* @throws WxErrorException the wx error exception
@@ -238,7 +238,7 @@ WxCpKfCustomerBatchGetResp customerBatchGet(List
+ *
* @param request 查询参数
* @return 客户数据统计 -企业汇总数据
* @throws WxErrorException the wx error exception
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpLivingService.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpLivingService.java
index a2e2344190..63fabad7a1 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpLivingService.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpLivingService.java
@@ -27,7 +27,7 @@ public interface WxCpLivingService {
/**
* 获取直播详情
* 请求方式:GET(HTTPS)
- * 请求地址:https://qyapi.weixin.qq.com/cgi-bin/living/get_living_info?access_token=ACCESS_TOKEN&livingid=LIVINGID
+ * {@code 请求地址:https://qyapi.weixin.qq.com/cgi-bin/living/get_living_info?access_token=ACCESS_TOKEN&livingid=LIVINGID}
*
* @param livingId 直播id
* @return 获取的直播详情 living info
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpMediaService.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpMediaService.java
index e874b26f42..dd5ce594b2 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpMediaService.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpMediaService.java
@@ -110,9 +110,9 @@ WxMediaUploadResult upload(String mediaType, String filename, String url)
* 获取高清语音素材.
* 可以使用本接口获取从JSSDK的uploadVoice接口上传的临时语音素材,格式为speex,16K采样率。该音频比上文的临时素材获取接口(格式为amr,8K采样率)更加清晰,适合用作语音识别等对音质要求较高的业务。
* 请求方式:GET(HTTPS)
- * 请求地址:https://qyapi.weixin.qq.com/cgi-bin/media/get/jssdk?access_token=ACCESS_TOKEN&media_id=MEDIA_ID
+ * {@code 请求地址:https://qyapi.weixin.qq.com/cgi-bin/media/get/jssdk?access_token=ACCESS_TOKEN&media_id=MEDIA_ID}
* 仅企业微信2.4及以上版本支持。
- * 文档地址:https://work.weixin.qq.com/api/doc#90000/90135/90255
+ * 文档地址:https://work.weixin.qq.com/api/doc/90000/90135/90255
*
* 请求方式:GET(HTTPS)
- * 请求地址:https://qyapi.weixin.qq.com/cgi-bin/school/getuserinfo?access_token=ACCESS_TOKEN&code=CODE
+ * {@code 请求地址:https://qyapi.weixin.qq.com/cgi-bin/school/getuserinfo?access_token=ACCESS_TOKEN&code=CODE}
+ *
*
* @param code the code
* @return school user info
@@ -123,7 +124,7 @@ public interface WxCpOAuth2Service {
/**
*
* 获取用户登录身份
- * https://qyapi.weixin.qq.com/cgi-bin/auth/getuserinfo?access_token=ACCESS_TOKEN&code=CODE
+ * {@code https://qyapi.weixin.qq.com/cgi-bin/auth/getuserinfo?access_token=ACCESS_TOKEN&code=CODE}
* 该接口可使用用户登录成功颁发的code来获取成员信息,适用于自建应用与代开发应用
*
* 注意: 旧的/user/getuserinfo 接口的url已变更为auth/getuserinfo,不过旧接口依旧可以使用,建议是关注新接口即可
@@ -140,13 +141,15 @@ public interface WxCpOAuth2Service {
/**
* 获取用户二次验证信息
- *
*
* @param wxCpOaMeetingRoomBookingInfoRequest 会议室预定信息查询对象
+ * @return 会议室预定信息
* @throws WxErrorException .
*/
WxCpOaMeetingRoomBookingInfoResult getMeetingRoomBookingInfo(WxCpOaMeetingRoomBookingInfoRequest wxCpOaMeetingRoomBookingInfoRequest) throws WxErrorException;
@@ -99,6 +100,7 @@ public interface WxCpOaMeetingRoomService {
*
*
* @param wxCpOaMeetingRoomBookRequest 会议室预定对象
+ * @return 预定结果
* @throws WxErrorException .
*/
WxCpOaMeetingRoomBookResult bookingMeetingRoom(WxCpOaMeetingRoomBookRequest wxCpOaMeetingRoomBookRequest) throws WxErrorException;
@@ -114,6 +116,7 @@ public interface WxCpOaMeetingRoomService {
*
*
* @param wxCpOaMeetingRoomBookByScheduleRequest 会议室预定对象
+ * @return 预定结果
* @throws WxErrorException .
*/
WxCpOaMeetingRoomBookResult bookingMeetingRoomBySchedule(WxCpOaMeetingRoomBookByScheduleRequest wxCpOaMeetingRoomBookByScheduleRequest) throws WxErrorException;
@@ -129,6 +132,7 @@ public interface WxCpOaMeetingRoomService {
*
*
* @param wxCpOaMeetingRoomBookByMeetingRequest 会议室预定对象
+ * @return 预定结果
* @throws WxErrorException .
*/
WxCpOaMeetingRoomBookResult bookingMeetingRoomByMeeting(WxCpOaMeetingRoomBookByMeetingRequest wxCpOaMeetingRoomBookByMeetingRequest) throws WxErrorException;
@@ -147,10 +151,10 @@ public interface WxCpOaMeetingRoomService {
* @param wxCpOaMeetingRoomCancelBookRequest 取消预定会议室对象
* @throws WxErrorException .
*/
- void cancelBookMeetingRoom(WxCpOaMeetingRoomCancelBookRequest wxCpOaMeetingRoomCancelBookRequest) throws WxErrorException;
+ void cancelBookMeetingRoom(WxCpOaMeetingRoomCancelBookRequest wxCpOaMeetingRoomCancelBookRequest) throws WxErrorException;
- /**
+ /**
* 根据会议室预定ID查询预定详情.
*
* api: https://qyapi.weixin.qq.com/cgi-bin/auth/get_tfa_info?access_token=ACCESS_TOKEN
* 权限说明:仅『通讯录同步』或者自建应用可调用,如用自建应用调用,用户需要在二次验证范围和应用可见范围内。
* 并发限制:20
+ *
*
* @param code 用户进入二次验证页面时,企业微信颁发的code,每次成员授权带上的code将不一样,code只能使用一次,5分钟未被使用自动过期
- * @return me.chanjar.weixin.cp.bean.workbench.WxCpSecondVerificationInfo 二次验证授权码,开发者可以调用通过二次验证接口,解锁企业微信终端.tfa_code有效期五分钟,且只能使用一次。
+ * @return 二次验证授权码,开发者可以调用通过二次验证接口,解锁企业微信终端.tfa_code有效期五分钟,且只能使用一次。
+ * @throws WxErrorException 微信错误异常
*/
WxCpSecondVerificationInfo getTfaInfo(String code) throws WxErrorException;
}
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpOaMeetingRoomService.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpOaMeetingRoomService.java
index c2e6c5c872..cc039fd9f5 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpOaMeetingRoomService.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpOaMeetingRoomService.java
@@ -84,6 +84,7 @@ public interface WxCpOaMeetingRoomService {
*
* 企业可通过此接口根据预定id查询相关会议室的预定情况
@@ -161,8 +165,9 @@ public interface WxCpOaMeetingRoomService {
*
*
* @param wxCpOaMeetingRoomBookingInfoByBookingIdRequest 根据会议室预定ID查询预定详情对象
+ * @return 预定详情
* @throws WxErrorException .
*/
- WxCpOaMeetingRoomBookingInfoByBookingIdResult getBookingInfoByBookingId(WxCpOaMeetingRoomBookingInfoByBookingIdRequest wxCpOaMeetingRoomBookingInfoByBookingIdRequest) throws WxErrorException;
+ WxCpOaMeetingRoomBookingInfoByBookingIdResult getBookingInfoByBookingId(WxCpOaMeetingRoomBookingInfoByBookingIdRequest wxCpOaMeetingRoomBookingInfoByBookingIdRequest) throws WxErrorException;
}
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpOaService.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpOaService.java
index ee57107b5c..3494dcfa4e 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpOaService.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpOaService.java
@@ -11,7 +11,8 @@
/**
* 企业微信OA相关接口.
*
- * @author Element & Wang_Wong created on 2019-04-06 10:52
+ * @author Element, Wang_Wong
+ * @since 2019-04-06 10:52
*/
public interface WxCpOaService {
@@ -331,7 +332,7 @@ List
+ *
* @param userId 需要录入的用户id
* @param userFace 需要录入的人脸图片数据,需要将图片数据base64处理后填入,对已录入的人脸会进行更新处理
* @throws WxErrorException the wx error exception
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpOaWeDocService.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpOaWeDocService.java
index 1356c839b2..d63d32694a 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpOaWeDocService.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpOaWeDocService.java
@@ -78,4 +78,53 @@ public interface WxCpOaWeDocService {
* @throws WxErrorException the wx error exception
*/
WxCpDocShare docShare(@NonNull String docId) throws WxErrorException;
+
+ /**
+ * 编辑表格内容
+ * 该接口可以对一个在线表格批量执行多个更新操作
+ *
* 请求方式:GET(HTTPS)
- * 请求地址:https://qyapi.weixin.qq.com/cgi-bin/school/living/get_living_info?access_token=ACCESS_TOKEN&livingid
- * =LIVINGID
+ * {@code 请求地址:https://qyapi.weixin.qq.com/cgi-bin/school/living/get_living_info?access_token=ACCESS_TOKEN&livingid=LIVINGID}
+ *
*
* @param livingId the living id
* @return living info
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpSchoolUserService.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpSchoolUserService.java
index a92bfcc100..d004ca8aa5 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpSchoolUserService.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpSchoolUserService.java
@@ -19,9 +19,10 @@ public interface WxCpSchoolUserService {
/**
* 获取访问用户身份
* 该接口用于根据code获取成员信息
- *
* 请求方式:GET(HTTPS)
- * 请求地址:https://qyapi.weixin.qq.com/cgi-bin/user/getuserinfo?access_token=ACCESS_TOKEN&code=CODE
+ * {@code 请求地址:https://qyapi.weixin.qq.com/cgi-bin/user/getuserinfo?access_token=ACCESS_TOKEN&code=CODE}
+ *
*
* @param code the code
* @return user info
@@ -32,9 +33,10 @@ public interface WxCpSchoolUserService {
/**
* 获取家校访问用户身份
* 该接口用于根据code获取家长或者学生信息
- *
* 请求方式:GET(HTTPS)
- * 请求地址:https://qyapi.weixin.qq.com/cgi-bin/school/getuserinfo?access_token=ACCESS_TOKEN&code=CODE
+ * {@code 请求地址:https://qyapi.weixin.qq.com/cgi-bin/school/getuserinfo?access_token=ACCESS_TOKEN&code=CODE}
+ *
*
* @param code the code
* @return school user info
@@ -90,8 +92,10 @@ public interface WxCpSchoolUserService {
/**
* 删除学生
+ *
* 请求方式:GET(HTTPS)
- * 请求地址:https://qyapi.weixin.qq.com/cgi-bin/school/user/delete_student?access_token=ACCESS_TOKEN&userid=USERID
+ * {@code 请求地址:https://qyapi.weixin.qq.com/cgi-bin/school/user/delete_student?access_token=ACCESS_TOKEN&userid=USERID}
+ *
*
* @param studentUserId the student user id
* @return wx cp base resp
@@ -160,8 +164,10 @@ WxCpBaseResp updateStudent(@NonNull String studentUserId, String newStudentUserI
/**
* 读取学生或家长
+ *
* 请求方式:GET(HTTPS)
- * 请求地址:https://qyapi.weixin.qq.com/cgi-bin/school/user/get?access_token=ACCESS_TOKEN&userid=USERID
+ * {@code 请求地址:https://qyapi.weixin.qq.com/cgi-bin/school/user/get?access_token=ACCESS_TOKEN&userid=USERID}
+ *
*
* @param userId the user id
* @return user
@@ -171,9 +177,10 @@ WxCpBaseResp updateStudent(@NonNull String studentUserId, String newStudentUserI
/**
* 获取部门成员详情
+ *
* 请求方式:GET(HTTPS)
- * 请求地址:https://qyapi.weixin.qq.com/cgi-bin/school/user/list?access_token=ACCESS_TOKEN&department_id=DEPARTMENT_ID
- * &fetch_child=FETCH_CHILD
+ * {@code 请求地址:https://qyapi.weixin.qq.com/cgi-bin/school/user/list?access_token=ACCESS_TOKEN&department_id=DEPARTMENT_ID&fetch_child=FETCH_CHILD}
+ *
*
* @param departmentId 获取的部门id
* @param fetchChild 1/0:是否递归获取子部门下面的成员
@@ -184,9 +191,10 @@ WxCpBaseResp updateStudent(@NonNull String studentUserId, String newStudentUserI
/**
* 获取部门家长详情
+ *
* 请求方式:GET(HTTPS)
- * 请求地址:https://qyapi.weixin.qq.com/cgi-bin/school/user/list_parent?access_token=ACCESS_TOKEN&department_id
- * =DEPARTMENT_ID
+ * {@code 请求地址:https://qyapi.weixin.qq.com/cgi-bin/school/user/list_parent?access_token=ACCESS_TOKEN&department_id=DEPARTMENT_ID}
+ *
*
* @param departmentId 获取的部门id
* @return user list parent
@@ -207,8 +215,10 @@ WxCpBaseResp updateStudent(@NonNull String studentUserId, String newStudentUserI
/**
* 删除家长
+ *
* 请求方式:GET(HTTPS)
- * 请求地址:https://qyapi.weixin.qq.com/cgi-bin/school/user/delete_parent?access_token=ACCESS_TOKEN&userid=USERID
+ * {@code 请求地址:https://qyapi.weixin.qq.com/cgi-bin/school/user/delete_parent?access_token=ACCESS_TOKEN&userid=USERID}
+ *
*
* @param userId the user id
* @return wx cp base resp
@@ -256,7 +266,7 @@ WxCpBaseResp updateStudent(@NonNull String studentUserId, String newStudentUserI
/**
* 删除部门
* 请求方式:GET(HTTPS)
- * 请求地址:https://qyapi.weixin.qq.com/cgi-bin/school/department/delete?access_token=ACCESS_TOKEN&id=ID
+ * {@code 请求地址:https://qyapi.weixin.qq.com/cgi-bin/school/department/delete?access_token=ACCESS_TOKEN&id=ID}
*
* @param id the id
* @return wx cp base resp
@@ -292,10 +302,9 @@ WxCpBaseResp updateStudent(@NonNull String studentUserId, String newStudentUserI
/**
* 获取外部联系人详情
* 学校可通过此接口,根据外部联系人的userid(如何获取?),拉取外部联系人详情。
- *
* 获取部门成员详情
* 请求方式:GET(HTTPS)
- * 请求地址:https://qyapi.weixin.qq.com/cgi-bin/user/list?access_token=ACCESS_TOKEN&department_id=DEPARTMENT_ID&fetch_child=FETCH_CHILD
+ * {@code 请求地址:https://qyapi.weixin.qq.com/cgi-bin/user/list?access_token=ACCESS_TOKEN&department_id=DEPARTMENT_ID&fetch_child=FETCH_CHILD}
*
* 文档地址:https://work.weixin.qq.com/api/doc/90000/90135/90201
*
@@ -213,7 +213,7 @@ public interface WxCpUserService {
* 获取加入企业二维码。
*
* 请求方式:GET(HTTPS)
- * 请求地址:https://qyapi.weixin.qq.com/cgi-bin/corp/get_join_qrcode?access_token=ACCESS_TOKEN&size_type=SIZE_TYPE
+ * {@code 请求地址:https://qyapi.weixin.qq.com/cgi-bin/corp/get_join_qrcode?access_token=ACCESS_TOKEN&size_type=SIZE_TYPE}
*
* 文档地址:https://work.weixin.qq.com/api/doc/90000/90135/91714
*
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/BaseWxCpServiceImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/BaseWxCpServiceImpl.java
index d0b7441d90..bc18c9bc7a 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/BaseWxCpServiceImpl.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/BaseWxCpServiceImpl.java
@@ -74,6 +74,7 @@ public abstract class BaseWxCpServiceImpl
+ *
*
*
+ *
*
*
* 请使用.
- * {@link LinkedCorpMsgType#TEXT}
- * {@link LinkedCorpMsgType#IMAGE}
- * {@link LinkedCorpMsgType#VIDEO}
- * {@link LinkedCorpMsgType#NEWS}
- * {@link LinkedCorpMsgType#MPNEWS}
- * {@link LinkedCorpMsgType#MARKDOWN}
- * {@link LinkedCorpMsgType#MINIPROGRAM_NOTICE}
+ * {@link me.chanjar.weixin.cp.constant.WxCpConsts.LinkedCorpMsgType#TEXT}
+ * {@link me.chanjar.weixin.cp.constant.WxCpConsts.LinkedCorpMsgType#IMAGE}
+ * {@link me.chanjar.weixin.cp.constant.WxCpConsts.LinkedCorpMsgType#VIDEO}
+ * {@link me.chanjar.weixin.cp.constant.WxCpConsts.LinkedCorpMsgType#NEWS}
+ * {@link me.chanjar.weixin.cp.constant.WxCpConsts.LinkedCorpMsgType#MPNEWS}
+ * {@link me.chanjar.weixin.cp.constant.WxCpConsts.LinkedCorpMsgType#MARKDOWN}
+ * {@link me.chanjar.weixin.cp.constant.WxCpConsts.LinkedCorpMsgType#MINIPROGRAM_NOTICE}
*
*
* @param msgType 消息类型
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/message/WxCpTpXmlMessage.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/message/WxCpTpXmlMessage.java
index e26b152daf..122bc26272 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/message/WxCpTpXmlMessage.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/message/WxCpTpXmlMessage.java
@@ -793,4 +793,6 @@ public static WxCpTpXmlMessage fromEncryptedXml(String encryptedXml, WxCpTpConfi
log.debug("解密后的原始xml消息内容:{}", plainText);
return fromXml(plainText);
}
+
+
}
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/message/WxCpXmlMessage.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/message/WxCpXmlMessage.java
index 2313bcb516..08a0936317 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/message/WxCpXmlMessage.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/message/WxCpXmlMessage.java
@@ -68,19 +68,19 @@ public class WxCpXmlMessage implements Serializable {
/**
*
* 当接受用户消息时,可能会获得以下值:
- * {@link WxConsts.XmlMsgType#TEXT}
- * {@link WxConsts.XmlMsgType#IMAGE}
- * {@link WxConsts.XmlMsgType#VOICE}
- * {@link WxConsts.XmlMsgType#VIDEO}
- * {@link WxConsts.XmlMsgType#LOCATION}
- * {@link WxConsts.XmlMsgType#LINK}
- * {@link WxConsts.XmlMsgType#EVENT}
+ * {@link me.chanjar.weixin.common.api.WxConsts.XmlMsgType#TEXT}
+ * {@link me.chanjar.weixin.common.api.WxConsts.XmlMsgType#IMAGE}
+ * {@link me.chanjar.weixin.common.api.WxConsts.XmlMsgType#VOICE}
+ * {@link me.chanjar.weixin.common.api.WxConsts.XmlMsgType#VIDEO}
+ * {@link me.chanjar.weixin.common.api.WxConsts.XmlMsgType#LOCATION}
+ * {@link me.chanjar.weixin.common.api.WxConsts.XmlMsgType#LINK}
+ * {@link me.chanjar.weixin.common.api.WxConsts.XmlMsgType#EVENT}
* 当发送消息的时候使用:
- * {@link WxConsts.XmlMsgType#TEXT}
- * {@link WxConsts.XmlMsgType#IMAGE}
- * {@link WxConsts.XmlMsgType#VOICE}
- * {@link WxConsts.XmlMsgType#VIDEO}
- * {@link WxConsts.XmlMsgType#NEWS}
+ * {@link me.chanjar.weixin.common.api.WxConsts.XmlMsgType#TEXT}
+ * {@link me.chanjar.weixin.common.api.WxConsts.XmlMsgType#IMAGE}
+ * {@link me.chanjar.weixin.common.api.WxConsts.XmlMsgType#VOICE}
+ * {@link me.chanjar.weixin.common.api.WxConsts.XmlMsgType#VIDEO}
+ * {@link me.chanjar.weixin.common.api.WxConsts.XmlMsgType#NEWS}
*
*/
@XStreamAlias("MsgType")
@@ -92,7 +92,7 @@ public class WxCpXmlMessage implements Serializable {
private String content;
@XStreamAlias("MsgId")
- private Long msgId;
+ private String msgId;
@XStreamAlias("PicUrl")
@XStreamConverter(value = XStreamCDataConverter.class)
@@ -157,7 +157,15 @@ public class WxCpXmlMessage implements Serializable {
@XStreamAlias("MemChangeList")
@XStreamConverter(value = XStreamCDataConverter.class)
- private String MemChangeList;
+ private String memChangeList;
+
+ @XStreamAlias("LastMemVer")
+ @XStreamConverter(value = XStreamCDataConverter.class)
+ private String lastMemVer;
+
+ @XStreamAlias("CurMemVer")
+ @XStreamConverter(value = XStreamCDataConverter.class)
+ private String curMemVer;
@XStreamAlias("Source")
@XStreamConverter(value = XStreamCDataConverter.class)
@@ -245,6 +253,24 @@ public class WxCpXmlMessage implements Serializable {
@XStreamConverter(value = XStreamCDataConverter.class)
private String linkId;
+ /**
+ * 智能机器人ID
+ * 接收智能机器人消息时使用
+ * https://developer.work.weixin.qq.com/document/path/100719
+ */
+ @XStreamAlias("RobotId")
+ @XStreamConverter(value = XStreamCDataConverter.class)
+ private String robotId;
+
+ /**
+ * 智能机器人会话ID
+ * 接收智能机器人消息时使用,用于保持会话连续性
+ * https://developer.work.weixin.qq.com/document/path/100719
+ */
+ @XStreamAlias("SessionId")
+ @XStreamConverter(value = XStreamCDataConverter.class)
+ private String sessionId;
+
/**
* 通讯录变更事件.
* 请参考常量 me.chanjar.weixin.cp.constant.WxCpConsts.ContactChangeType
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/messagebuilder/TemplateCardBuilder.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/messagebuilder/TemplateCardBuilder.java
index d3cbb89a3d..1ab92630a9 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/messagebuilder/TemplateCardBuilder.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/messagebuilder/TemplateCardBuilder.java
@@ -12,7 +12,8 @@
* 用法: WxCustomMessage m = WxCustomMessage.TEMPLATECARD().title(...)....toUser(...).build();
*
*
- * @author yzts a> created on 2019-05-16
+ * @author yzts
+ * @since 2019-05-16
*/
public class TemplateCardBuilder extends BaseBuilder
+ *
* 取值范围:-604800 ~ 86399
*/
@SerializedName("remind_time_diffs")
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/applydata/ContentValue.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/applydata/ContentValue.java
index 158206867e..92ec8a43e8 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/applydata/ContentValue.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/applydata/ContentValue.java
@@ -34,6 +34,9 @@ public class ContentValue implements Serializable {
private List
+ *
* 会议室冲突日期列表,为当天0点的时间戳;使用重复会议预定会议室,部分日期与会议室预定情况冲突时返回
*/
@SerializedName("conflict_date")
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/templatedata/TemplateOptions.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/templatedata/TemplateOptions.java
index 32ada7b338..8b1605a5a1 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/templatedata/TemplateOptions.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/templatedata/TemplateOptions.java
@@ -3,6 +3,7 @@
import lombok.Data;
import java.io.Serializable;
+import java.util.List;
/**
* The type Template options.
@@ -17,11 +18,8 @@ public class TemplateOptions implements Serializable {
private String key;
/**
- * 创建审批模板,value为对象类型
- * https://developer.work.weixin.qq.com/document/path/97437#%E9%99%845-%E5%8D%95%E9%80%89%E5%A4%9A%E9%80%89%E6%8E%A7%E4%BB%B6%EF%BC%88control%E5%8F%82%E6%95%B0%E4%B8%BAselector%EF%BC%89
- *
- * 获取审批模板详情,value为list类型
- * https://developer.work.weixin.qq.com/document/path/91982
+ * 创建审批模板,value为对象类型
+ * 获取审批模板详情,value为list类型
**/
- private TemplateTitle value;
+ private List
+ * 服务商可通过本接口获取服务商所拥有的应用模板列表
+ * 文档地址:https://developer.work.weixin.qq.com/document/path/97111
+ *
+ *
+ * @return 应用模板列表
+ * @throws WxErrorException 微信错误异常
+ */
+ WxCpTpTemplateList getTemplateList() throws WxErrorException;
+
+ /**
+ * 获取代开发应用详情
+ *
+ * 服务商可通过本接口获取某个授权企业中已经安装的代开发自建应用的详情
+ * 文档地址:https://developer.work.weixin.qq.com/document/path/97111
+ *
+ *
+ * @param authCorpId 授权企业的corpid
+ * @param agentId 应用的agentid,为空时则返回该企业所有的代开发自建应用详情
+ * @return 代开发应用详情
+ * @throws WxErrorException 微信错误异常
+ */
+ WxCpTpCustomizedAppDetail getCustomizedAppDetail(String authCorpId, Integer agentId) throws WxErrorException;
+
+}
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/WxCpTpEditionService.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/WxCpTpEditionService.java
index 2c2f11628b..d539f03bca 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/WxCpTpEditionService.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/WxCpTpEditionService.java
@@ -15,7 +15,7 @@ public interface WxCpTpEditionService {
* 延长试用期
*
*
- * 获取审批申请详情
+ * 获取审批申请详情
*
* @param spNo 审批单编号。
* @param corpId the corp id
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/WxCpTpOrderService.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/WxCpTpOrderService.java
index 3aff90bb56..6e0acb7dee 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/WxCpTpOrderService.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/WxCpTpOrderService.java
@@ -18,7 +18,7 @@ public interface WxCpTpOrderService {
* 获取订单详情
*
* 文档地址 - *
+ * * * @param startTime 起始时间 * @param endTime 终止时间 diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/WxCpTpService.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/WxCpTpService.java index 5c433c0b49..92966c1d03 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/WxCpTpService.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/WxCpTpService.java @@ -8,6 +8,7 @@ import me.chanjar.weixin.common.util.http.RequestExecutor; import me.chanjar.weixin.common.util.http.RequestHttp; import me.chanjar.weixin.cp.bean.*; +import me.chanjar.weixin.cp.bean.message.WxCpTpXmlMessage; import me.chanjar.weixin.cp.config.WxCpTpConfigStorage; import java.util.List; @@ -186,6 +187,8 @@ public interface WxCpTpService { @Deprecated WxCpTpCorp getPermanentCode(String authCode) throws WxErrorException; + WxCpTpCorp getV2PermanentCode(String authCode) throws WxErrorException; + /** * 获取企业永久授权码信息 *
@@ -196,10 +199,12 @@ public interface WxCpTpService {
* @return permanent code info
* @throws WxErrorException the wx error exception
* @author yuan
- * @since 2020 -03-18
+ * @since 2020-03-18
*/
WxCpTpPermanentCodeInfo getPermanentCodeInfo(String authCode) throws WxErrorException;
+ WxCpTpPermanentCodeInfo getV2PermanentCodeInfo(String authCode) throws WxErrorException;
+
/**
*
* 获取预授权链接
@@ -222,7 +227,7 @@ public interface WxCpTpService {
* @param authType 授权类型:0 正式授权, 1 测试授权。
* @return pre auth url
* @throws WxErrorException the wx error exception
- * @link https ://work.weixin.qq.com/api/doc/90001/90143/90602
+ * @see 文档地址
*/
String getPreAuthUrl(String redirectUri, String state, int authType) throws WxErrorException;
@@ -343,9 +348,7 @@ public interface WxCpTpService {
* 获取WxCpTpConfigStorage 对象.
*
* @return WxCpTpConfigStorage wx cp tp config storage
- * @deprecated storage应该在service内部使用 ,提供这个接口,容易破坏这个封装
*/
- @Deprecated
WxCpTpConfigStorage getWxCpTpConfigStorage();
/**
@@ -527,6 +530,11 @@ public interface WxCpTpService {
*/
WxCpTpLicenseService getWxCpTpLicenseService();
+ WxCpTpXmlMessage fromEncryptedXml(String encryptedXml,
+ String timestamp, String nonce, String msgSignature);
+
+ String getVerifyDecrypt(String sVerifyEchoStr);
+
/**
* 获取应用的管理员列表
*
@@ -550,12 +558,12 @@ public interface WxCpTpService {
WxCpTpAppQrcode getAppQrcode(String suiteId, String appId, String state, Integer style, Integer resultType) throws WxErrorException ;
/**
- *
* 明文corpid转换为加密corpid 为更好地保护企业与用户的数据,第三方应用获取的corpid不再是明文的corpid,将升级为第三方服务商级别的加密corpid。文档说明
* 第三方可以将已有的明文corpid转换为第三方的加密corpid。
- * @param corpId
- * @return
- * @throws WxErrorException
+ *
+ * @param corpId 企业ID
+ * @return 加密的企业ID
+ * @throws WxErrorException 微信错误异常
*/
WxCpTpCorpId2OpenCorpId corpId2OpenCorpId(String corpId) throws WxErrorException;
@@ -647,9 +655,25 @@ public interface WxCpTpService {
/**
* 构造第三方应用oauth2链接
+ *
+ * @return OAuth2服务
*/
WxCpTpOAuth2Service getWxCpTpOAuth2Service();
void setWxCpTpOAuth2Service(WxCpTpOAuth2Service wxCpTpOAuth2Service);
+ /**
+ * 获取代开发服务
+ *
+ * @return 代开发服务
+ */
+ WxCpTpCustomizedService getWxCpTpCustomizedService();
+
+ /**
+ * 设置代开发服务
+ *
+ * @param wxCpTpCustomizedService 代开发服务
+ */
+ void setWxCpTpCustomizedService(WxCpTpCustomizedService wxCpTpCustomizedService);
+
}
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/WxCpTpTagService.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/WxCpTpTagService.java
index b508df59a1..df60041865 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/WxCpTpTagService.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/WxCpTpTagService.java
@@ -13,7 +13,7 @@
*
*
* @author zhangq
- * @since 2021 -02-14 16:02
+ * @since 2021-02-14 16:02
*/
public interface WxCpTpTagService {
/**
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/impl/BaseWxCpTpServiceImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/impl/BaseWxCpTpServiceImpl.java
index 9d620264c4..25c1470eb2 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/impl/BaseWxCpTpServiceImpl.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/impl/BaseWxCpTpServiceImpl.java
@@ -25,8 +25,10 @@
import me.chanjar.weixin.common.util.json.GsonParser;
import me.chanjar.weixin.common.util.json.WxGsonBuilder;
import me.chanjar.weixin.cp.bean.*;
+import me.chanjar.weixin.cp.bean.message.WxCpTpXmlMessage;
import me.chanjar.weixin.cp.config.WxCpTpConfigStorage;
import me.chanjar.weixin.cp.tp.service.*;
+import me.chanjar.weixin.cp.util.crypto.WxCpTpCryptUtil;
import org.apache.commons.lang3.StringUtils;
import java.io.File;
@@ -58,6 +60,7 @@ public abstract class BaseWxCpTpServiceImpl implements WxCpTpService, Requ
private WxCpTpLicenseService wxCpTpLicenseService = new WxCpTpLicenseServiceImpl(this);
private WxCpTpIdConvertService wxCpTpIdConvertService = new WxCpTpIdConvertServiceImpl(this);
private WxCpTpOAuth2Service wxCpTpOAuth2Service = new WxCpTpOAuth2ServiceImpl(this);
+ private WxCpTpCustomizedService wxCpTpCustomizedService = new WxCpTpCustomizedServiceImpl(this);
/**
* 全局的是否正在刷新access token的锁.
*/
@@ -91,6 +94,7 @@ public abstract class BaseWxCpTpServiceImpl implements WxCpTpService, Requ
private final WxSessionManager sessionManager = new StandardSessionManager();
+
/**
* 临时文件目录.
*/
@@ -259,6 +263,18 @@ public WxCpTpCorp getPermanentCode(String authCode) throws WxErrorException {
return wxCpTpCorp;
}
+ @Override
+ public WxCpTpCorp getV2PermanentCode(String authCode) throws WxErrorException {
+ JsonObject jsonObject = new JsonObject();
+ jsonObject.addProperty("auth_code", authCode);
+
+ String result = post(configStorage.getApiUrl(GET_V2_PERMANENT_CODE), jsonObject.toString());
+ jsonObject = GsonParser.parse(result);
+ WxCpTpCorp wxCpTpCorp = WxCpTpCorp.fromJson(jsonObject.get("auth_corp_info").getAsJsonObject().toString());
+ wxCpTpCorp.setPermanentCode(jsonObject.get("permanent_code").getAsString());
+ return wxCpTpCorp;
+ }
+
@Override
public WxCpTpPermanentCodeInfo getPermanentCodeInfo(String authCode) throws WxErrorException {
JsonObject jsonObject = new JsonObject();
@@ -267,6 +283,14 @@ public WxCpTpPermanentCodeInfo getPermanentCodeInfo(String authCode) throws WxEr
return WxCpTpPermanentCodeInfo.fromJson(result);
}
+ @Override
+ public WxCpTpPermanentCodeInfo getV2PermanentCodeInfo(String authCode) throws WxErrorException {
+ JsonObject jsonObject = new JsonObject();
+ jsonObject.addProperty("auth_code", authCode);
+ String result = post(configStorage.getApiUrl(GET_V2_PERMANENT_CODE), jsonObject.toString());
+ return WxCpTpPermanentCodeInfo.fromJson(result);
+ }
+
@Override
@SneakyThrows
public String getPreAuthUrl(String redirectUri, String state) throws WxErrorException {
@@ -452,7 +476,7 @@ protected T executeInternal(RequestExecutor executor, String uri, E
if (error.getErrorCode() == WxCpErrorMsgEnum.CODE_42009.getCode()) {
// 强制设置wxCpTpConfigStorage它的suite access token过期了,这样在下一次请求里就会刷新suite access token
this.configStorage.expireSuiteAccessToken();
- if (this.getWxCpTpConfigStorage().autoRefreshToken()) {
+ if (this.configStorage.autoRefreshToken()) {
log.warn("即将重新获取新的access_token,错误代码:{},错误信息:{}", error.getErrorCode(), error.getErrorMsg());
return this.execute(executor, uri, data);
}
@@ -646,6 +670,27 @@ public void setWxCpTpUserService(WxCpTpUserService wxCpTpUserService) {
this.wxCpTpUserService = wxCpTpUserService;
}
+
+ /**
+ *
+ * @param encryptedXml the encrypted xml
+ * @param timestamp the timestamp
+ * @param nonce the nonce
+ * @param msgSignature the msg signature
+ * @return the wx cp tp xml message
+ */
+ @Override
+ public WxCpTpXmlMessage fromEncryptedXml(String encryptedXml,
+ String timestamp, String nonce, String msgSignature) {
+ return WxCpTpXmlMessage.fromEncryptedXml(encryptedXml,this.configStorage,timestamp,nonce,msgSignature);
+ }
+
+ @Override
+ public String getVerifyDecrypt(String sVerifyEchoStr) {
+ WxCpTpCryptUtil cryptUtil = new WxCpTpCryptUtil(this.configStorage);
+ return cryptUtil.decrypt(sVerifyEchoStr);
+ }
+
@Override
public WxCpTpAdmin getAdminList(String authCorpId, Integer agentId) throws WxErrorException {
JsonObject jsonObject = new JsonObject();
@@ -764,4 +809,19 @@ public WxCpTpOAuth2Service getWxCpTpOAuth2Service() {
public void setWxCpTpOAuth2Service(WxCpTpOAuth2Service wxCpTpOAuth2Service) {
this.wxCpTpOAuth2Service = wxCpTpOAuth2Service;
}
+
+ @Override
+ public WxCpTpCustomizedService getWxCpTpCustomizedService() {
+ return wxCpTpCustomizedService;
+ }
+
+ @Override
+ public void setWxCpTpCustomizedService(WxCpTpCustomizedService wxCpTpCustomizedService) {
+ this.wxCpTpCustomizedService = wxCpTpCustomizedService;
+ }
+
+ @Override
+ public WxCpTpConfigStorage getWxCpTpConfigStorage() {
+ return this.configStorage;
+ }
}
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/impl/WxCpTpCustomizedServiceImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/impl/WxCpTpCustomizedServiceImpl.java
new file mode 100644
index 0000000000..54ea0fc4bc
--- /dev/null
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/impl/WxCpTpCustomizedServiceImpl.java
@@ -0,0 +1,64 @@
+package me.chanjar.weixin.cp.tp.service.impl;
+
+import com.google.gson.JsonObject;
+import lombok.RequiredArgsConstructor;
+import me.chanjar.weixin.common.error.WxErrorException;
+import me.chanjar.weixin.cp.bean.WxCpTpCustomizedAppDetail;
+import me.chanjar.weixin.cp.bean.WxCpTpTemplateList;
+import me.chanjar.weixin.cp.config.WxCpTpConfigStorage;
+import me.chanjar.weixin.cp.tp.service.WxCpTpCustomizedService;
+import me.chanjar.weixin.cp.tp.service.WxCpTpService;
+
+import static me.chanjar.weixin.cp.constant.WxCpApiPathConsts.Tp.GET_CUSTOMIZED_APP_DETAIL;
+import static me.chanjar.weixin.cp.constant.WxCpApiPathConsts.Tp.GET_TEMPLATE_LIST;
+
+/**
+ * 企业微信第三方应用 - 代开发相关接口实现.
+ *
+ * @author Binary Wang
+ * created on 2026-01-14
+ */
+@RequiredArgsConstructor
+public class WxCpTpCustomizedServiceImpl implements WxCpTpCustomizedService {
+
+ private final WxCpTpService mainService;
+
+ @Override
+ public WxCpTpTemplateList getTemplateList() throws WxErrorException {
+ String responseText = this.mainService.get(getWxCpTpConfigStorage().getApiUrl(GET_TEMPLATE_LIST)
+ + getProviderAccessToken(), null, true);
+ return WxCpTpTemplateList.fromJson(responseText);
+ }
+
+ @Override
+ public WxCpTpCustomizedAppDetail getCustomizedAppDetail(String authCorpId, Integer agentId) throws WxErrorException {
+ JsonObject jsonObject = new JsonObject();
+ jsonObject.addProperty("auth_corpid", authCorpId);
+ if (agentId != null) {
+ jsonObject.addProperty("agentid", agentId);
+ }
+ String responseText = this.mainService.post(getWxCpTpConfigStorage().getApiUrl(GET_CUSTOMIZED_APP_DETAIL)
+ + getProviderAccessToken(), jsonObject.toString(), true);
+ return WxCpTpCustomizedAppDetail.fromJson(responseText);
+ }
+
+ /**
+ * 获取provider_access_token参数
+ *
+ * @return provider_access_token参数
+ * @throws WxErrorException 微信错误异常
+ */
+ private String getProviderAccessToken() throws WxErrorException {
+ return "?provider_access_token=" + mainService.getWxCpProviderToken();
+ }
+
+ /**
+ * 获取tp参数配置
+ *
+ * @return config
+ */
+ private WxCpTpConfigStorage getWxCpTpConfigStorage() {
+ return mainService.getWxCpTpConfigStorage();
+ }
+
+}
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/impl/WxCpTpEditionServiceImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/impl/WxCpTpEditionServiceImpl.java
index 34ca852c3f..b77fb924c2 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/impl/WxCpTpEditionServiceImpl.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/impl/WxCpTpEditionServiceImpl.java
@@ -26,7 +26,7 @@ public class WxCpTpEditionServiceImpl implements WxCpTpEditionService {
* 延长试用期
*
* 文档地址
- *
+ *
*
* - 一个应用可以多次延长试用,但是试用总天数不能超过60天
* - 仅限时试用或试用过期状态下的应用可以延长试用期
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/impl/WxCpTpOrderServiceImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/impl/WxCpTpOrderServiceImpl.java
index b8aff54d39..7b2d45b5c6 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/impl/WxCpTpOrderServiceImpl.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/impl/WxCpTpOrderServiceImpl.java
@@ -30,7 +30,7 @@ public class WxCpTpOrderServiceImpl implements WxCpTpOrderService {
* 获取订单详情
*
* 文档地址
- *
+ *
*
* @param orderId 订单号
* @return the order
@@ -49,7 +49,7 @@ public WxCpTpOrderDetails getOrder(String orderId) throws WxErrorException {
* 获取订单列表
*
* 文档地址
- *
+ *
*
* @param startTime 起始时间
* @param endTime 终止时间
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/impl/WxCpTpServiceApacheHttpClientImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/impl/WxCpTpServiceApacheHttpClientImpl.java
index ce4e584aac..a287513331 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/impl/WxCpTpServiceApacheHttpClientImpl.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/impl/WxCpTpServiceApacheHttpClientImpl.java
@@ -5,14 +5,13 @@
import me.chanjar.weixin.common.error.WxError;
import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.common.error.WxRuntimeException;
-import me.chanjar.weixin.common.util.http.HttpType;
+import me.chanjar.weixin.common.util.http.HttpClientType;
import me.chanjar.weixin.common.util.http.apache.ApacheBasicResponseHandler;
import me.chanjar.weixin.common.util.http.apache.ApacheHttpClientBuilder;
import me.chanjar.weixin.common.util.http.apache.DefaultApacheHttpClientBuilder;
import me.chanjar.weixin.common.util.json.GsonParser;
import me.chanjar.weixin.cp.config.WxCpTpConfigStorage;
import me.chanjar.weixin.cp.constant.WxCpApiPathConsts;
-import org.apache.http.Consts;
import org.apache.http.HttpHost;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.HttpPost;
@@ -20,6 +19,7 @@
import org.apache.http.impl.client.CloseableHttpClient;
import java.io.IOException;
+import java.nio.charset.StandardCharsets;
/**
* The type Wx cp tp service apache http client.
@@ -41,8 +41,8 @@ public HttpHost getRequestHttpProxy() {
}
@Override
- public HttpType getRequestType() {
- return HttpType.APACHE_HTTP;
+ public HttpClientType getRequestType() {
+ return HttpClientType.APACHE_HTTP;
}
@Override
@@ -63,7 +63,7 @@ public String getSuiteAccessToken(boolean forceRefresh) throws WxErrorException
jsonObject.addProperty("suite_id", this.configStorage.getSuiteId());
jsonObject.addProperty("suite_secret", this.configStorage.getSuiteSecret());
jsonObject.addProperty("suite_ticket", this.getSuiteTicket());
- StringEntity entity = new StringEntity(jsonObject.toString(), Consts.UTF_8);
+ StringEntity entity = new StringEntity(jsonObject.toString(), StandardCharsets.UTF_8);
httpPost.setEntity(entity);
String resultContent = getRequestHttpClient().execute(httpPost, ApacheBasicResponseHandler.INSTANCE);
@@ -101,9 +101,9 @@ public void initHttp() {
this.httpClient = apacheHttpClientBuilder.build();
}
- @Override
- public WxCpTpConfigStorage getWxCpTpConfigStorage() {
- return this.configStorage;
- }
+// @Override
+// public WxCpTpConfigStorage getWxCpTpConfigStorage() {
+// return this.configStorage;
+// }
}
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/impl/WxCpTpServiceHttpComponentsImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/impl/WxCpTpServiceHttpComponentsImpl.java
new file mode 100644
index 0000000000..44b5fd8693
--- /dev/null
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/impl/WxCpTpServiceHttpComponentsImpl.java
@@ -0,0 +1,106 @@
+package me.chanjar.weixin.cp.tp.service.impl;
+
+import com.google.gson.JsonObject;
+import me.chanjar.weixin.common.enums.WxType;
+import me.chanjar.weixin.common.error.WxError;
+import me.chanjar.weixin.common.error.WxErrorException;
+import me.chanjar.weixin.common.error.WxRuntimeException;
+import me.chanjar.weixin.common.util.http.HttpClientType;
+import me.chanjar.weixin.common.util.http.hc.BasicResponseHandler;
+import me.chanjar.weixin.common.util.http.hc.DefaultHttpComponentsClientBuilder;
+import me.chanjar.weixin.common.util.http.hc.HttpComponentsClientBuilder;
+import me.chanjar.weixin.common.util.json.GsonParser;
+import me.chanjar.weixin.cp.constant.WxCpApiPathConsts;
+import org.apache.hc.client5.http.classic.methods.HttpPost;
+import org.apache.hc.client5.http.config.RequestConfig;
+import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
+import org.apache.hc.core5.http.HttpHost;
+import org.apache.hc.core5.http.io.entity.StringEntity;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+
+/**
+ * The type Wx cp tp service apache http client.
+ *
+ * @author altusea
+ */
+public class WxCpTpServiceHttpComponentsImpl extends BaseWxCpTpServiceImpl {
+ private CloseableHttpClient httpClient;
+ private HttpHost httpProxy;
+
+ @Override
+ public CloseableHttpClient getRequestHttpClient() {
+ return httpClient;
+ }
+
+ @Override
+ public HttpHost getRequestHttpProxy() {
+ return httpProxy;
+ }
+
+ @Override
+ public HttpClientType getRequestType() {
+ return HttpClientType.HTTP_COMPONENTS;
+ }
+
+ @Override
+ public String getSuiteAccessToken(boolean forceRefresh) throws WxErrorException {
+ if (!this.configStorage.isSuiteAccessTokenExpired() && !forceRefresh) {
+ return this.configStorage.getSuiteAccessToken();
+ }
+
+ synchronized (this.globalSuiteAccessTokenRefreshLock) {
+ try {
+ HttpPost httpPost = new HttpPost(configStorage.getApiUrl(WxCpApiPathConsts.Tp.GET_SUITE_TOKEN));
+ if (this.httpProxy != null) {
+ RequestConfig config = RequestConfig.custom()
+ .setProxy(this.httpProxy).build();
+ httpPost.setConfig(config);
+ }
+ JsonObject jsonObject = new JsonObject();
+ jsonObject.addProperty("suite_id", this.configStorage.getSuiteId());
+ jsonObject.addProperty("suite_secret", this.configStorage.getSuiteSecret());
+ jsonObject.addProperty("suite_ticket", this.getSuiteTicket());
+ StringEntity entity = new StringEntity(jsonObject.toString(), StandardCharsets.UTF_8);
+ httpPost.setEntity(entity);
+
+ String resultContent = getRequestHttpClient().execute(httpPost, BasicResponseHandler.INSTANCE);
+ WxError error = WxError.fromJson(resultContent, WxType.CP);
+ if (error.getErrorCode() != 0) {
+ throw new WxErrorException(error);
+ }
+ jsonObject = GsonParser.parse(resultContent);
+ String suiteAccussToken = jsonObject.get("suite_access_token").getAsString();
+ int expiresIn = jsonObject.get("expires_in").getAsInt();
+ this.configStorage.updateSuiteAccessToken(suiteAccussToken, expiresIn);
+ } catch (IOException e) {
+ throw new WxRuntimeException(e);
+ }
+ }
+ return this.configStorage.getSuiteAccessToken();
+ }
+
+ @Override
+ public void initHttp() {
+ HttpComponentsClientBuilder apacheHttpClientBuilder = DefaultHttpComponentsClientBuilder.get();
+
+ apacheHttpClientBuilder.httpProxyHost(this.configStorage.getHttpProxyHost())
+ .httpProxyPort(this.configStorage.getHttpProxyPort())
+ .httpProxyUsername(this.configStorage.getHttpProxyUsername())
+ .httpProxyPassword(this.configStorage.getHttpProxyPassword() == null ? null :
+ this.configStorage.getHttpProxyPassword().toCharArray());
+
+ if (this.configStorage.getHttpProxyHost() != null && this.configStorage.getHttpProxyPort() > 0) {
+ this.httpProxy = new HttpHost(this.configStorage.getHttpProxyHost(), this.configStorage.getHttpProxyPort());
+ }
+
+ this.httpClient = apacheHttpClientBuilder.build();
+ }
+
+// @Override
+// public WxCpTpConfigStorage getWxCpTpConfigStorage() {
+// return this.configStorage;
+// }
+
+}
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/impl/WxCpTpServiceJoddHttpImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/impl/WxCpTpServiceJoddHttpImpl.java
new file mode 100644
index 0000000000..9379f62e81
--- /dev/null
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/impl/WxCpTpServiceJoddHttpImpl.java
@@ -0,0 +1,104 @@
+package me.chanjar.weixin.cp.tp.service.impl;
+
+import com.google.gson.JsonObject;
+import jodd.http.HttpConnectionProvider;
+import jodd.http.HttpRequest;
+import jodd.http.HttpResponse;
+import jodd.http.ProxyInfo;
+import jodd.http.net.SocketHttpConnectionProvider;
+import me.chanjar.weixin.common.bean.WxAccessToken;
+import me.chanjar.weixin.common.enums.WxType;
+import me.chanjar.weixin.common.error.WxError;
+import me.chanjar.weixin.common.error.WxErrorException;
+import me.chanjar.weixin.common.util.http.HttpClientType;
+import me.chanjar.weixin.common.util.json.GsonParser;
+import me.chanjar.weixin.cp.api.impl.BaseWxCpServiceImpl;
+import me.chanjar.weixin.cp.config.WxCpConfigStorage;
+import me.chanjar.weixin.cp.constant.WxCpApiPathConsts;
+
+/**
+ * The type Wx cp service jodd http.
+ *
+ * @author someone
+ */
+public class WxCpTpServiceJoddHttpImpl extends BaseWxCpTpServiceImpl {
+ private HttpConnectionProvider httpClient;
+ private ProxyInfo httpProxy;
+
+ @Override
+ public HttpConnectionProvider getRequestHttpClient() {
+ return httpClient;
+ }
+
+ @Override
+ public ProxyInfo getRequestHttpProxy() {
+ return httpProxy;
+ }
+
+ @Override
+ public HttpClientType getRequestType() {
+ return HttpClientType.JODD_HTTP;
+ }
+
+ @Override
+ public String getSuiteAccessToken(boolean forceRefresh) throws WxErrorException {
+ if (!this.configStorage.isSuiteAccessTokenExpired() && !forceRefresh) {
+ return this.configStorage.getSuiteAccessToken();
+ }
+
+ synchronized (this.globalSuiteAccessTokenRefreshLock) {
+ // 构建请求 URL
+ String url = this.configStorage.getApiUrl(WxCpApiPathConsts.Tp.GET_SUITE_TOKEN);
+
+ JsonObject jsonObject = new JsonObject();
+ jsonObject.addProperty("suite_id", this.configStorage.getSuiteId());
+ jsonObject.addProperty("suite_secret", this.configStorage.getSuiteSecret());
+ jsonObject.addProperty("suite_ticket", this.getSuiteTicket());
+ String jsonBody = jsonObject.toString();
+
+ if (this.httpProxy != null) {
+ httpClient.useProxy(this.httpProxy);
+ }
+ // 创建 POST 请求
+ HttpRequest request = HttpRequest
+ .post(url)
+ .contentType("application/json")
+ .body(jsonBody); // 使用 .body() 设置请求体
+
+ 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);
+ }
+
+ // 更新 access token
+ jsonObject = GsonParser.parse(resultContent);
+ String suiteAccussToken = jsonObject.get("suite_access_token").getAsString();
+ int expiresIn = jsonObject.get("expires_in").getAsInt();
+ this.configStorage.updateSuiteAccessToken(suiteAccussToken, expiresIn);
+ }
+
+ return this.configStorage.getSuiteAccessToken();
+ }
+
+ @Override
+ public void initHttp() {
+ if (this.configStorage.getHttpProxyHost() != null && this.configStorage.getHttpProxyPort() > 0) {
+ httpProxy = new ProxyInfo(ProxyInfo.ProxyType.HTTP, configStorage.getHttpProxyHost(),
+ configStorage.getHttpProxyPort(), configStorage.getHttpProxyUsername(), configStorage.getHttpProxyPassword());
+ }
+
+ httpClient = new SocketHttpConnectionProvider();
+ }
+//
+// @Override
+// public WxCpConfigStorage getWxCpConfigStorage() {
+// return this.configStorage;
+// }
+}
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/impl/WxCpTpServiceOkHttpImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/impl/WxCpTpServiceOkHttpImpl.java
new file mode 100644
index 0000000000..c4dab9cf20
--- /dev/null
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/impl/WxCpTpServiceOkHttpImpl.java
@@ -0,0 +1,130 @@
+package me.chanjar.weixin.cp.tp.service.impl;
+
+import com.google.gson.JsonObject;
+import lombok.extern.slf4j.Slf4j;
+import me.chanjar.weixin.common.bean.WxAccessToken;
+import me.chanjar.weixin.common.enums.WxType;
+import me.chanjar.weixin.common.error.WxError;
+import me.chanjar.weixin.common.error.WxErrorException;
+import me.chanjar.weixin.common.error.WxRuntimeException;
+import me.chanjar.weixin.common.util.http.HttpClientType;
+import me.chanjar.weixin.common.util.http.okhttp.DefaultOkHttpClientBuilder;
+import me.chanjar.weixin.common.util.http.okhttp.OkHttpProxyInfo;
+import me.chanjar.weixin.common.util.json.GsonParser;
+import me.chanjar.weixin.cp.api.impl.BaseWxCpServiceImpl;
+import me.chanjar.weixin.cp.config.WxCpConfigStorage;
+import me.chanjar.weixin.cp.constant.WxCpApiPathConsts;
+import okhttp3.*;
+
+import java.io.IOException;
+
+import static me.chanjar.weixin.cp.constant.WxCpApiPathConsts.GET_TOKEN;
+
+/**
+ * The type Wx cp service ok http.
+ *
+ * @author someone
+ */
+@Slf4j
+public class WxCpTpServiceOkHttpImpl extends BaseWxCpTpServiceImpl {
+ private OkHttpClient httpClient;
+ private OkHttpProxyInfo httpProxy;
+
+ @Override
+ public OkHttpClient getRequestHttpClient() {
+ return httpClient;
+ }
+
+ @Override
+ public OkHttpProxyInfo getRequestHttpProxy() {
+ return httpProxy;
+ }
+
+ @Override
+ public HttpClientType getRequestType() {
+ return HttpClientType.OK_HTTP;
+ }
+
+ @Override
+ public String getSuiteAccessToken(boolean forceRefresh) throws WxErrorException {
+ if (!this.configStorage.isSuiteAccessTokenExpired() && !forceRefresh) {
+ return this.configStorage.getSuiteAccessToken();
+ }
+
+ synchronized (this.globalSuiteAccessTokenRefreshLock) {
+ // 得到 httpClient
+ OkHttpClient client = getRequestHttpClient();
+
+ JsonObject jsonObject = new JsonObject();
+ jsonObject.addProperty("suite_id", this.configStorage.getSuiteId());
+ jsonObject.addProperty("suite_secret", this.configStorage.getSuiteSecret());
+ jsonObject.addProperty("suite_ticket", this.getSuiteTicket());
+ String jsonBody = jsonObject.toString();
+
+ RequestBody requestBody = RequestBody.create(
+ MediaType.get("application/json; charset=utf-8"),
+ jsonBody
+ );
+
+ // 构建 POST 请求
+ Request request = new Request.Builder()
+ .url(this.configStorage.getApiUrl(WxCpApiPathConsts.Tp.GET_SUITE_TOKEN)) // URL 不包含查询参数
+ .post(requestBody) // 使用 POST 方法
+ .build();
+
+ String resultContent = null;
+ try (Response response = client.newCall(request).execute()) {
+ if (!response.isSuccessful()) {
+ throw new IOException("Unexpected response code: " + response);
+ }
+ resultContent = response.body().string();
+ } catch (IOException e) {
+ log.error("获取 suite token 失败: {}", e.getMessage(), e);
+ throw new WxRuntimeException("获取 suite token 失败", e);
+ }
+
+ WxError error = WxError.fromJson(resultContent, WxType.CP);
+ if (error.getErrorCode() != 0) {
+ throw new WxErrorException(error);
+ }
+
+ jsonObject = GsonParser.parse(resultContent);
+ String suiteAccussToken = jsonObject.get("suite_access_token").getAsString();
+ int expiresIn = jsonObject.get("expires_in").getAsInt();
+ this.configStorage.updateSuiteAccessToken(suiteAccussToken, expiresIn);
+ }
+ return this.configStorage.getSuiteAccessToken();
+ }
+
+ @Override
+ public void initHttp() {
+ log.debug("WxCpServiceOkHttpImpl initHttp");
+ //设置代理
+ if (configStorage.getHttpProxyHost() != null && configStorage.getHttpProxyPort() > 0) {
+ httpProxy = OkHttpProxyInfo.httpProxy(configStorage.getHttpProxyHost(),
+ configStorage.getHttpProxyPort(),
+ configStorage.getHttpProxyUsername(),
+ configStorage.getHttpProxyPassword());
+ OkHttpClient.Builder clientBuilder = new OkHttpClient.Builder();
+ clientBuilder.proxy(getRequestHttpProxy().getProxy());
+ //设置授权
+ clientBuilder.proxyAuthenticator(new Authenticator() {
+ @Override
+ public Request authenticate(Route route, Response response) throws IOException {
+ String credential = Credentials.basic(httpProxy.getProxyUsername(), httpProxy.getProxyPassword());
+ return response.request().newBuilder()
+ .header("Proxy-Authorization", credential)
+ .build();
+ }
+ });
+ httpClient = clientBuilder.build();
+ } else {
+ httpClient = DefaultOkHttpClientBuilder.get().build();
+ }
+ }
+
+// @Override
+// public WxCpConfigStorage getWxCpConfigStorage() {
+// return this.configStorage;
+// }
+}
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/impl/WxCpTpTagServiceImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/impl/WxCpTpTagServiceImpl.java
index b81760e72c..1b03f18c79 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/impl/WxCpTpTagServiceImpl.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/impl/WxCpTpTagServiceImpl.java
@@ -25,7 +25,7 @@
*
*
* @author zhangq
* 触发云函数。注意:HTTP API 途径触发云函数不包含用户信息。
* 文档地址:https://developers.weixin.qq.com/miniprogram/dev/wxcloud/reference-http-api/functions/invokeCloudFunction.html
*
- * 请求地址
- * POST https://api.weixin.qq.com/tcb/invokecloudfunction?access_token=ACCESS_TOKEN&env=ENV&name=FUNCTION_NAME
- *
- *
- *
- * @param env string 是 云开发环境ID
- * @param name string 是 云函数名称
- * @param body string 是 云函数的传入参数,具体结构由开发者定义。
- * @return resp_data string 云函数返回的buffer
- * @throws WxErrorException .
+ * @param env 云开发环境ID
+ * @param name 云函数名称
+ * @param body 云函数的传入参数,具体结构由开发者定义
+ * @return 云函数返回的buffer
+ * @throws WxErrorException 调用失败时抛出
*/
String invokeCloudFunction(String env, String name, String body) throws WxErrorException;
/**
- * Add list.
+ * 批量添加记录到集合。
*
- * @param collection the collection
- * @param list the list
- * @return the list
- * @throws WxErrorException the wx error exception
+ * @param collection 集合名称
+ * @param list 要添加的记录列表
+ * @return 插入成功的记录ID列表
+ * @throws WxErrorException 添加失败时抛出
*/
List
- * 数据库插入记录
- *
+ * 数据库插入记录。
* 文档地址:https://developers.weixin.qq.com/miniprogram/dev/wxcloud/reference-http-api/database/databaseAdd.html
- * 请求地址:POST https://api.weixin.qq.com/tcb/databaseadd?access_token=ACCESS_TOKEN
- *
*
- * @param env 云环境ID
- * @param query 数据库操作语句
- * @return 插入成功的数据集合主键_id json array
- * @throws WxErrorException .
+ * @param env 云环境ID
+ * @param query 数据库操作语句
+ * @return 插入成功的数据集合主键_id列表
+ * @throws WxErrorException 插入失败时抛出
*/
JsonArray databaseAdd(String env, String query) throws WxErrorException;
/**
- * Delete integer.
+ * 删除集合中符合条件的记录。
*
- * @param collection the collection
- * @param whereJson the where json
- * @return the integer
- * @throws WxErrorException the wx error exception
+ * @param collection 集合名称
+ * @param whereJson 查询条件JSON字符串
+ * @return 删除的记录数量
+ * @throws WxErrorException 删除失败时抛出
*/
Integer delete(String collection, String whereJson) throws WxErrorException;
/**
- * Database delete int.
+ * 数据库删除记录。
*
- * @param query the query
- * @return the int
- * @throws WxErrorException the wx error exception
+ * @param query 数据库操作语句
+ * @return 删除记录数量
+ * @throws WxErrorException 删除失败时抛出
*/
int databaseDelete(String query) throws WxErrorException;
/**
- *
- * 数据库删除记录
- *
+ * 数据库删除记录。
* 文档地址:https://developers.weixin.qq.com/miniprogram/dev/wxcloud/reference-http-api/database/databaseDelete.html
- * 请求地址:POST https://api.weixin.qq.com/tcb/databasedelete?access_token=ACCESS_TOKEN
- *
*
- * @param env 云环境ID
- * @param query 数据库操作语句
- * @return 删除记录数量 int
- * @throws WxErrorException .
+ * @param env 云环境ID
+ * @param query 数据库操作语句
+ * @return 删除记录数量
+ * @throws WxErrorException 删除失败时抛出
*/
int databaseDelete(String env, String query) throws WxErrorException;
/**
- * Update wx cloud database update result.
+ * 更新集合中符合条件的记录。
*
- * @param collection the collection
- * @param whereJson the where json
- * @param updateJson the update json
- * @return the wx cloud database update result
- * @throws WxErrorException the wx error exception
+ * @param collection 集合名称
+ * @param whereJson 查询条件JSON字符串
+ * @param updateJson 更新内容JSON字符串
+ * @return 更新结果对象
+ * @throws WxErrorException 更新失败时抛出
*/
WxCloudDatabaseUpdateResult update(String collection, String whereJson, String updateJson) throws WxErrorException;
/**
- * Database update wx cloud database update result.
+ * 数据库更新记录。
*
- * @param query the query
- * @return the wx cloud database update result
- * @throws WxErrorException the wx error exception
+ * @param query 数据库操作语句
+ * @return 更新结果对象
+ * @throws WxErrorException 更新失败时抛出
*/
WxCloudDatabaseUpdateResult databaseUpdate(String query) throws WxErrorException;
/**
- *
- * 数据库更新记录
- *
+ * 数据库更新记录。
* 文档地址:https://developers.weixin.qq.com/miniprogram/dev/wxcloud/reference-http-api/database/databaseUpdate.html
- * 请求地址:POST https://api.weixin.qq.com/tcb/databaseupdate?access_token=ACCESS_TOKEN
- *
*
- * @param env 云环境ID
- * @param query 数据库操作语句
- * @return . wx cloud database update result
- * @throws WxErrorException .
+ * @param env 云环境ID
+ * @param query 数据库操作语句
+ * @return 更新结果对象
+ * @throws WxErrorException 更新失败时抛出
*/
WxCloudDatabaseUpdateResult databaseUpdate(String env, String query) throws WxErrorException;
/**
+ * 查询集合中的记录。
+ * 示例:
* db.collection('geo')
- * .where({
- * price: _.gt(10)
- * })
- * .orderBy('_id', 'asc')
- * .orderBy('price', 'desc')
- * .skip(1)
- * .limit(10)
- * .get()
- *
- * @param collection the collection
- * @param whereJson the where json
- * @param orderBy the order by
- * @param skip the skip
- * @param limit the limit
- * @return wx cloud database query result
- * @throws WxErrorException the wx error exception
+ * .where({price: _.gt(10)})
+ * .orderBy('_id', 'asc')
+ * .orderBy('price', 'desc')
+ * .skip(1)
+ * .limit(10)
+ * .get()
+ *
+ * @param collection 集合名称
+ * @param whereJson 查询条件JSON字符串
+ * @param orderBy 排序条件Map
+ * @param skip 跳过记录数
+ * @param limit 限制返回记录数
+ * @return 查询结果对象
+ * @throws WxErrorException 查询失败时抛出
*/
WxCloudDatabaseQueryResult query(String collection, String whereJson, Map
- * 数据库查询记录
- *
+ * 数据库查询记录。
* 文档地址:https://developers.weixin.qq.com/miniprogram/dev/wxcloud/reference-http-api/database/databaseQuery.html
- * 请求地址:POST https://api.weixin.qq.com/tcb/databasequery?access_token=ACCESS_TOKEN
- *
*
- * @param env 云环境ID
- * @param query 数据库操作语句
- * @return . wx cloud database query result
- * @throws WxErrorException .
+ * @param env 云环境ID
+ * @param query 数据库操作语句
+ * @return 查询结果对象
+ * @throws WxErrorException 查询失败时抛出
*/
WxCloudDatabaseQueryResult databaseQuery(String env, String query) throws WxErrorException;
/**
- * Database aggregate json array.
+ * 数据库聚合记录。
*
- * @param query the query
- * @return the json array
- * @throws WxErrorException the wx error exception
+ * @param query 数据库操作语句
+ * @return 聚合结果JSON数组
+ * @throws WxErrorException 聚合失败时抛出
*/
JsonArray databaseAggregate(String query) throws WxErrorException;
/**
- *
- * 数据库聚合记录
- *
+ * 数据库聚合记录。
* 文档地址:https://developers.weixin.qq.com/miniprogram/dev/wxcloud/reference-http-api/database/databaseAggregate.html
- * 请求地址:POST https://api.weixin.qq.com/tcb/databaseaggregate?access_token=ACCESS_TOKEN
- *
*
- * @param env 云环境ID
- * @param query 数据库操作语句
- * @return . json array
- * @throws WxErrorException .
+ * @param env 云环境ID
+ * @param query 数据库操作语句
+ * @return 聚合结果JSON数组
+ * @throws WxErrorException 聚合失败时抛出
*/
JsonArray databaseAggregate(String env, String query) throws WxErrorException;
/**
- * Count long.
+ * 统计集合中符合条件的记录数。
*
- * @param collection the collection
- * @param whereJson the where json
- * @return the long
- * @throws WxErrorException the wx error exception
+ * @param collection 集合名称
+ * @param whereJson 查询条件JSON字符串
+ * @return 记录数量
+ * @throws WxErrorException 统计失败时抛出
*/
Long count(String collection, String whereJson) throws WxErrorException;
/**
- * Database count long.
+ * 统计集合记录数或统计查询语句对应的结果记录数。
*
- * @param query the query
- * @return the long
- * @throws WxErrorException the wx error exception
+ * @param query 数据库操作语句
+ * @return 记录数量
+ * @throws WxErrorException 统计失败时抛出
*/
Long databaseCount(String query) throws WxErrorException;
/**
- *
- * 统计集合记录数或统计查询语句对应的结果记录数
- *
+ * 统计集合记录数或统计查询语句对应的结果记录数。
* 文档地址:https://developers.weixin.qq.com/miniprogram/dev/wxcloud/reference-http-api/database/databaseCount.html
- * 请求地址:POST https://api.weixin.qq.com/tcb/databasecount?access_token=ACCESS_TOKEN
- *
*
- * @param env 云环境ID
- * @param query 数据库操作语句
- * @return 记录数量 long
- * @throws WxErrorException .
+ * @param env 云环境ID
+ * @param query 数据库操作语句
+ * @return 记录数量
+ * @throws WxErrorException 统计失败时抛出
*/
Long databaseCount(String env, String query) throws WxErrorException;
/**
- * Update index.
+ * 变更数据库索引。
*
- * @param collectionName the collection name
- * @param createIndexes the create indexes
- * @param dropIndexNames the drop index names
- * @throws WxErrorException the wx error exception
+ * @param collectionName 集合名称
+ * @param createIndexes 新增索引对象列表
+ * @param dropIndexNames 要删除的索引名称列表
+ * @throws WxErrorException 更新失败时抛出
*/
void updateIndex(String collectionName, List
- * 变更数据库索引
- *
+ * 变更数据库索引。
* 文档地址:https://developers.weixin.qq.com/miniprogram/dev/wxcloud/reference-http-api/database/updateIndex.html
- * 请求地址:POST https://api.weixin.qq.com/tcb/updateindex?access_token=ACCESS_TOKEN
- *
*
- * @param env 云环境ID
- * @param collectionName 集合名称
- * @param createIndexes 新增索引对象
- * @param dropIndexNames 要删除的索引的名字
- * @throws WxErrorException .
+ * @param env 云环境ID
+ * @param collectionName 集合名称
+ * @param createIndexes 新增索引对象列表
+ * @param dropIndexNames 要删除的索引名称列表
+ * @throws WxErrorException 更新失败时抛出
*/
void updateIndex(String env, String collectionName, List
- * 数据库导入
+ * 数据库导入。
+ * 文档地址:https://developers.weixin.qq.com/miniprogram/dev/wxcloud/reference-http-api/database/databaseMigrateImport.html
+ * 注意:导入文件需先上传到同环境的存储中,可使用开发者工具或HTTP API的上传文件API上传
*
- * 文档地址:https://developers.weixin.qq.com/miniprogram/dev/wxcloud/reference-http-api/database/databaseMigrateImport
- * .html
- * 请求地址: POST https://api.weixin.qq.com/tcb/databasemigrateimport?access_token=ACCESS_TOKEN
- *
- *
- * @param env 云环境ID
- * @param collectionName 导入collection名
- * @param filePath 导入文件路径(导入文件需先上传到同环境的存储中,可使用开发者工具或 HTTP API的上传文件 API上传)
- * @param fileType 导入文件类型, 1 JSON, 2 CSV
- * @param stopOnError 是否在遇到错误时停止导入
- * @param conflictMode 冲突处理模式 : 1 INSERT , 2 UPSERT
- * @return jobId long
- * @throws WxErrorException .
+ * @param env 云环境ID
+ * @param collectionName 导入collection名
+ * @param filePath 导入文件路径
+ * @param fileType 导入文件类型,1:JSON,2:CSV
+ * @param stopOnError 是否在遇到错误时停止导入
+ * @param conflictMode 冲突处理模式,1:INSERT,2:UPSERT
+ * @return 任务ID
+ * @throws WxErrorException 导入失败时抛出
*/
Long databaseMigrateImport(String env, String collectionName, String filePath, int fileType, boolean stopOnError,
int conflictMode) throws WxErrorException;
/**
- * Database migrate export long.
+ * 数据库导出。
*
- * @param filePath the file path
- * @param fileType the file type
- * @param query the query
- * @return the long
- * @throws WxErrorException the wx error exception
+ * @param filePath 导出文件路径
+ * @param fileType 导出文件类型,1:JSON,2:CSV
+ * @param query 导出条件
+ * @return 任务ID
+ * @throws WxErrorException 导出失败时抛出
*/
Long databaseMigrateExport(String filePath, int fileType, String query) throws WxErrorException;
/**
- * - * 数据库导出 - * - * 文档地址:https://developers.weixin.qq.com/miniprogram/dev/wxcloud/reference-http-api/database/databaseMigrateExport - * .html - * 请求地址: POST https://api.weixin.qq.com/tcb/databasemigrateexport?access_token=ACCESS_TOKEN - *+ * 数据库导出。 + * 文档地址:https://developers.weixin.qq.com/miniprogram/dev/wxcloud/reference-http-api/database/databaseMigrateExport.html + * 注意:文件会导出到同环境的云存储中,可使用获取下载链接API获取下载链接 * - * @param env 云环境ID - * @param filePath 导出文件路径(文件会导出到同环境的云存储中,可使用获取下载链接 API 获取下载链接) - * @param fileType 导出文件类型, 1 JSON, 2 CSV - * @param query 导出条件 - * @return jobId long - * @throws WxErrorException . + * @param env 云环境ID + * @param filePath 导出文件路径 + * @param fileType 导出文件类型,1:JSON,2:CSV + * @param query 导出条件 + * @return 任务ID + * @throws WxErrorException 导出失败时抛出 */ Long databaseMigrateExport(String env, String filePath, int fileType, String query) throws WxErrorException; /** - * Database migrate query info wx cloud cloud database migrate query info result. + * 数据库迁移状态查询。 * - * @param jobId the job id - * @return the wx cloud cloud database migrate query info result - * @throws WxErrorException the wx error exception + * @param jobId 迁移任务ID + * @return 迁移状态查询结果 + * @throws WxErrorException 查询失败时抛出 */ WxCloudCloudDatabaseMigrateQueryInfoResult databaseMigrateQueryInfo(Long jobId) throws WxErrorException; /** - *
- * 数据库迁移状态查询 - * - * 文档地址:https://developers.weixin.qq.com/miniprogram/dev/wxcloud/reference-http-api/database - * /databaseMigrateQueryInfo.html - * 请求地址:POST https://api.weixin.qq.com/tcb/databasemigratequeryinfo?access_token=ACCESS_TOKEN - *+ * 数据库迁移状态查询。 + * 文档地址:https://developers.weixin.qq.com/miniprogram/dev/wxcloud/reference-http-api/database/databaseMigrateQueryInfo.html * - * @param env 云环境ID - * @param jobId 迁移任务ID - * @return . wx cloud cloud database migrate query info result - * @throws WxErrorException . + * @param env 云环境ID + * @param jobId 迁移任务ID + * @return 迁移状态查询结果 + * @throws WxErrorException 查询失败时抛出 */ WxCloudCloudDatabaseMigrateQueryInfoResult databaseMigrateQueryInfo(String env, Long jobId) throws WxErrorException; /** - * Upload file wx cloud upload file result. + * 获取文件上传链接。 * - * @param path the path - * @return the wx cloud upload file result - * @throws WxErrorException the wx error exception + * @param path 上传路径 + * @return 上传结果对象 + * @throws WxErrorException 获取失败时抛出 */ WxCloudUploadFileResult uploadFile(String path) throws WxErrorException; /** - *
- * 获取文件上传链接
- *
+ * 获取文件上传链接。
* 文档地址:https://developers.weixin.qq.com/miniprogram/dev/wxcloud/reference-http-api/storage/uploadFile.html
- * 请求地址:POST https://api.weixin.qq.com/tcb/uploadfile?access_token=ACCESS_TOKEN
- *
- *
*
- * @param env 云环境ID
- * @param path 上传路径
- * @return 上传结果 wx cloud upload file result
- * @throws WxErrorException .
+ * @param env 云环境ID
+ * @param path 上传路径
+ * @return 上传结果对象
+ * @throws WxErrorException 获取失败时抛出
*/
WxCloudUploadFileResult uploadFile(String env, String path) throws WxErrorException;
/**
- * Batch download file wx cloud batch download file result.
+ * 获取文件下载链接。
*
- * @param fileIds the file ids
- * @param maxAges the max ages
- * @return the wx cloud batch download file result
- * @throws WxErrorException the wx error exception
+ * @param fileIds 文件ID数组
+ * @param maxAges 下载链接有效期数组,对应文件id列表
+ * @return 下载链接信息对象
+ * @throws WxErrorException 获取失败时抛出
*/
WxCloudBatchDownloadFileResult batchDownloadFile(String[] fileIds, long[] maxAges) throws WxErrorException;
/**
- *
- * 获取文件下载链接
- *
+ * 获取文件下载链接。
* 文档地址:https://developers.weixin.qq.com/miniprogram/dev/wxcloud/reference-http-api/storage/batchDownloadFile.html
- * 请求地址:POST https://api.weixin.qq.com/tcb/batchdownloadfile?access_token=ACCESS_TOKEN
- *
- *
*
- * @param env 云环境ID
- * @param fileIds 文件ID列表
- * @param maxAges 下载链接有效期列表,对应文件id列表
- * @return 下载链接信息 wx cloud batch download file result
- * @throws WxErrorException .
+ * @param env 云环境ID
+ * @param fileIds 文件ID数组
+ * @param maxAges 下载链接有效期数组,对应文件id列表
+ * @return 下载链接信息对象
+ * @throws WxErrorException 获取失败时抛出
*/
WxCloudBatchDownloadFileResult batchDownloadFile(String env, String[] fileIds, long[] maxAges) throws WxErrorException;
/**
- * Batch delete file wx cloud batch delete file result.
+ * 删除文件。
*
- * @param fileIds the file ids
- * @return the wx cloud batch delete file result
- * @throws WxErrorException the wx error exception
+ * @param fileIds 文件ID数组
+ * @return 删除结果对象
+ * @throws WxErrorException 删除失败时抛出
*/
WxCloudBatchDeleteFileResult batchDeleteFile(String[] fileIds) throws WxErrorException;
/**
- *
- * 删除文件
- *
+ * 删除文件。
* 文档地址:https://developers.weixin.qq.com/miniprogram/dev/wxcloud/reference-http-api/storage/batchDeleteFile.html
- * 请求地址:POST https://api.weixin.qq.com/tcb/batchdeletefile?access_token=ACCESS_TOKEN
- *
- *
*
- * @param env 云环境ID
- * @param fileIds 文件ID列表
- * @return 下载链接信息 wx cloud batch delete file result
- * @throws WxErrorException .
+ * @param env 云环境ID
+ * @param fileIds 文件ID数组
+ * @return 删除结果对象
+ * @throws WxErrorException 删除失败时抛出
*/
WxCloudBatchDeleteFileResult batchDeleteFile(String env, String[] fileIds) throws WxErrorException;
/**
- * - * 获取腾讯云API调用凭证 - * - * 文档地址:https://developers.weixin.qq.com/miniprogram/dev/wxcloud/reference-http-api/utils/getQcloudToken.html - * 请求地址:POST https://api.weixin.qq.com/tcb/getqcloudtoken?access_token=ACCESS_TOKEN - *+ * 获取腾讯云API调用凭证。 + * 文档地址:https://developers.weixin.qq.com/miniprogram/dev/wxcloud/reference-http-api/utils/getQcloudToken.html * - * @param lifeSpan 有效期(单位为秒,最大7200) - * @return . qcloud token - * @throws WxErrorException . + * @param lifeSpan 有效期(单位为秒,最大7200) + * @return 腾讯云Token结果对象 + * @throws WxErrorException 获取失败时抛出 */ WxCloudGetQcloudTokenResult getQcloudToken(long lifeSpan) throws WxErrorException; /** - * Database collection add. + * 新增集合。 * - * @param collectionName the collection name - * @throws WxErrorException the wx error exception + * @param collectionName 集合名称 + * @throws WxErrorException 新增失败时抛出 */ void databaseCollectionAdd(String collectionName) throws WxErrorException; /** - *
- * 新增集合
+ * 新增集合。
+ * 文档地址:https://developers.weixin.qq.com/miniprogram/dev/wxcloud/reference-http-api/database/databaseCollectionAdd.html
*
- * 文档地址:https://developers.weixin.qq.com/miniprogram/dev/wxcloud/reference-http-api/database/databaseCollectionAdd
- * .html
- * 请求地址:POST https://api.weixin.qq.com/tcb/databasecollectionadd?access_token=ACCESS_TOKEN
- *
- *
- * @param env 云环境ID
- * @param collectionName 集合名称
- * @throws WxErrorException .
+ * @param env 云环境ID
+ * @param collectionName 集合名称
+ * @throws WxErrorException 新增失败时抛出
*/
void databaseCollectionAdd(String env, String collectionName) throws WxErrorException;
/**
- * Database collection delete.
+ * 删除集合。
*
- * @param collectionName the collection name
- * @throws WxErrorException the wx error exception
+ * @param collectionName 集合名称
+ * @throws WxErrorException 删除失败时抛出
*/
void databaseCollectionDelete(String collectionName) throws WxErrorException;
/**
- * - * 删除集合 - * - * 文档地址:https://developers.weixin.qq.com/miniprogram/dev/wxcloud/reference-http-api/database - * /databaseCollectionDelete.html - * 请求地址:POST https://api.weixin.qq.com/tcb/databasecollectionadd?access_token=ACCESS_TOKEN - *+ * 删除集合。 + * 文档地址:https://developers.weixin.qq.com/miniprogram/dev/wxcloud/reference-http-api/database/databaseCollectionDelete.html * - * @param env 云环境ID - * @param collectionName 集合名称 - * @throws WxErrorException . + * @param env 云环境ID + * @param collectionName 集合名称 + * @throws WxErrorException 删除失败时抛出 */ void databaseCollectionDelete(String env, String collectionName) throws WxErrorException; /** - * Database collection get wx cloud database collection get result. + * 获取特定云环境下集合信息。 * - * @param limit the limit - * @param offset the offset - * @return the wx cloud database collection get result - * @throws WxErrorException the wx error exception + * @param limit 获取数量限制,默认值:10 + * @param offset 偏移量,默认值:0 + * @return 集合信息获取结果对象 + * @throws WxErrorException 获取失败时抛出 */ WxCloudDatabaseCollectionGetResult databaseCollectionGet(Long limit, Long offset) throws WxErrorException; /** - *
- * 获取特定云环境下集合信息 - * - * 文档地址:https://developers.weixin.qq.com/miniprogram/dev/wxcloud/reference-http-api/database/databaseCollectionGet - * .html - * 请求地址:POST https://api.weixin.qq.com/tcb/databasecollectionget?access_token=ACCESS_TOKEN - *+ * 获取特定云环境下集合信息。 + * 文档地址:https://developers.weixin.qq.com/miniprogram/dev/wxcloud/reference-http-api/database/databaseCollectionGet.html * - * @param env 云环境ID - * @param limit 获取数量限制,默认值:10 - * @param offset 偏移量,默认值:0 - * @return . wx cloud database collection get result - * @throws WxErrorException . + * @param env 云环境ID + * @param limit 获取数量限制,默认值:10 + * @param offset 偏移量,默认值:0 + * @return 集合信息获取结果对象 + * @throws WxErrorException 获取失败时抛出 */ WxCloudDatabaseCollectionGetResult databaseCollectionGet(String env, Long limit, Long offset) throws WxErrorException; /** - * 发送携带 URL Link 的短信 - * + * 发送携带 URL Link 的短信。 * 文档地址:https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/cloudbase/cloudbase.sendSmsV2.html - * @param request - * @return WxCloudSendSmsV2Result - * @throws WxErrorException + * + * @param request 短信发送请求对象 + * @return 短信发送结果对象 + * @throws WxErrorException 发送失败时抛出 */ WxCloudSendSmsV2Result sendSmsV2(WxCloudSendSmsV2Request request) throws WxErrorException; - } diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaComplaintService.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaComplaintService.java new file mode 100644 index 0000000000..bd12f60850 --- /dev/null +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaComplaintService.java @@ -0,0 +1,159 @@ +package cn.binarywang.wx.miniapp.api; + +import cn.binarywang.wx.miniapp.bean.complaint.*; +import me.chanjar.weixin.common.error.WxErrorException; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; + +/** + * 小程序交易投诉接口 + * + * @author Binary Wang + * created on 2025-01-01 + */ +public interface WxMaComplaintService { + + /** + *
+ * 查询投诉单列表API + * 商户可通过调用此接口,查询指定时间段的所有用户投诉信息,以分页输出查询结果。 + * 文档详见: https://developers.weixin.qq.com/miniprogram/dev/platform-capabilities/business-capabilities/guarantee/complaint.html + *+ * + * @param request {@link WxMaComplaintRequest} 查询投诉单列表请求数据 + * @return {@link WxMaComplaintResult} 微信返回的投诉单列表 + * @throws WxErrorException the wx error exception + */ + WxMaComplaintResult queryComplaints(WxMaComplaintRequest request) throws WxErrorException; + + /** + *
+ * 查询投诉单详情API + * 商户可通过调用此接口,查询指定投诉单的用户投诉详情,包含投诉内容、投诉关联订单、投诉人联系方式等信息,方便商户处理投诉。 + * 文档详见: https://developers.weixin.qq.com/miniprogram/dev/platform-capabilities/business-capabilities/guarantee/complaint.html + *+ * + * @param request {@link WxMaComplaintDetailRequest} 投诉单详情请求数据 + * @return {@link WxMaComplaintDetailResult} 微信返回的投诉单详情 + * @throws WxErrorException the wx error exception + */ + WxMaComplaintDetailResult getComplaint(WxMaComplaintDetailRequest request) throws WxErrorException; + + /** + *
+ * 查询投诉协商历史API + * 商户可通过调用此接口,查询指定投诉的用户商户协商历史,以分页输出查询结果,方便商户根据处理历史来制定后续处理方案。 + * 文档详见: https://developers.weixin.qq.com/miniprogram/dev/platform-capabilities/business-capabilities/guarantee/complaint.html + *+ * + * @param request {@link WxMaNegotiationHistoryRequest} 请求数据 + * @return {@link WxMaNegotiationHistoryResult} 微信返回结果 + * @throws WxErrorException the wx error exception + */ + WxMaNegotiationHistoryResult queryNegotiationHistorys(WxMaNegotiationHistoryRequest request) throws WxErrorException; + + /** + *
+ * 创建投诉通知回调地址API + * 商户通过调用此接口创建投诉通知回调URL,当用户产生新投诉且投诉状态已变更时,微信会通过回调URL通知商户。 + * 文档详见: https://developers.weixin.qq.com/miniprogram/dev/platform-capabilities/business-capabilities/guarantee/complaint.html + *+ * + * @param request {@link WxMaComplaintNotifyUrlRequest} 请求数据 + * @return {@link WxMaComplaintNotifyUrlResult} 微信返回结果 + * @throws WxErrorException the wx error exception + */ + WxMaComplaintNotifyUrlResult addComplaintNotifyUrl(WxMaComplaintNotifyUrlRequest request) throws WxErrorException; + + /** + *
+ * 查询投诉通知回调地址API + * 商户通过调用此接口查询投诉通知的回调URL。 + * 文档详见: https://developers.weixin.qq.com/miniprogram/dev/platform-capabilities/business-capabilities/guarantee/complaint.html + *+ * + * @return {@link WxMaComplaintNotifyUrlResult} 微信返回结果 + * @throws WxErrorException the wx error exception + */ + WxMaComplaintNotifyUrlResult getComplaintNotifyUrl() throws WxErrorException; + + /** + *
+ * 更新投诉通知回调地址API + * 商户通过调用此接口更新投诉通知的回调URL。 + * 文档详见: https://developers.weixin.qq.com/miniprogram/dev/platform-capabilities/business-capabilities/guarantee/complaint.html + *+ * + * @param request {@link WxMaComplaintNotifyUrlRequest} 请求数据 + * @return {@link WxMaComplaintNotifyUrlResult} 微信返回结果 + * @throws WxErrorException the wx error exception + */ + WxMaComplaintNotifyUrlResult updateComplaintNotifyUrl(WxMaComplaintNotifyUrlRequest request) throws WxErrorException; + + /** + *
+ * 删除投诉通知回调地址API + * 当商户不再需要推送通知时,可通过调用此接口删除投诉通知的回调URL,取消通知回调。 + * 文档详见: https://developers.weixin.qq.com/miniprogram/dev/platform-capabilities/business-capabilities/guarantee/complaint.html + *+ * + * @throws WxErrorException the wx error exception + */ + void deleteComplaintNotifyUrl() throws WxErrorException; + + /** + *
+ * 提交回复API + * 商户可通过调用此接口,提交回复内容。其中上传图片凭证需首先调用商户上传反馈图片接口,得到图片id,再将id填入请求。 + * 回复可配置文字链,传入跳转链接文案和跳转链接字段,用户点击即可跳转对应页面 + * 文档详见: https://developers.weixin.qq.com/miniprogram/dev/platform-capabilities/business-capabilities/guarantee/complaint.html + *+ * + * @param request {@link WxMaResponseRequest} 请求数据 + * @throws WxErrorException the wx error exception + */ + void submitResponse(WxMaResponseRequest request) throws WxErrorException; + + /** + *
+ * 反馈处理完成API + * 商户可通过调用此接口,反馈投诉单已处理完成。 + * 文档详见: https://developers.weixin.qq.com/miniprogram/dev/platform-capabilities/business-capabilities/guarantee/complaint.html + *+ * + * @param request {@link WxMaCompleteRequest} 请求数据 + * @throws WxErrorException the wx error exception + */ + void complete(WxMaCompleteRequest request) throws WxErrorException; + + /** + *
+ * 商户上传反馈图片API + * 商户可通过调用此接口上传反馈图片凭证,上传成功后可在提交回复时使用。 + * 文档详见: https://developers.weixin.qq.com/miniprogram/dev/platform-capabilities/business-capabilities/guarantee/complaint.html + *+ * + * @param imageFile 需要上传的图片文件 + * @return String 微信返回的媒体文件标识Id + * @throws WxErrorException the wx error exception + * @throws IOException IO异常 + */ + String uploadResponseImage(File imageFile) throws WxErrorException, IOException; + + /** + *
+ * 商户上传反馈图片API + * 商户可通过调用此接口上传反馈图片凭证,上传成功后可在提交回复时使用。 + * 文档详见: https://developers.weixin.qq.com/miniprogram/dev/platform-capabilities/business-capabilities/guarantee/complaint.html + *+ * + * @param inputStream 需要上传的图片文件流 + * @param fileName 需要上传的图片文件名 + * @return String 微信返回的媒体文件标识Id + * @throws WxErrorException the wx error exception + * @throws IOException IO异常 + */ + String uploadResponseImage(InputStream inputStream, String fileName) throws WxErrorException, IOException; +} \ No newline at end of file diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaCustomserviceWorkService.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaCustomserviceWorkService.java new file mode 100644 index 0000000000..bf119bc596 --- /dev/null +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaCustomserviceWorkService.java @@ -0,0 +1,57 @@ +package cn.binarywang.wx.miniapp.api; + +import cn.binarywang.wx.miniapp.bean.customservice.WxMaCustomserviceResult; +import me.chanjar.weixin.common.error.WxErrorException; + + +/** + *
+ * 小程序 - 微信客服 相关接口 + * 负责处理 https://api.weixin.qq.com/customservice/work/** + * 文档:https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/kf-work/getKfWorkBound.html + * 绑定的企业ID,需和小程序主体一致。 + * 目前仅支持绑定非个人小程序。 + * Created by tryking123 on 2025/8/18. + *+ * + * @author tryking123 + */ +public interface WxMaCustomserviceWorkService { + + /** + * 查询小程序的微信客服绑定情况 + */ + String GET_CUSTOMSERVICE_URL = "https://api.weixin.qq.com/customservice/work/get"; + /** + * 为小程序绑定微信客服 注:此接口绑定的企业ID需完成企业认证 + */ + String BIND_CUSTOMSERVICE_URL = "https://api.weixin.qq.com/customservice/work/bind"; + /** + * 为小程序解除绑定微信客服 + */ + String UNBIND_CUSTOMSERVICE_URL = "https://api.weixin.qq.com/customservice/work/unbind"; + + /** + * 查询小程序的微信客服绑定情况 + * + * @return 成功示例json { "errcode": 0,"entityName": "XXXXX有限公司","corpid": "wwee11111xxxxxxx","bindTime": 1694611289 } + * @throws WxErrorException + */ + WxMaCustomserviceResult getCustomservice() throws WxErrorException; + + /** + * 绑定微信客服 + * @param corpid 企业ID,获取方式参考:https://developer.work.weixin.qq.com/document/path/90665#corpid + * @return 成功示例json { "errcode": 0 } + * @throws WxErrorException + */ + WxMaCustomserviceResult bindCustomservice(String corpid) throws WxErrorException; + + /** + * 解除绑定微信客服 + * @param corpid 企业ID,获取方式参考:https://developer.work.weixin.qq.com/document/path/90665#corpid + * @return 成功示例json { "errcode": 0 } + * @throws WxErrorException + */ + WxMaCustomserviceResult unbindCustomservice(String corpid) throws WxErrorException; +} diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaDeviceSubscribeService.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaDeviceSubscribeService.java index f44f64e48d..b45bc058a6 100644 --- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaDeviceSubscribeService.java +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaDeviceSubscribeService.java @@ -1,9 +1,10 @@ package cn.binarywang.wx.miniapp.api; -import cn.binarywang.wx.miniapp.bean.device.WxMaDeviceSubscribeMessageRequest; -import cn.binarywang.wx.miniapp.bean.device.WxMaDeviceTicketRequest; +import cn.binarywang.wx.miniapp.bean.device.*; import me.chanjar.weixin.common.error.WxErrorException; +import java.util.List; + /** * 小程序设备订阅消息相关 API * 文档: @@ -21,6 +22,7 @@ public interface WxMaDeviceSubscribeService { * 注意: * 设备ticket有效时间为5分钟 * + * * @param deviceTicketRequest * @return * @throws WxErrorException @@ -37,4 +39,53 @@ public interface WxMaDeviceSubscribeService { */ void sendDeviceSubscribeMsg(WxMaDeviceSubscribeMessageRequest deviceSubscribeMessageRequest) throws WxErrorException; + /** + *
+ * 创建设备组 + * 详情请见:https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/hardware-device/createIotGroupId.html + *+ * + * @param createIotGroupIdRequest 请求参数 + * @return 设备组的唯一标识 + * @throws WxErrorException + */ + String createIotGroupId(WxMaCreateIotGroupIdRequest createIotGroupIdRequest) throws WxErrorException; + + /** + *
+ * 查询设备组信息 + * 详情请见:https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/hardware-device/getIotGroupInfo.html + *+ * + * @param getIotGroupInfoRequest 请求参数 + * @return 设备组信息 + * @throws WxErrorException + */ + WxMaIotGroupDeviceInfoResponse getIotGroupInfo(WxMaGetIotGroupInfoRequest getIotGroupInfoRequest) throws WxErrorException; + + /** + *
+ * 设备组添加设备 + * 一个设备组最多添加 50 个设备。 一个设备同一时间只能被添加到一个设备组中。 + * 详情请见:https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/hardware-device/addIotGroupDevice.html + *+ * + * @param request 请求参数 + * @return 成功添加的设备信息 + * @throws WxErrorException + */ + List
+ * 设备组删除设备 + * 详情请见:https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/hardware-device/removeIotGroupDevice.html + *+ * + * @param request 请求参数 + * @return 成功删除的设备信息 + * @throws WxErrorException + */ + List
+ * 文档地址:用工关系简介 + *
+ * + * @author Binary Wang + * created on 2025-12-19 + */ +public interface WxMaEmployeeRelationService { + + /** + * 解绑用工关系 + *+ * 企业可以调用该接口解除和用户的B2C用工关系 + *
+ * 文档地址:解绑用工关系 + * + * @param request 解绑请求参数 + * @throws WxErrorException 调用微信接口失败时抛出 + */ + void unbindEmployee(WxMaUnbindEmployeeRequest request) throws WxErrorException; + + /** + * 推送用工消息 + *+ * 企业可以调用该接口向用户推送用工相关消息 + *
+ * 文档地址:推送用工消息 + * + * @param request 推送消息请求参数 + * @throws WxErrorException 调用微信接口失败时抛出 + */ + void sendEmployeeMsg(WxMaSendEmployeeMsgRequest request) throws WxErrorException; +} diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaExpressDeliveryReturnService.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaExpressDeliveryReturnService.java index 4254d18dfe..6d950f6801 100644 --- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaExpressDeliveryReturnService.java +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaExpressDeliveryReturnService.java @@ -5,18 +5,47 @@ import me.chanjar.weixin.common.error.WxErrorException; /** - * 退货组件 + * 微信小程序物流退货组件接口。 + * 用于处理退货单相关操作,包括新增、查询和取消退货单。 + * 文档:https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/express/express.html + * */ public interface WxMaExpressDeliveryReturnService { - /** - * 获取支持的快递公司列表 - */ + /** 新增退货单接口地址 */ String ADD_DELIVERY_RETURN_URL = "https://api.weixin.qq.com/cgi-bin/express/delivery/return/add"; + /** 获取退货单接口地址 */ String GET_DELIVERY_RETURN_URL = "https://api.weixin.qq.com/cgi-bin/express/delivery/return/get"; + /** 取消退货单接口地址 */ String UNBIND_DELIVERY_RETURN_URL = "https://api.weixin.qq.com/cgi-bin/express/delivery/return/unbind"; + /** + * 新增退货单。 + * 用于创建新的退货单,返回退货单信息。 + * + * @param wxMaExpressDeliveryReturnAddRequest 退货单新增请求对象 + * @return 退货单信息结果 + * @throws WxErrorException 新增失败时抛出 + */ WxMaExpressReturnInfoResult addDeliveryReturn(WxMaExpressDeliveryReturnAddRequest wxMaExpressDeliveryReturnAddRequest) throws WxErrorException; + + /** + * 获取退货单信息。 + * 根据退货单ID查询退货单的详细信息。 + * + * @param returnId 退货单ID + * @return 退货单信息结果 + * @throws WxErrorException 获取失败时抛出 + */ WxMaExpressReturnInfoResult getDeliveryReturn(String returnId) throws WxErrorException; + + /** + * 取消退货单。 + * 取消指定的退货单,取消后的退货单将无法继续使用。 + * + * @param returnId 退货单ID + * @return 操作结果 + * @throws WxErrorException 取消失败时抛出 + */ WxMaExpressReturnInfoResult unbindDeliveryReturn(String returnId) throws WxErrorException; } diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaKefuService.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaKefuService.java new file mode 100644 index 0000000000..b8b5a03809 --- /dev/null +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaKefuService.java @@ -0,0 +1,128 @@ +package cn.binarywang.wx.miniapp.api; + +import cn.binarywang.wx.miniapp.bean.kefu.WxMaKfInfo; +import cn.binarywang.wx.miniapp.bean.kefu.WxMaKfList; +import cn.binarywang.wx.miniapp.bean.kefu.WxMaKfSession; +import cn.binarywang.wx.miniapp.bean.kefu.WxMaKfSessionList; +import cn.binarywang.wx.miniapp.bean.kefu.request.WxMaKfAccountRequest; +import me.chanjar.weixin.common.error.WxErrorException; + +/** + *+ * 小程序客服管理接口. + * 不同于 WxMaCustomserviceWorkService (企业微信客服绑定) 和 WxMaMsgService.sendKefuMsg (发送客服消息), + * 此接口专门处理小程序客服账号管理、会话管理等功能。 + * + * 注意:小程序客服管理接口与公众号客服管理接口在API端点和功能上有所不同。 + *+ * + * @author Binary Wang + */ +public interface WxMaKefuService { + + /** + *
+ * 获取客服基本信息 + * 详情请见:获取客服基本信息 + * 接口url格式:https://api.weixin.qq.com/cgi-bin/customservice/getkflist?access_token=ACCESS_TOKEN + *+ * + * @return 客服列表 + * @throws WxErrorException 异常 + */ + WxMaKfList kfList() throws WxErrorException; + + /** + *
+ * 添加客服账号 + * 详情请见:添加客服账号 + * 接口url格式:https://api.weixin.qq.com/customservice/kfaccount/add?access_token=ACCESS_TOKEN + *+ * + * @param request 客服账号信息 + * @return 是否成功 + * @throws WxErrorException 异常 + */ + boolean kfAccountAdd(WxMaKfAccountRequest request) throws WxErrorException; + + /** + *
+ * 修改客服账号 + * 详情请见:修改客服账号 + * 接口url格式:https://api.weixin.qq.com/customservice/kfaccount/update?access_token=ACCESS_TOKEN + *+ * + * @param request 客服账号信息 + * @return 是否成功 + * @throws WxErrorException 异常 + */ + boolean kfAccountUpdate(WxMaKfAccountRequest request) throws WxErrorException; + + /** + *
+ * 删除客服账号 + * 详情请见:删除客服账号 + * 接口url格式:https://api.weixin.qq.com/customservice/kfaccount/del?access_token=ACCESS_TOKEN&kf_account=KFACCOUNT + *+ * + * @param kfAccount 客服账号 + * @return 是否成功 + * @throws WxErrorException 异常 + */ + boolean kfAccountDel(String kfAccount) throws WxErrorException; + + /** + *
+ * 创建会话 + * 详情请见:创建会话 + * 接口url格式:https://api.weixin.qq.com/customservice/kfsession/create?access_token=ACCESS_TOKEN + *+ * + * @param openid 用户openid + * @param kfAccount 客服账号 + * @return 是否成功 + * @throws WxErrorException 异常 + */ + boolean kfSessionCreate(String openid, String kfAccount) throws WxErrorException; + + /** + *
+ * 关闭会话 + * 详情请见:关闭会话 + * 接口url格式:https://api.weixin.qq.com/customservice/kfsession/close?access_token=ACCESS_TOKEN + *+ * + * @param openid 用户openid + * @param kfAccount 客服账号 + * @return 是否成功 + * @throws WxErrorException 异常 + */ + boolean kfSessionClose(String openid, String kfAccount) throws WxErrorException; + + /** + *
+ * 获取客户的会话状态 + * 详情请见:获取客户的会话状态 + * 接口url格式:https://api.weixin.qq.com/customservice/kfsession/getsession?access_token=ACCESS_TOKEN&openid=OPENID + *+ * + * @param openid 用户openid + * @return 会话信息 + * @throws WxErrorException 异常 + */ + WxMaKfSession kfSessionGet(String openid) throws WxErrorException; + + /** + *
+ * 获取客服的会话列表 + * 详情请见:获取客服的会话列表 + * 接口url格式:https://api.weixin.qq.com/customservice/kfsession/getsessionlist?access_token=ACCESS_TOKEN&kf_account=KFACCOUNT + *+ * + * @param kfAccount 客服账号 + * @return 会话列表 + * @throws WxErrorException 异常 + */ + WxMaKfSessionList kfSessionList(String kfAccount) throws WxErrorException; + +} \ No newline at end of file diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaService.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaService.java index 6ed9af7727..dc7425fa6e 100644 --- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaService.java +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaService.java @@ -16,7 +16,7 @@ import me.chanjar.weixin.common.util.http.RequestHttp; /** - * The interface Wx ma service. + * 微信小程序主服务接口,定义了所有小程序相关的核心操作方法。 * * @author Binary Wang */ @@ -37,115 +37,101 @@ public interface WxMaService extends WxService { String SET_DYNAMIC_DATA_URL = "https://api.weixin.qq.com/wxa/setdynamicdata"; /** - * 获取登录后的session信息. + * 获取登录后的 session 信息。 * - * @param jsCode 登录时获取的 code - * @return the wx ma jscode 2 session result - * @throws WxErrorException the wx error exception + * @param jsCode 登录时获取的 code + * @return 登录 session 结果对象 + * @throws WxErrorException 调用微信接口失败时抛出 */ WxMaJscode2SessionResult jsCode2SessionInfo(String jsCode) throws WxErrorException; /** - * 导入抽样数据 - * - *
+ * 导入抽样数据到微信后台,用于流量分配。
* 第三方通过调用微信API,将数据写入到setdynamicdata这个API。每个Post数据包不超过5K,若数据过多可开多进(线)程并发导入数据(例如:数据量为十万量级可以开50个线程并行导数据)。
* 文档地址:https://wsad.weixin.qq.com/wsad/zh_CN/htmledition/widget-docs-v3/html/custom/quickstart/implement/import/index.html
* http请求方式:POST http(s)://api.weixin.qq.com/wxa/setdynamicdata?access_token=ACCESS_TOKEN
- *
*
- * @param lifespan 数据有效时间,秒为单位,一般为86400,一天一次导入的频率
- * @param type 用于标识数据所属的服务类目
- * @param scene 1代表用于搜索的数据
- * @param data 推送到微信后台的数据列表,该数据被微信用于流量分配,注意该字段为string类型而不是object
- * @throws WxErrorException .
+ * @param lifespan 数据有效时间(秒),如 86400 表示一天
+ * @param type 数据所属服务类目标识
+ * @param scene 场景值,1 代表用于搜索的数据
+ * @param data 推送到微信后台的数据列表(字符串类型)
+ * @throws WxErrorException 调用微信接口失败时抛出
*/
void setDynamicData(int lifespan, String type, int scene, String data) throws WxErrorException;
/**
- *
- *
- *
- * 验证消息的确来自微信服务器.
+ * 校验消息是否来自微信服务器。
* 详情请见: http://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421135319&token=&lang=zh_CN
- *
*
- * @param timestamp the timestamp
- * @param nonce the nonce
- * @param signature the signature
- * @return the boolean
+ * @param timestamp 时间戳
+ * @param nonce 随机数
+ * @param signature 签名字符串
+ * @return 校验通过返回 true,否则返回 false
*/
boolean checkSignature(String timestamp, String nonce, String signature);
/**
- * 获取access_token, 不强制刷新access_token.
+ * 获取 access_token,不强制刷新。
*
- * @return the access token
- * @throws WxErrorException the wx error exception
- * @see #getAccessToken(boolean) #getAccessToken(boolean)
+ * @return access_token 字符串
+ * @throws WxErrorException 调用微信接口失败时抛出
+ * @see #getAccessToken(boolean)
*/
String getAccessToken() throws WxErrorException;
/**
+ * 获取 access_token,本方法线程安全。多线程同时刷新时只刷新一次,避免超出调用次数上限。
+ * 一般无需主动调用,所有接口会自动处理 token 过期。
+ * 详情见:http://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140183&token=&lang=zh_CN
*
- *
- * - * 获取access_token,本方法线程安全. - * 且在多线程同时刷新时只刷新一次,避免超出2000次/日的调用次数上限 - * - * 另:本service的所有方法都会在access_token过期是调用此方法 - * - * 程序员在非必要情况下尽量不要主动调用此方法 - * - * 详情请见: http://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140183&token=&lang=zh_CN - *- * - * @param forceRefresh 强制刷新 - * @return the access token - * @throws WxErrorException the wx error exception + * @param forceRefresh 是否强制刷新 + * @return access_token 字符串 + * @throws WxErrorException 调用微信接口失败时抛出 */ String getAccessToken(boolean forceRefresh) throws WxErrorException; /** - * - * - *
* 用户支付完成后,获取该用户的 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
- *
+ * 文档:https://developers.weixin.qq.com/miniprogram/dev/api/getPaidUnionId.html
*
- * @param openid 必填 支付用户唯一标识
- * @param transactionId 非必填 微信支付订单号
- * @param mchId 非必填 微信支付分配的商户号,和商户订单号配合使用
- * @param outTradeNo 非必填 微信支付商户订单号,和商户号配合使用
- * @return UnionId. paid union id
- * @throws WxErrorException .
+ * @param openid 支付用户唯一标识(必填)
+ * @param transactionId 微信支付订单号(可选)
+ * @param mchId 微信支付分配的商户号,与商户订单号配合使用(可选)
+ * @param outTradeNo 微信支付商户订单号,与商户号配合使用(可选)
+ * @return 用户的 UnionId
+ * @throws WxErrorException 调用微信接口失败时抛出
*/
String getPaidUnionId(String openid, String transactionId, String mchId, String outTradeNo)
throws WxErrorException;
/**
+ * 执行自定义的微信API请求。
+ *
- * Service没有实现某个API的时候,可以用这个,
- * 比{@link #get}和{@link #post}方法更灵活,可以自己构造RequestExecutor用来处理不同的参数和不同的返回类型。
- * 可以参考,{@link MediaUploadRequestExecutor}的实现方法
- *
- *
- * @param - * 设置当微信系统响应系统繁忙时,要等待多少 retrySleepMillis(ms) * 2^(重试次数 - 1) 再发起重试. - * 默认:1000ms - *- * - * @param retrySleepMillis 重试等待毫秒数 + * @param retrySleepMillis 重试等待的毫秒数,默认1000ms */ void setRetrySleepMillis(int retrySleepMillis); /** + * 设置微信系统繁忙时的最大重试次数。 * - * - *
- * 设置当微信系统响应系统繁忙时,最大重试次数. - * 默认:5次 - *- * - * @param maxRetryTimes 最大重试次数 + * @param maxRetryTimes 最大重试次数,默认5次 */ void setMaxRetryTimes(int maxRetryTimes); /** - * 获取WxMaConfig 对象. + * 获取当前小程序的配置信息对象。 * - * @return WxMaConfig wx ma config + * @return 当前小程序的WxMaConfig配置对象 */ WxMaConfig getWxMaConfig(); /** - * 注入 {@link WxMaConfig} 的实现. + * 注入小程序配置信息。 * - * @param maConfig config + * @param maConfig 小程序配置信息对象 */ void setWxMaConfig(WxMaConfig maConfig); /** - * Map里 加入新的 {@link WxMaConfig},适用于动态添加新的微信公众号配置. + * 动态添加新的小程序配置信息。 * - * @param miniappId 小程序标识 - * @param configStorage 新的微信配置 + * @param miniappId 小程序唯一标识 + * @param configStorage 新的小程序配置信息 */ void addConfig(String miniappId, WxMaConfig configStorage); /** - * 从 Map中 移除 {@link String miniappId} 所对应的 {@link WxMaConfig},适用于动态移除小程序配置. + * 动态移除指定小程序的配置信息。 * - * @param miniappId 对应小程序的标识 + * @param miniappId 小程序唯一标识 */ void removeConfig(String miniappId); /** - * 注入多个 {@link WxMaConfig} 的实现. 并为每个 {@link WxMaConfig} 赋予不同的 {@link String mpId} 值 随机采用一个{@link - * String mpId}进行Http初始化操作 + * 批量注入多个小程序配置信息。 * - * @param configs WxMaConfig map + * @param configs 小程序配置Map,key为小程序标识 */ void setMultiConfigs(Map
+ * 通过 code 换取用户登录态信息,用于多端登录场景(如手表端)。 + *
+ * 文档地址:多端登录 + * + * @param code 登录时获取的 code + * @param checkcode 手表授权页面返回的 checkcode + * @return 登录验证结果,包含 session_key、openid、unionid 和 is_limit 字段 + * @throws WxErrorException 调用微信接口失败时抛出 + */ + WxMaCode2VerifyInfoResult getCode2VerifyInfo(String code, String checkcode) throws WxErrorException; } diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaVodService.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaVodService.java index 547d280962..217a699c5a 100644 --- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaVodService.java +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaVodService.java @@ -6,39 +6,181 @@ import java.io.File; import java.util.List; +/** + * 小程序短剧管理服务接口。 + * 提供短剧视频上传、管理、审核、播放等相关功能。 + * 文档:https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/vod.html + * + */ public interface WxMaVodService { + + /** + * 获取媒体列表。 + * 分页获取已上传的媒体文件列表。 + * + * @param request 获取媒体列表请求对象 + * @return 媒体信息列表 + * @throws WxErrorException 获取失败时抛出 + */ List+ * 小程序 - 微信客服 相关接口 + * 负责处理 https://api.weixin.qq.com/customservice/work/** + * 文档:https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/kf-work/getKfWorkBound.html + * 绑定的企业ID,需和小程序主体一致。 + * 目前仅支持绑定非个人小程序。 + * Created by tryking123 on 2025/8/18. + *+ * + * @author tryking123 + */ +@RequiredArgsConstructor +public class WxMaCustomserviceWorkServiceImpl implements WxMaCustomserviceWorkService { + private static final String CORPID = "corpid"; + + private final WxMaService service; + + @Override + public WxMaCustomserviceResult getCustomservice() throws WxErrorException { + String responseContent = this.service.get(GET_CUSTOMSERVICE_URL, null); + return WxMaCustomserviceResult.fromJson(responseContent); + } + + @Override + public WxMaCustomserviceResult bindCustomservice(String corpid) throws WxErrorException { + JsonObject paramJson = new JsonObject(); + paramJson.addProperty(CORPID, corpid); + String response = this.service.post(BIND_CUSTOMSERVICE_URL, paramJson); + return WxMaCustomserviceResult.fromJson(response); + } + + @Override + public WxMaCustomserviceResult unbindCustomservice(String corpid) throws WxErrorException { + JsonObject paramJson = new JsonObject(); + paramJson.addProperty(CORPID, corpid); + String response = this.service.post(UNBIND_CUSTOMSERVICE_URL, paramJson); + return WxMaCustomserviceResult.fromJson(response); + } +} diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaDeviceSubscribeServiceImpl.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaDeviceSubscribeServiceImpl.java index 7f8dce1df8..632fe7bd94 100644 --- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaDeviceSubscribeServiceImpl.java +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaDeviceSubscribeServiceImpl.java @@ -2,18 +2,26 @@ import cn.binarywang.wx.miniapp.api.WxMaDeviceSubscribeService; import cn.binarywang.wx.miniapp.api.WxMaService; -import cn.binarywang.wx.miniapp.bean.device.WxMaDeviceSubscribeMessageRequest; -import cn.binarywang.wx.miniapp.bean.device.WxMaDeviceTicketRequest; +import cn.binarywang.wx.miniapp.bean.device.*; +import cn.binarywang.wx.miniapp.json.WxMaGsonBuilder; import com.google.gson.JsonObject; +import com.google.gson.reflect.TypeToken; import lombok.RequiredArgsConstructor; import me.chanjar.weixin.common.api.WxConsts; import me.chanjar.weixin.common.enums.WxType; import me.chanjar.weixin.common.error.WxError; import me.chanjar.weixin.common.error.WxErrorException; import me.chanjar.weixin.common.util.json.GsonParser; +import me.chanjar.weixin.common.util.json.WxGsonBuilder; + +import java.util.List; import static cn.binarywang.wx.miniapp.constant.WxMaApiUrlConstants.DeviceSubscribe.GET_SN_TICKET_URL; import static cn.binarywang.wx.miniapp.constant.WxMaApiUrlConstants.DeviceSubscribe.SEND_DEVICE_SUBSCRIBE_MSG_URL; +import static cn.binarywang.wx.miniapp.constant.WxMaApiUrlConstants.DeviceSubscribe.CREATE_IOT_GROUP_ID_URL; +import static cn.binarywang.wx.miniapp.constant.WxMaApiUrlConstants.DeviceSubscribe.GET_IOT_GROUP_INFO_URL; +import static cn.binarywang.wx.miniapp.constant.WxMaApiUrlConstants.DeviceSubscribe.ADD_IOT_GROUP_DEVICE_URL; +import static cn.binarywang.wx.miniapp.constant.WxMaApiUrlConstants.DeviceSubscribe.REMOVE_IOT_GROUP_DEVICE_URL; /** * 小程序设备订阅消息相关 API @@ -47,4 +55,46 @@ public void sendDeviceSubscribeMsg(WxMaDeviceSubscribeMessageRequest deviceSubsc throw new WxErrorException(WxError.fromJson(responseContent, WxType.MiniApp)); } } + + @Override + public String createIotGroupId(WxMaCreateIotGroupIdRequest createIotGroupIdRequest) throws WxErrorException { + String responseContent = this.service.post(CREATE_IOT_GROUP_ID_URL, createIotGroupIdRequest.toJson()); + JsonObject jsonObject = GsonParser.parse(responseContent); + if (jsonObject.get(WxConsts.ERR_CODE).getAsInt() != 0) { + throw new WxErrorException(WxError.fromJson(responseContent, WxType.MiniApp)); + } + return jsonObject.get("group_id").getAsString(); + } + + @Override + public WxMaIotGroupDeviceInfoResponse getIotGroupInfo(WxMaGetIotGroupInfoRequest getIotGroupInfoRequest) throws WxErrorException { + String responseContent = this.service.post(GET_IOT_GROUP_INFO_URL, getIotGroupInfoRequest.toJson()); + JsonObject jsonObject = GsonParser.parse(responseContent); + if (jsonObject.get(WxConsts.ERR_CODE).getAsInt() != 0) { + throw new WxErrorException(WxError.fromJson(responseContent, WxType.MiniApp)); + } + return WxGsonBuilder.create().fromJson(responseContent, WxMaIotGroupDeviceInfoResponse.class); + } + + @Override + public List
+ * 多端登录验证接口的响应
+ * 文档地址:https://developers.weixin.qq.com/miniprogram/dev/platform-capabilities/miniapp/openapi/code2Verifyinfo.html
+ *
+ * 微信返回报文:{"errcode": 0, "errmsg": "ok", "session_key":"xxx", "openid":"xxx", "unionid":"xxx", "is_limit": false}
+ *
+ *
+ * @author Binary Wang
+ */
+@Data
+@EqualsAndHashCode(callSuper = false)
+public class WxMaCode2VerifyInfoResult implements Serializable {
+ private static final long serialVersionUID = -2468325025088437364L;
+
+ @SerializedName("session_key")
+ private String sessionKey;
+
+ @SerializedName("openid")
+ private String openid;
+
+ @SerializedName("unionid")
+ private String unionid;
+
+ /**
+ * 是否为受限用户
+ */
+ @SerializedName("is_limit")
+ private Boolean isLimit;
+
+ public static WxMaCode2VerifyInfoResult fromJson(String json) {
+ return WxMaGsonBuilder.create().fromJson(json, WxMaCode2VerifyInfoResult.class);
+ }
+
+}
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/WxMaGroupEnterInfo.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/WxMaGroupEnterInfo.java
new file mode 100644
index 0000000000..e65ec602da
--- /dev/null
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/WxMaGroupEnterInfo.java
@@ -0,0 +1,46 @@
+package cn.binarywang.wx.miniapp.bean;
+
+import cn.binarywang.wx.miniapp.json.WxMaGsonBuilder;
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * 微信小程序群入口信息.
+ * 对应 wx.getGroupEnterInfo 接口返回的解密数据
+ *
+ * @see wx.getGroupEnterInfo 官方文档
+ */
+@Data
+public class WxMaGroupEnterInfo implements Serializable {
+ private static final long serialVersionUID = -8053613683499632227L;
+
+ /**
+ * 多聊群下返回的群唯一标识.
+ */
+ @SerializedName("opengid")
+ private String openGId;
+
+ /**
+ * 单聊群下返回的群唯一标识.
+ */
+ @SerializedName("open_single_roomid")
+ private String openSingleRoomid;
+
+ /**
+ * 用户在当前群的唯一标识.
+ */
+ @SerializedName("group_openid")
+ private String groupOpenid;
+
+ /**
+ * 聊天室类型.
+ */
+ @SerializedName("chat_type")
+ private Integer chatType;
+
+ public static WxMaGroupEnterInfo fromJson(String json) {
+ return WxMaGsonBuilder.create().fromJson(json, WxMaGroupEnterInfo.class);
+ }
+}
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/WxMaKefuMessage.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/WxMaKefuMessage.java
index 0cd77c7937..cdce681175 100644
--- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/WxMaKefuMessage.java
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/WxMaKefuMessage.java
@@ -40,6 +40,9 @@ public class WxMaKefuMessage implements Serializable {
@SerializedName("miniprogrampage")
private KfMaPage maPage;
+ @SerializedName("aimsgcontext")
+ private AiMsgContext aiMsgContext;
+
@Data
@AllArgsConstructor
@NoArgsConstructor
@@ -90,6 +93,16 @@ public static class KfMaPage implements Serializable {
private String thumbMediaId;
}
+ @Data
+ @AllArgsConstructor
+ @NoArgsConstructor
+ public static class AiMsgContext implements Serializable {
+ private static final long serialVersionUID = 1L;
+
+ @SerializedName("msgid")
+ private String msgId;
+ }
+
/**
* 获得文本消息builder.
*/
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/WxMaMessage.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/WxMaMessage.java
index 75d8174caf..88450403e3 100644
--- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/WxMaMessage.java
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/WxMaMessage.java
@@ -212,6 +212,107 @@ public class WxMaMessage implements Serializable {
@XStreamAlias("SubscribeMsgSentEvent")
private WxMaSubscribeMsgEvent.SubscribeMsgSentEvent subscribeMsgSentEvent;
+ // 小程序基本信息
+
+ //region 小程序基本信息 infoType=notify_3rd_wxa_auth_and_icp
+
+ /**
+ * 返回值
+ */
+ @XStreamAlias("ret")
+ private String ret;
+
+ /**
+ * 一级类目id
+ */
+ @XStreamAlias("first")
+ private String first;
+
+ /**
+ * 二级类目id
+ */
+ @XStreamAlias("second")
+ private String second;
+
+ /**
+ * 驳回原因
+ */
+ @XStreamAlias("reason")
+ private String reason;
+
+ /**
+ * 小程序代码审核驳回原因
+ */
+ @XStreamAlias("Reason")
+ private String weAppReason;
+
+ /**
+ * 昵称
+ */
+ @XStreamAlias("nickname")
+ private String nickname;
+
+ /**
+ * 原始通知内容
+ */
+ private String context;
+
+ /**
+ * 微信支付订单号
+ */
+ @XStreamAlias("transaction_id")
+ private String transactionId;
+ /**
+ * 商户号
+ */
+ @XStreamAlias("merchant_id")
+ private String merchantId;
+ /**
+ * 子商户号
+ */
+ @XStreamAlias("sub_merchant_id")
+ private String subMerchantId;
+ /**
+ * 商户订单号
+ */
+ @XStreamAlias("merchant_trade_no")
+ private String merchantTradeNo;
+ /**
+ * 支付成功时间,秒级时间戳
+ */
+ @XStreamAlias("pay_time")
+ private Long payTime;
+ /**
+ * 消息文本内容
+ */
+ @XStreamAlias("msg")
+ private String msg;
+ /**
+ * 发货时间,秒级时间戳
+ */
+ @XStreamAlias("shipped_time")
+ private Long shippedTime;
+ /**
+ * 预计结算时间,秒级时间戳。发货时推送才有该字段
+ */
+ @XStreamAlias("estimated_settlement_time")
+ private Long estimatedSettlementTime;
+ /**
+ * 确认收货方式:1. 手动确认收货;2. 自动确认收货。结算时推送才有该字段
+ */
+ @XStreamAlias("confirm_receive_method")
+ private Integer confirmReceiveMethod;
+ /**
+ * 确认收货时间,秒级时间戳。结算时推送才有该字段
+ */
+ @XStreamAlias("confirm_receive_time")
+ private Long confirmReceiveTime;
+ /**
+ * 订单结算时间,秒级时间戳。结算时推送才有该字段
+ */
+ @XStreamAlias("settlement_time")
+ private Long settlementTime;
+
/**
* 不要直接使用这个字段,
* 这个字段只是为了适配 SubscribeMsgPopupEvent SubscribeMsgChangeEvent SubscribeMsgSentEvent
@@ -261,7 +362,9 @@ public static WxMaMessage fromEncryptedXml(String encryptedXml,
WxMaConfig wxMaConfig, String timestamp, String nonce,
String msgSignature) {
String plainText = new WxMaCryptUtils(wxMaConfig).decryptXml(msgSignature, timestamp, nonce, encryptedXml);
- return fromXml(plainText);
+ WxMaMessage wxMaMessage = fromXml(plainText);
+ wxMaMessage.setContext(plainText);
+ return wxMaMessage;
}
public static WxMaMessage fromEncryptedXml(InputStream is, WxMaConfig wxMaConfig, String timestamp,
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/complaint/WxMaComplaintDetailRequest.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/complaint/WxMaComplaintDetailRequest.java
new file mode 100644
index 0000000000..759f7392bf
--- /dev/null
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/complaint/WxMaComplaintDetailRequest.java
@@ -0,0 +1,38 @@
+package cn.binarywang.wx.miniapp.bean.complaint;
+
+import cn.binarywang.wx.miniapp.json.WxMaGsonBuilder;
+import com.google.gson.annotations.SerializedName;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+
+/**
+ * 小程序投诉单详情请求实体
+ *
+ * @author Binary Wang
+ * created on 2025-01-01
+ */
+@Data
+@Builder(builderMethodName = "newBuilder")
+@NoArgsConstructor
+@AllArgsConstructor
+public class WxMaComplaintDetailRequest implements Serializable {
+ private static final long serialVersionUID = 3244929701614280806L;
+
+ /**
+ * + * 字段名:投诉单号 + * 是否必填:是 + * 描述:投诉单对应的投诉单号 + *+ */ + @SerializedName("complaint_id") + private String complaintId; + + public String toJson() { + return WxMaGsonBuilder.create().toJson(this); + } +} \ No newline at end of file diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/complaint/WxMaComplaintDetailResult.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/complaint/WxMaComplaintDetailResult.java new file mode 100644 index 0000000000..52a0be1704 --- /dev/null +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/complaint/WxMaComplaintDetailResult.java @@ -0,0 +1,166 @@ +package cn.binarywang.wx.miniapp.bean.complaint; + +import cn.binarywang.wx.miniapp.bean.WxMaBaseResponse; +import com.google.gson.annotations.SerializedName; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.io.Serializable; +import java.util.List; + +/** + * 小程序投诉单详情结果 + * + * @author Binary Wang + * created on 2025-01-01 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class WxMaComplaintDetailResult extends WxMaBaseResponse { + + /** + *
+ * 字段名:投诉单号 + * 是否必填:是 + * 描述:投诉单对应的投诉单号 + *+ */ + @SerializedName("complaint_id") + private String complaintId; + + /** + *
+ * 字段名:投诉时间 + * 是否必填:是 + * 描述:用户提交投诉的时间,遵循rfc3339标准格式,格式为YYYY-MM-DDTHH:mm:ss+TIMEZONE + *+ */ + @SerializedName("complaint_time") + private String complaintTime; + + /** + *
+ * 字段名:投诉详情 + * 是否必填:是 + * 描述:用户提交的投诉详情 + *+ */ + @SerializedName("complaint_detail") + private String complaintDetail; + + /** + *
+ * 字段名:投诉状态 + * 是否必填:是 + * 描述:投诉单状态:WAITING_MERCHANT_RESPONSE-等待商户回复 MERCHANT_RESPONSED-商户已回复 WAITING_USER_RESPONSE-等待用户回复 USER_RESPONSED-用户已回复 COMPLAINT_COMPLETED-投诉已完成 + *+ */ + @SerializedName("complaint_state") + private String complaintState; + + /** + *
+ * 字段名:投诉人openid + * 是否必填:是 + * 描述:投诉人在小程序的openid + *+ */ + @SerializedName("openid") + private String openid; + + /** + *
+ * 字段名:投诉人联系方式 + * 是否必填:否 + * 描述:投诉人联系方式,可能为空 + *+ */ + @SerializedName("phone_number") + private String phoneNumber; + + /** + *
+ * 字段名:被投诉订单信息 + * 是否必填:是 + * 描述:被投诉的订单信息 + *+ */ + @SerializedName("complaint_order_info") + private ComplaintOrderInfo complaintOrderInfo; + + /** + *
+ * 字段名:投诉材料 + * 是否必填:否 + * 描述:用户上传的投诉相关的图片凭证 + *+ */ + @SerializedName("complaint_media_list") + private List
+ * 字段名:商户订单号 + * 是否必填:是 + * 描述:商户系统内部订单号 + *+ */ + @SerializedName("transaction_id") + private String transactionId; + + /** + *
+ * 字段名:微信订单号 + * 是否必填:是 + * 描述:微信支付系统生成的订单号 + *+ */ + @SerializedName("out_trade_no") + private String outTradeNo; + + /** + *
+ * 字段名:订单金额 + * 是否必填:是 + * 描述:订单金额,单位为分 + *+ */ + @SerializedName("amount") + private Integer amount; + } + + /** + * 投诉材料 + */ + @Data + public static class ComplaintMedia implements Serializable { + private static final long serialVersionUID = 3244929701614280806L; + + /** + *
+ * 字段名:媒体文件业务类型 + * 是否必填:是 + * 描述:媒体文件对应的业务类型:USER_COMPLAINT_IMAGE-用户投诉图片 + *+ */ + @SerializedName("media_type") + private String mediaType; + + /** + *
+ * 字段名:媒体文件请求URL + * 是否必填:是 + * 描述:微信返回的媒体文件请求URL,通过该URL可以获取到对应的媒体文件(图片、视频等) + *+ */ + @SerializedName("media_url") + private String mediaUrl; + } +} \ No newline at end of file diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/complaint/WxMaComplaintNotifyUrlRequest.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/complaint/WxMaComplaintNotifyUrlRequest.java new file mode 100644 index 0000000000..6af338c974 --- /dev/null +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/complaint/WxMaComplaintNotifyUrlRequest.java @@ -0,0 +1,38 @@ +package cn.binarywang.wx.miniapp.bean.complaint; + +import cn.binarywang.wx.miniapp.json.WxMaGsonBuilder; +import com.google.gson.annotations.SerializedName; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * 小程序投诉通知回调URL请求实体 + * + * @author Binary Wang + * created on 2025-01-01 + */ +@Data +@Builder(builderMethodName = "newBuilder") +@NoArgsConstructor +@AllArgsConstructor +public class WxMaComplaintNotifyUrlRequest implements Serializable { + private static final long serialVersionUID = 3244929701614280806L; + + /** + *
+ * 字段名:通知地址 + * 是否必填:是 + * 描述:通知地址,仅支持https + *+ */ + @SerializedName("url") + private String url; + + public String toJson() { + return WxMaGsonBuilder.create().toJson(this); + } +} \ No newline at end of file diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/complaint/WxMaComplaintNotifyUrlResult.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/complaint/WxMaComplaintNotifyUrlResult.java new file mode 100644 index 0000000000..7771998b80 --- /dev/null +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/complaint/WxMaComplaintNotifyUrlResult.java @@ -0,0 +1,37 @@ +package cn.binarywang.wx.miniapp.bean.complaint; + +import cn.binarywang.wx.miniapp.bean.WxMaBaseResponse; +import com.google.gson.annotations.SerializedName; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 小程序投诉通知回调URL结果 + * + * @author Binary Wang + * created on 2025-01-01 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class WxMaComplaintNotifyUrlResult extends WxMaBaseResponse { + + /** + *
+ * 字段名:通知地址 + * 是否必填:是 + * 描述:通知地址,仅支持https + *+ */ + @SerializedName("url") + private String url; + + /** + *
+ * 字段名:签名串 + * 是否必填:是 + * 描述:用于验证通知消息的签名串 + *+ */ + @SerializedName("signature") + private String signature; +} \ No newline at end of file diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/complaint/WxMaComplaintRequest.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/complaint/WxMaComplaintRequest.java new file mode 100644 index 0000000000..9ac45e0c12 --- /dev/null +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/complaint/WxMaComplaintRequest.java @@ -0,0 +1,70 @@ +package cn.binarywang.wx.miniapp.bean.complaint; + +import cn.binarywang.wx.miniapp.json.WxMaGsonBuilder; +import com.google.gson.annotations.SerializedName; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * 小程序交易投诉查询请求实体 + * + * @author Binary Wang + * created on 2025-01-01 + */ +@Data +@Builder(builderMethodName = "newBuilder") +@NoArgsConstructor +@AllArgsConstructor +public class WxMaComplaintRequest implements Serializable { + private static final long serialVersionUID = 3244929701614280806L; + + /** + *
+ * 字段名:开始日期 + * 是否必填:是 + * 描述:查询的起始时间,格式为YYYY-MM-DD,例如2021-01-01 + *+ */ + @SerializedName("begin_date") + private String beginDate; + + /** + *
+ * 字段名:结束日期 + * 是否必填:是 + * 描述:查询的结束时间,格式为YYYY-MM-DD,例如2021-01-31 + *+ */ + @SerializedName("end_date") + private String endDate; + + /** + *
+ * 字段名:分页大小 + * 是否必填:否 + * 描述:单次拉取条目,最大为50,不传默认为10 + *+ */ + @SerializedName("limit") + @Builder.Default + private Integer limit = 10; + + /** + *
+ * 字段名:分页开始位置 + * 是否必填:否 + * 描述:该次请求的分页开始位置,从0开始计数,例如offset=10,表示从第11条记录开始返回,不传默认为0 + *+ */ + @SerializedName("offset") + @Builder.Default + private Integer offset = 0; + + public String toJson() { + return WxMaGsonBuilder.create().toJson(this); + } +} \ No newline at end of file diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/complaint/WxMaComplaintResult.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/complaint/WxMaComplaintResult.java new file mode 100644 index 0000000000..0e4208fdc1 --- /dev/null +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/complaint/WxMaComplaintResult.java @@ -0,0 +1,156 @@ +package cn.binarywang.wx.miniapp.bean.complaint; + +import cn.binarywang.wx.miniapp.bean.WxMaBaseResponse; +import com.google.gson.annotations.SerializedName; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.io.Serializable; +import java.util.List; + +/** + * 小程序交易投诉查询结果 + * + * @author Binary Wang + * created on 2025-01-01 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class WxMaComplaintResult extends WxMaBaseResponse { + + /** + *
+ * 字段名:投诉单信息 + * 是否必填:是 + * 描述:查询返回的投诉单信息 + *+ */ + @SerializedName("data") + private List
+ * 字段名:总数 + * 是否必填:是 + * 描述:总投诉单条数,用于分页展示 + *+ */ + @SerializedName("total_count") + private Integer totalCount; + + /** + * 投诉单信息 + */ + @Data + public static class Complaint implements Serializable { + private static final long serialVersionUID = 3244929701614280806L; + + /** + *
+ * 字段名:投诉单号 + * 是否必填:是 + * 描述:投诉单对应的投诉单号 + *+ */ + @SerializedName("complaint_id") + private String complaintId; + + /** + *
+ * 字段名:投诉时间 + * 是否必填:是 + * 描述:用户提交投诉的时间,遵循rfc3339标准格式,格式为YYYY-MM-DDTHH:mm:ss+TIMEZONE + *+ */ + @SerializedName("complaint_time") + private String complaintTime; + + /** + *
+ * 字段名:投诉详情 + * 是否必填:是 + * 描述:用户提交的投诉详情 + *+ */ + @SerializedName("complaint_detail") + private String complaintDetail; + + /** + *
+ * 字段名:投诉状态 + * 是否必填:是 + * 描述:投诉单状态:WAITING_MERCHANT_RESPONSE-等待商户回复 MERCHANT_RESPONSED-商户已回复 WAITING_USER_RESPONSE-等待用户回复 USER_RESPONSED-用户已回复 COMPLAINT_COMPLETED-投诉已完成 + *+ */ + @SerializedName("complaint_state") + private String complaintState; + + /** + *
+ * 字段名:投诉人openid + * 是否必填:是 + * 描述:投诉人在小程序的openid + *+ */ + @SerializedName("openid") + private String openid; + + /** + *
+ * 字段名:投诉人联系方式 + * 是否必填:否 + * 描述:投诉人联系方式,可能为空 + *+ */ + @SerializedName("phone_number") + private String phoneNumber; + + /** + *
+ * 字段名:被投诉订单信息 + * 是否必填:是 + * 描述:被投诉的订单信息 + *+ */ + @SerializedName("complaint_order_info") + private ComplaintOrderInfo complaintOrderInfo; + } + + /** + * 被投诉订单信息 + */ + @Data + public static class ComplaintOrderInfo implements Serializable { + private static final long serialVersionUID = 3244929701614280806L; + + /** + *
+ * 字段名:商户订单号 + * 是否必填:是 + * 描述:商户系统内部订单号 + *+ */ + @SerializedName("transaction_id") + private String transactionId; + + /** + *
+ * 字段名:微信订单号 + * 是否必填:是 + * 描述:微信支付系统生成的订单号 + *+ */ + @SerializedName("out_trade_no") + private String outTradeNo; + + /** + *
+ * 字段名:订单金额 + * 是否必填:是 + * 描述:订单金额,单位为分 + *+ */ + @SerializedName("amount") + private Integer amount; + } +} \ No newline at end of file diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/complaint/WxMaCompleteRequest.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/complaint/WxMaCompleteRequest.java new file mode 100644 index 0000000000..71d1066024 --- /dev/null +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/complaint/WxMaCompleteRequest.java @@ -0,0 +1,38 @@ +package cn.binarywang.wx.miniapp.bean.complaint; + +import cn.binarywang.wx.miniapp.json.WxMaGsonBuilder; +import com.google.gson.annotations.SerializedName; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * 小程序反馈处理完成请求实体 + * + * @author Binary Wang + * created on 2025-01-01 + */ +@Data +@Builder(builderMethodName = "newBuilder") +@NoArgsConstructor +@AllArgsConstructor +public class WxMaCompleteRequest implements Serializable { + private static final long serialVersionUID = 3244929701614280806L; + + /** + *
+ * 字段名:投诉单号 + * 是否必填:是 + * 描述:投诉单对应的投诉单号 + *+ */ + @SerializedName("complaint_id") + private String complaintId; + + public String toJson() { + return WxMaGsonBuilder.create().toJson(this); + } +} \ No newline at end of file diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/complaint/WxMaNegotiationHistoryRequest.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/complaint/WxMaNegotiationHistoryRequest.java new file mode 100644 index 0000000000..a03742b6af --- /dev/null +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/complaint/WxMaNegotiationHistoryRequest.java @@ -0,0 +1,60 @@ +package cn.binarywang.wx.miniapp.bean.complaint; + +import cn.binarywang.wx.miniapp.json.WxMaGsonBuilder; +import com.google.gson.annotations.SerializedName; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * 小程序查询投诉协商历史请求实体 + * + * @author Binary Wang + * created on 2025-01-01 + */ +@Data +@Builder(builderMethodName = "newBuilder") +@NoArgsConstructor +@AllArgsConstructor +public class WxMaNegotiationHistoryRequest implements Serializable { + private static final long serialVersionUID = 3244929701614280806L; + + /** + *
+ * 字段名:投诉单号 + * 是否必填:是 + * 描述:投诉单对应的投诉单号 + *+ */ + @SerializedName("complaint_id") + private String complaintId; + + /** + *
+ * 字段名:分页大小 + * 是否必填:否 + * 描述:单次拉取条目,最大为50,不传默认为10 + *+ */ + @SerializedName("limit") + @Builder.Default + private Integer limit = 10; + + /** + *
+ * 字段名:分页开始位置 + * 是否必填:否 + * 描述:该次请求的分页开始位置,从0开始计数,例如offset=10,表示从第11条记录开始返回,不传默认为0 + *+ */ + @SerializedName("offset") + @Builder.Default + private Integer offset = 0; + + public String toJson() { + return WxMaGsonBuilder.create().toJson(this); + } +} \ No newline at end of file diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/complaint/WxMaNegotiationHistoryResult.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/complaint/WxMaNegotiationHistoryResult.java new file mode 100644 index 0000000000..194daca9ff --- /dev/null +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/complaint/WxMaNegotiationHistoryResult.java @@ -0,0 +1,88 @@ +package cn.binarywang.wx.miniapp.bean.complaint; + +import cn.binarywang.wx.miniapp.bean.WxMaBaseResponse; +import com.google.gson.annotations.SerializedName; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.io.Serializable; +import java.util.List; + +/** + * 小程序查询投诉协商历史结果 + * + * @author Binary Wang + * created on 2025-01-01 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class WxMaNegotiationHistoryResult extends WxMaBaseResponse { + + /** + *
+ * 字段名:协商历史 + * 是否必填:是 + * 描述:协商历史记录 + *+ */ + @SerializedName("data") + private List
+ * 字段名:总数 + * 是否必填:是 + * 描述:总协商历史条数,用于分页展示 + *+ */ + @SerializedName("total_count") + private Integer totalCount; + + /** + * 协商历史 + */ + @Data + public static class NegotiationHistory implements Serializable { + private static final long serialVersionUID = 3244929701614280806L; + + /** + *
+ * 字段名:操作时间 + * 是否必填:是 + * 描述:操作时间,遵循rfc3339标准格式,格式为YYYY-MM-DDTHH:mm:ss+TIMEZONE + *+ */ + @SerializedName("operate_time") + private String operateTime; + + /** + *
+ * 字段名:操作类型 + * 是否必填:是 + * 描述:操作类型:USER_CREATE_COMPLAINT-用户提交投诉 USER_CONTINUE_COMPLAINT-用户继续投诉 MERCHANT_RESPONSE-商户回复 MERCHANT_CONFIRM_COMPLETE-商户确认完成处理 + *+ */ + @SerializedName("operate_type") + private String operateType; + + /** + *
+ * 字段名:操作内容 + * 是否必填:是 + * 描述:具体的操作内容 + *+ */ + @SerializedName("operate_details") + private String operateDetails; + + /** + *
+ * 字段名:图片凭证 + * 是否必填:否 + * 描述:操作过程中上传的图片凭证 + *+ */ + @SerializedName("image_list") + private List
+ * 字段名:投诉单号 + * 是否必填:是 + * 描述:投诉单对应的投诉单号 + *+ */ + @SerializedName("complaint_id") + private String complaintId; + + /** + *
+ * 字段名:回复内容 + * 是否必填:是 + * 描述:具体的回复内容,长度不超过200字符 + *+ */ + @SerializedName("response_content") + private String responseContent; + + /** + *
+ * 字段名:回复图片 + * 是否必填:否 + * 描述:回复的图片凭证,最多可传5张图片,由图片上传接口返回 + *+ */ + @SerializedName("response_images") + private List
+ * 字段名:跳转链接 + * 是否必填:否 + * 描述:点击跳转链接 + *+ */ + @SerializedName("jump_url") + private String jumpUrl; + + /** + *
+ * 字段名:跳转链接文案 + * 是否必填:否 + * 描述:跳转链接文案,在response_content中展示的跳转链接文案,长度不超过10个字符 + *+ */ + @SerializedName("jump_url_text") + private String jumpUrlText; + + public String toJson() { + return WxMaGsonBuilder.create().toJson(this); + } +} \ No newline at end of file diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/customservice/WxMaCustomserviceResult.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/customservice/WxMaCustomserviceResult.java new file mode 100644 index 0000000000..e7a9a46de3 --- /dev/null +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/customservice/WxMaCustomserviceResult.java @@ -0,0 +1,56 @@ +package cn.binarywang.wx.miniapp.bean.customservice; + +import cn.binarywang.wx.miniapp.json.WxMaGsonBuilder; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.google.gson.annotations.SerializedName; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; +import java.util.List; + +/** + * 客服绑定结果信息,包括错误码、主体名称、企业ID和绑定时间戳。 + *
+ * 字段说明: + *
+ * 文档地址:推送用工消息 + *
+ * + * @author Binary Wang + * created on 2025-12-19 + */ +@Data +@Builder(builderMethodName = "newBuilder") +@NoArgsConstructor +@AllArgsConstructor +public class WxMaSendEmployeeMsgRequest implements Serializable { + private static final long serialVersionUID = 1L; + + /** + *+ * 字段名:用户openid + * 是否必填:是 + * 描述:需要接收消息的用户openid + *+ */ + @SerializedName("openid") + private String openid; + + /** + *
+ * 字段名:企业id + * 是否必填:是 + * 描述:企业id,小程序管理员在微信开放平台配置 + *+ */ + @SerializedName("corp_id") + private String corpId; + + /** + *
+ * 字段名:消息内容 + * 是否必填:是 + * 描述:推送的消息内容,文本格式,最长不超过200个字符 + *+ */ + @SerializedName("msg") + private String msg; + + public String toJson() { + return WxMaGsonBuilder.create().toJson(this); + } +} diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/employee/WxMaUnbindEmployeeRequest.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/employee/WxMaUnbindEmployeeRequest.java new file mode 100644 index 0000000000..e56d84670c --- /dev/null +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/employee/WxMaUnbindEmployeeRequest.java @@ -0,0 +1,51 @@ +package cn.binarywang.wx.miniapp.bean.employee; + +import cn.binarywang.wx.miniapp.json.WxMaGsonBuilder; +import com.google.gson.annotations.SerializedName; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * 小程序解绑用工关系请求实体 + *
+ * 文档地址:解绑用工关系 + *
+ * + * @author Binary Wang + * created on 2025-12-19 + */ +@Data +@Builder(builderMethodName = "newBuilder") +@NoArgsConstructor +@AllArgsConstructor +public class WxMaUnbindEmployeeRequest implements Serializable { + private static final long serialVersionUID = 1L; + + /** + *+ * 字段名:用户openid + * 是否必填:是 + * 描述:需要解绑的用户openid + *+ */ + @SerializedName("openid") + private String openid; + + /** + *
+ * 字段名:企业id + * 是否必填:是 + * 描述:企业id,小程序管理员在微信开放平台配置 + *+ */ + @SerializedName("corp_id") + private String corpId; + + public String toJson() { + return WxMaGsonBuilder.create().toJson(this); + } +} diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/express/request/WxMaExpressOrderCargo.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/express/request/WxMaExpressOrderCargo.java index 96817a2256..b6de21b9e6 100644 --- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/express/request/WxMaExpressOrderCargo.java +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/express/request/WxMaExpressOrderCargo.java @@ -34,7 +34,7 @@ public class WxMaExpressOrderCargo implements Serializable { * 描述: 单位是千克(kg) * */ - private Integer weight; + private Double weight; /** * 包裹长度 @@ -44,7 +44,7 @@ public class WxMaExpressOrderCargo implements Serializable { * */ @SerializedName("space_x") - private Integer spaceLength; + private Double spaceLength; /** * 包裹宽度 @@ -54,7 +54,7 @@ public class WxMaExpressOrderCargo implements Serializable { * */ @SerializedName("space_y") - private Integer spaceWidth; + private Double spaceWidth; /** * 包裹高度 @@ -64,7 +64,7 @@ public class WxMaExpressOrderCargo implements Serializable { * */ @SerializedName("space_z") - private Integer spaceHeight; + private Double spaceHeight; /** * 包裹中商品详情列表 diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/kefu/WxMaKfInfo.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/kefu/WxMaKfInfo.java new file mode 100644 index 0000000000..476056d518 --- /dev/null +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/kefu/WxMaKfInfo.java @@ -0,0 +1,79 @@ +package cn.binarywang.wx.miniapp.bean.kefu; + +import cn.binarywang.wx.miniapp.json.WxMaGsonBuilder; +import com.google.gson.annotations.SerializedName; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * 小程序客服信息. + * + * @author Binary Wang + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class WxMaKfInfo implements Serializable { + private static final long serialVersionUID = -7916302137791763175L; + + /** + * 客服账号. + */ + @SerializedName("kf_account") + private String kfAccount; + + /** + * 客服昵称. + */ + @SerializedName("kf_nick") + private String kfNick; + + /** + * 客服密码. + */ + @SerializedName("kf_id") + private String kfId; + + /** + * 客服头像. + */ + @SerializedName("kf_headimgurl") + private String kfHeadImgUrl; + + /** + * 如果客服帐号已绑定了客服人员微信号,则此处显示微信号. + */ + @SerializedName("kf_wx") + private String kfWx; + + /** + * 如果客服帐号尚未绑定微信号,但是已经发起了一个绑定邀请,则此处显示绑定邀请的微信号. + */ + @SerializedName("invite_wx") + private String inviteWx; + + /** + * 邀请的状态,有等待确认"waiting",被拒绝"rejected",过期"expired". + */ + @SerializedName("invite_expire_time") + private Long inviteExpireTime; + + /** + * 邀请的过期时间,为unix时间戳. + */ + @SerializedName("invite_status") + private String inviteStatus; + + public static WxMaKfInfo fromJson(String json) { + return WxMaGsonBuilder.create().fromJson(json, WxMaKfInfo.class); + } + + public String toJson() { + return WxMaGsonBuilder.create().toJson(this); + } +} \ No newline at end of file diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/kefu/WxMaKfList.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/kefu/WxMaKfList.java new file mode 100644 index 0000000000..8dd87a1728 --- /dev/null +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/kefu/WxMaKfList.java @@ -0,0 +1,35 @@ +package cn.binarywang.wx.miniapp.bean.kefu; + +import cn.binarywang.wx.miniapp.json.WxMaGsonBuilder; +import com.google.gson.annotations.SerializedName; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; +import java.util.List; + +/** + * 小程序客服列表. + * + * @author Binary Wang + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class WxMaKfList implements Serializable { + private static final long serialVersionUID = 6416633293297389972L; + + @SerializedName("kf_list") + private List
* {@link cn.binarywang.wx.miniapp.api.impl.BaseWxMaServiceImpl#setRetrySleepMillis(int)}
*
+ *
+ * @return 重试间隔,单位:毫秒
*/
int getRetrySleepMillis();
/**
- * http 请求最大重试次数
+ * HTTP 请求最大重试次数
*
*
* {@link cn.binarywang.wx.miniapp.api.impl.BaseWxMaServiceImpl#setMaxRetryTimes(int)}
*
+ *
+ * @return 最大重试次数
*/
int getMaxRetryTimes();
/**
- * http client builder
+ * 获取用于创建 HTTP 客户端的 ApacheHttpClientBuilder
*
- * @return ApacheHttpClientBuilder apache http client builder
+ * @return ApacheHttpClientBuilder 实例
*/
ApacheHttpClientBuilder getApacheHttpClientBuilder();
/**
- * 是否自动刷新token
+ * 是否在 token 失效时自动刷新
*
- * @return the boolean
+ * @return 如果自动刷新则返回 true,否则返回 false
*/
boolean autoRefreshToken();
/**
- * 设置自定义的apiHost地址
- * 具体取值,可以参考https://developers.weixin.qq.com/doc/offiaccount/Basic_Information/Interface_field_description.html
+ * 设置自定义的 apiHost 地址
+ * 具体取值,可以参考 API 域名文档
*
- * @param apiHostUrl api域名地址
+ * @param apiHostUrl api 域名地址
*/
void setApiHostUrl(String apiHostUrl);
/**
- * 获取自定义的apiHost地址,用于替换原请求中的https://api.weixin.qq.com
- * 具体取值,可以参考https://developers.weixin.qq.com/doc/offiaccount/Basic_Information/Interface_field_description.html
+ * 获取自定义的 apiHost 地址,用于替换原请求中的 https://api.weixin.qq.com
+ * 具体取值,可以参考 API 域名文档
*
- * @return 自定义的api域名地址
+ * @return 自定义的 api 域名地址
*/
String getApiHostUrl();
/**
- * 获取自定义的获取accessToken地址,用于向自定义统一服务获取accessToken
+ * 获取自定义的获取 accessToken 地址,用于向自定义统一服务获取 accessToken
*
- * @return 自定义的获取accessToken地址
+ * @return 自定义的获取 accessToken 地址
*/
String getAccessTokenUrl();
/**
- * 设置自定义的获取accessToken地址 可用于设置获取accessToken的自定义服务
+ * 设置自定义的获取 accessToken 地址,可用于设置获取 accessToken 的自定义服务
*
- * @param accessTokenUrl 自定义的获取accessToken地址
+ * @param accessTokenUrl 自定义的获取 accessToken 地址
*/
void setAccessTokenUrl(String accessTokenUrl);
/**
- * 服务端API签名用到的RSA私钥【pkcs8格式,会以 -----BEGIN PRIVATE KEY-----开头, 'BEGIN RSA PRIVATE
- * KEY'的是pkcs1格式,需要转换(可用openssl转换)。 设置参考:
- * https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/getting_started/api_signature.html
+ * 服务端 API 签名用到的 RSA 私钥(pkcs8 格式,会以 -----BEGIN PRIVATE KEY----- 开头,
+ * 'BEGIN RSA PRIVATE KEY' 的是 pkcs1 格式,需要转换(可用 openssl 转换)。设置参考:
+ * API 签名文档
*
- * @return rsa private key string
+ * @return RSA 私钥字符串(pkcs8 格式)
*/
String getApiSignatureRsaPrivateKey();
/**
- * 服务端API签名用到的AES密钥
- * https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/getting_started/api_signature.html
+ * 服务端 API 签名用到的 AES 密钥
+ * API 签名文档
*
- * @return aes key string
+ * @return AES 密钥字符串
*/
String getApiSignatureAesKey();
@@ -307,6 +343,52 @@ default void updateAccessTokenBefore(WxAccessTokenEntity wxAccessTokenEntity) {}
/** 密钥对应的序号 */
String getApiSignatureRsaPrivateKeySn();
- /** 密钥对应的小程序ID (普通小程序同 appId, 托管第三方平台的是 componentAppId) */
+ /** 密钥对应的小程序 ID(普通小程序为 appId,托管第三方平台为 componentAppId) */
String getWechatMpAppid();
+
+ /** 微信 API 默认主机地址 */
+ String DEFAULT_API_HOST_URL = "https://api.weixin.qq.com";
+ /** 微信云托管使用的 HTTP 协议主机地址 */
+ String CLOUD_RUN_API_HOST_URL = "http://api.weixin.qq.com";
+
+ /**
+ * 是否使用微信云托管内网模式
+ * 当部署在微信云托管环境时,api.weixin.qq.com 会被解析为内网地址,此时需要使用 HTTP 协议访问
+ * 开启此配置后,SDK 会自动将 https://api.weixin.qq.com 替换为 http://api.weixin.qq.com
+ *
+ * @see 微信云托管内网调用微信接口
+ * @return 是否使用微信云托管模式
+ */
+ default boolean isUseWxCloudRun() {
+ return false;
+ }
+
+ /**
+ * 设置是否使用微信云托管内网模式
+ * 当部署在微信云托管环境时,api.weixin.qq.com 会被解析为内网地址,此时需要使用 HTTP 协议访问
+ * 开启此配置后,SDK 会自动将 https://api.weixin.qq.com 替换为 http://api.weixin.qq.com
+ *
+ * @see 微信云托管内网调用微信接口
+ * @param useWxCloudRun 是否使用微信云托管模式
+ */
+ default void setUseWxCloudRun(boolean useWxCloudRun) {
+ // 默认空实现
+ }
+
+ /**
+ * 根据配置获取实际应使用的 API 主机地址
+ * 优先级:自定义 apiHostUrl > 微信云托管模式 > 默认 HTTPS 地址
+ *
+ * @return 实际应使用的 API 主机地址
+ */
+ default String getEffectiveApiHostUrl() {
+ String apiHostUrl = getApiHostUrl();
+ if (apiHostUrl != null && !apiHostUrl.isEmpty()) {
+ return apiHostUrl;
+ }
+ if (isUseWxCloudRun()) {
+ return CLOUD_RUN_API_HOST_URL;
+ }
+ return DEFAULT_API_HOST_URL;
+ }
}
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/config/impl/WxMaDefaultConfigImpl.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/config/impl/WxMaDefaultConfigImpl.java
index ab82d6209e..07dfaefcc9 100644
--- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/config/impl/WxMaDefaultConfigImpl.java
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/config/impl/WxMaDefaultConfigImpl.java
@@ -69,6 +69,10 @@ public class WxMaDefaultConfigImpl implements WxMaConfig {
private String apiHostUrl;
private String accessTokenUrl;
+ /** 是否使用微信云托管模式(使用 HTTP 协议访问内网地址) */
+ @Getter(AccessLevel.NONE)
+ private boolean useWxCloudRun = false;
+
/** 自定义配置token的消费者 */
@Setter private Consumer+ * 文档地址:https://developers.weixin.qq.com/miniprogram/dev/platform-capabilities/business-capabilities/guarantee/complaint.html + *+ */ + public interface Complaint { + /** 查询投诉单列表 */ + String QUERY_COMPLAINTS_URL = "https://api.weixin.qq.com/cgi-bin/miniapp/complaint/list"; + /** 查询投诉单详情 */ + String GET_COMPLAINT_URL = "https://api.weixin.qq.com/cgi-bin/miniapp/complaint/detail"; + /** 查询投诉协商历史 */ + String QUERY_NEGOTIATION_HISTORY_URL = "https://api.weixin.qq.com/cgi-bin/miniapp/complaint/negotiation/history"; + /** 创建投诉通知回调地址 */ + String ADD_COMPLAINT_NOTIFY_URL = "https://api.weixin.qq.com/cgi-bin/miniapp/complaint/notify/add"; + /** 查询投诉通知回调地址 */ + String GET_COMPLAINT_NOTIFY_URL = "https://api.weixin.qq.com/cgi-bin/miniapp/complaint/notify/get"; + /** 更新投诉通知回调地址 */ + String UPDATE_COMPLAINT_NOTIFY_URL = "https://api.weixin.qq.com/cgi-bin/miniapp/complaint/notify/update"; + /** 删除投诉通知回调地址 */ + String DELETE_COMPLAINT_NOTIFY_URL = "https://api.weixin.qq.com/cgi-bin/miniapp/complaint/notify/delete"; + /** 提交回复 */ + String SUBMIT_RESPONSE_URL = "https://api.weixin.qq.com/cgi-bin/miniapp/complaint/response"; + /** 反馈处理完成 */ + String COMPLETE_COMPLAINT_URL = "https://api.weixin.qq.com/cgi-bin/miniapp/complaint/complete"; + /** 上传反馈图片 */ + String UPLOAD_RESPONSE_IMAGE_URL = "https://api.weixin.qq.com/cgi-bin/miniapp/complaint/upload"; + } + + /** 用工关系 */ + public interface Employee { + /** 解绑用工关系 */ + String UNBIND_EMPLOYEE_URL = "https://api.weixin.qq.com/wxa/unbinduserb2cauthinfo"; + /** 推送用工消息 */ + String SEND_EMPLOYEE_MSG_URL = "https://api.weixin.qq.com/wxa/sendemployeerelationmsg"; + } } diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/executor/ApacheApiSignaturePostRequestExecutor.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/executor/ApacheApiSignaturePostRequestExecutor.java index da9e1a5ad2..0a858256a8 100644 --- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/executor/ApacheApiSignaturePostRequestExecutor.java +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/executor/ApacheApiSignaturePostRequestExecutor.java @@ -5,25 +5,21 @@ import me.chanjar.weixin.common.error.WxErrorException; import me.chanjar.weixin.common.util.http.RequestHttp; import me.chanjar.weixin.common.util.http.apache.Utf8ResponseHandler; -import org.apache.http.Consts; import org.apache.http.Header; 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.HttpPost; +import org.apache.http.entity.ContentType; import org.apache.http.entity.StringEntity; import org.apache.http.impl.client.CloseableHttpClient; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.Map; -public class ApacheApiSignaturePostRequestExecutor - extends ApiSignaturePostRequestExecutor