diff --git a/README.md b/README.md
index f1cccac4b3..080b831d1e 100644
--- a/README.md
+++ b/README.md
@@ -64,7 +64,7 @@
### 重要信息
1. [`WxJava` 荣获 `GitCode` 2024年度十大开源社区奖项](https://mp.weixin.qq.com/s/wM_UlMsDm3IZ1CPPDvcvQw)。
2. 项目合作洽谈请联系微信`binary0000`(在微信里自行搜索并添加好友,请注明来意,如有关于SDK问题需讨论请参考下文入群讨论,不要加此微信)。
-3. **2024年12月30日 发布 [【4.7.0正式版】](https://mp.weixin.qq.com/s/_7k-XLYBqeJJhvHWCsdT0A)**!
+3. **2026年01月03日 发布 [【4.8.0正式版】](https://mp.weixin.qq.com/s/mJoFtGc25pXCn3uZRh6Q-w)**!
5. 贡献源码可以参考视频:[【贡献源码全过程(上集)】](https://mp.weixin.qq.com/s/3xUZSATWwHR_gZZm207h7Q)、[【贡献源码全过程(下集)】](https://mp.weixin.qq.com/s/nyzJwVVoYSJ4hSbwyvTx9A) ,友情提供:[程序员小山与Bug](https://space.bilibili.com/473631007)
6. 新手重要提示:本项目仅是一个SDK开发工具包,未提供Web实现,建议使用 `maven` 或 `gradle` 引用本项目即可使用本SDK提供的各种功能,详情可参考 **[【Demo项目】](demo.md)** 或本项目中的部分单元测试代码;
7. 微信开发新手请务必阅读【开发文档】([Gitee Wiki](https://gitee.com/binary/weixin-java-tools/wikis/Home) 或者 [Github Wiki](https://github.com/binarywang/WxJava/wiki))的常见问题部分,可以少走很多弯路,节省不少时间。
@@ -95,7 +95,7 @@
com.github.binarywang
(不同模块参考下文)
- 4.7.0
+ 4.8.0
```
@@ -114,6 +114,51 @@
- **微信开放平台**(`weixin-java-open`)主要用于第三方平台,代公众号或小程序进行开发和管理
+---------------------------------
+### HTTP 客户端支持
+
+本项目同时支持多种 HTTP 客户端实现,默认推荐使用 **Apache HttpClient 5.x**(最新稳定版本)。
+
+#### 支持的 HTTP 客户端类型
+
+| HTTP 客户端 | 说明 | 配置值 | 推荐程度 |
+|------------|------|--------|---------|
+| Apache HttpClient 5.x | Apache HttpComponents Client 5.x,最新版本 | `HttpComponents` | ⭐⭐⭐⭐⭐ 推荐 |
+| Apache HttpClient 4.x | Apache HttpClient 4.x,向后兼容 | `HttpClient` | ⭐⭐⭐⭐ 兼容 |
+| OkHttp | Square OkHttp 客户端 | `OkHttp` | ⭐⭐⭐ 可选 |
+| Jodd-http | Jodd 轻量级 HTTP 客户端 | `JoddHttp` | ⭐⭐ 可选 |
+
+#### 配置方式
+
+**Spring Boot 配置示例:**
+
+```properties
+# 使用 HttpClient 5.x(推荐,MP/MiniApp/CP/Channel/QiDian 模块默认)
+wx.mp.config-storage.http-client-type=HttpComponents
+
+# 使用 HttpClient 4.x(兼容模式)
+wx.mp.config-storage.http-client-type=HttpClient
+
+# 使用 OkHttp
+wx.mp.config-storage.http-client-type=OkHttp
+
+# 使用 Jodd-http
+wx.mp.config-storage.http-client-type=JoddHttp
+```
+
+**注意**:如果使用 Multi-Starter(如 `wx-java-mp-multi-spring-boot-starter`),枚举值需使用大写下划线格式:
+```properties
+# Multi-Starter 配置格式
+wx.mp.config-storage.http-client-type=HTTP_COMPONENTS # 注意使用大写下划线
+```
+
+**注意事项:**
+1. **MP、MiniApp、Channel、QiDian 模块**已完整支持 HttpClient 5.x,默认推荐使用
+2. **CP 模块**的支持情况取决于具体使用的 Starter 版本,请参考对应模块文档
+3. 如需使用 OkHttp 或 Jodd-http,需在项目中添加对应的依赖(scope为provided)
+4. HttpClient 4.x 和 HttpClient 5.x 可以共存,按需配置即可
+
+
---------------------------------
### 版本说明
diff --git a/docs/CP_MSG_AUDIT_SDK_SAFE_USAGE.md b/docs/CP_MSG_AUDIT_SDK_SAFE_USAGE.md
new file mode 100644
index 0000000000..b3a3ea1d33
--- /dev/null
+++ b/docs/CP_MSG_AUDIT_SDK_SAFE_USAGE.md
@@ -0,0 +1,295 @@
+# 企业微信会话存档SDK安全使用指南
+
+## 问题背景
+
+在使用企业微信会话存档功能时,部分开发者遇到了JVM崩溃的问题。典型错误信息如下:
+
+```
+SIGSEGV (0xb) at pc=0x00007fcd50460d93
+Problematic frame:
+C [libWeWorkFinanceSdk_Java.so+0x260d93] WeWorkFinanceSdk::TryRefresh(std::string const&, std::string const&, int)+0x23
+```
+
+## 问题原因
+
+旧版API设计存在以下问题:
+
+1. **SDK生命周期管理混乱**
+ - `getChatDatas()` 方法会返回SDK实例给调用方
+ - 开发者需要手动调用 `Finance.DestroySdk()` 来销毁SDK
+ - 但SDK在框架内部有7200秒的缓存机制
+
+2. **手动销毁导致缓存失效**
+ - 当开发者手动销毁SDK后,框架缓存中的SDK引用变为无效
+ - 后续调用(如 `getMediaFile()`)仍然使用已销毁的SDK
+ - 底层C++库访问无效指针,导致SIGSEGV错误
+
+3. **多线程并发问题**
+ - 在多线程环境下,一个线程销毁SDK后
+ - 其他线程仍在使用该SDK,导致崩溃
+
+## 解决方案
+
+从 **4.8.0** 版本开始,WxJava提供了新的安全API,完全由框架管理SDK生命周期。
+
+### 新API列表
+
+| 旧API(已废弃) | 新API(推荐使用) | 说明 |
+|----------------|------------------|------|
+| `getChatDatas()` | `getChatRecords()` | 拉取聊天记录,不暴露SDK |
+| `getDecryptData(sdk, ...)` | `getDecryptChatData(...)` | 解密聊天数据,无需传入SDK |
+| `getChatPlainText(sdk, ...)` | `getChatRecordPlainText(...)` | 获取明文数据,无需传入SDK |
+| `getMediaFile(sdk, ...)` | `downloadMediaFile(...)` | 下载媒体文件,无需传入SDK |
+
+### 使用示例
+
+#### 错误用法(旧API,已废弃)
+
+```java
+// ❌ 不推荐:容易导致JVM崩溃
+WxCpMsgAuditService msgAuditService = wxCpService.getMsgAuditService();
+
+// 拉取聊天记录
+WxCpChatDatas chatDatas = msgAuditService.getChatDatas(seq, 1000L, null, null, 1000L);
+
+for (WxCpChatDatas.WxCpChatData chatData : chatDatas.getChatData()) {
+ // 解密数据
+ WxCpChatModel model = msgAuditService.getDecryptData(chatDatas.getSdk(), chatData, 2);
+
+ // 下载媒体文件
+ if ("image".equals(model.getMsgType())) {
+ String sdkFileId = model.getImage().getSdkFileId();
+ msgAuditService.getMediaFile(chatDatas.getSdk(), sdkFileId, null, null, 1000L, targetPath);
+ }
+}
+
+// ❌ 危险操作:手动销毁SDK可能导致后续调用崩溃
+Finance.DestroySdk(chatDatas.getSdk());
+```
+
+#### 正确用法(新API,推荐)
+
+```java
+// ✅ 推荐:SDK生命周期由框架自动管理,安全可靠
+WxCpMsgAuditService msgAuditService = wxCpService.getMsgAuditService();
+
+// 拉取聊天记录(不返回SDK)
+List chatRecords =
+ msgAuditService.getChatRecords(seq, 1000L, null, null, 1000L);
+
+for (WxCpChatDatas.WxCpChatData chatData : chatRecords) {
+ // 解密数据(无需传入SDK)
+ WxCpChatModel model = msgAuditService.getDecryptChatData(chatData, 2);
+
+ // 下载媒体文件(无需传入SDK)
+ if ("image".equals(model.getMsgType())) {
+ String sdkFileId = model.getImage().getSdkFileId();
+ msgAuditService.downloadMediaFile(sdkFileId, null, null, 1000L, targetPath);
+ }
+}
+
+// ✅ 无需手动销毁SDK,框架会自动管理
+```
+
+### 完整示例:拉取并处理会话存档
+
+```java
+import me.chanjar.weixin.cp.api.WxCpService;
+import me.chanjar.weixin.cp.api.WxCpMsgAuditService;
+import me.chanjar.weixin.cp.bean.msgaudit.WxCpChatDatas;
+import me.chanjar.weixin.cp.bean.msgaudit.WxCpChatModel;
+import me.chanjar.weixin.cp.constant.WxCpConsts;
+
+import java.util.List;
+
+public class MsgAuditExample {
+
+ private final WxCpService wxCpService;
+
+ public void processMessages(long seq) throws Exception {
+ WxCpMsgAuditService msgAuditService = wxCpService.getMsgAuditService();
+
+ // 拉取聊天记录
+ List chatRecords =
+ msgAuditService.getChatRecords(seq, 1000L, null, null, 1000L);
+
+ for (WxCpChatDatas.WxCpChatData chatData : chatRecords) {
+ seq = chatData.getSeq();
+
+ // 获取明文数据
+ String plainText = msgAuditService.getChatRecordPlainText(chatData, 2);
+ WxCpChatModel model = WxCpChatModel.fromJson(plainText);
+
+ // 处理不同类型的消息
+ switch (model.getMsgType()) {
+ case WxCpConsts.MsgAuditMediaType.TEXT:
+ processTextMessage(model);
+ break;
+
+ case WxCpConsts.MsgAuditMediaType.IMAGE:
+ processImageMessage(model, msgAuditService);
+ break;
+
+ case WxCpConsts.MsgAuditMediaType.FILE:
+ processFileMessage(model, msgAuditService);
+ break;
+
+ default:
+ // 处理其他类型消息
+ break;
+ }
+ }
+ }
+
+ private void processTextMessage(WxCpChatModel model) {
+ String content = model.getText().getContent();
+ System.out.println("文本消息:" + content);
+ }
+
+ private void processImageMessage(WxCpChatModel model, WxCpMsgAuditService msgAuditService)
+ throws Exception {
+ String sdkFileId = model.getImage().getSdkFileId();
+ String md5Sum = model.getImage().getMd5Sum();
+ String targetPath = "/path/to/save/" + md5Sum + ".jpg";
+
+ // 下载图片(无需传入SDK)
+ msgAuditService.downloadMediaFile(sdkFileId, null, null, 1000L, targetPath);
+ System.out.println("图片已保存:" + targetPath);
+ }
+
+ private void processFileMessage(WxCpChatModel model, WxCpMsgAuditService msgAuditService)
+ throws Exception {
+ String sdkFileId = model.getFile().getSdkFileId();
+ String fileName = model.getFile().getFileName();
+ String targetPath = "/path/to/save/" + fileName;
+
+ // 下载文件(无需传入SDK)
+ msgAuditService.downloadMediaFile(sdkFileId, null, null, 1000L, targetPath);
+ System.out.println("文件已保存:" + targetPath);
+ }
+}
+```
+
+### 使用Lambda处理媒体文件流
+
+新API同样支持使用Lambda表达式处理媒体文件的数据流:
+
+```java
+msgAuditService.downloadMediaFile(sdkFileId, null, null, 1000L, data -> {
+ try {
+ // 处理每个数据分片(大文件会分片传输)
+ // 例如:上传到云存储、写入数据库等
+ uploadToCloud(data);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+});
+```
+
+## 技术实现原理
+
+### 引用计数机制
+
+新API在内部实现了SDK引用计数机制:
+
+1. **获取SDK时**:引用计数 +1
+2. **使用完成后**:引用计数 -1
+3. **计数归零时**:SDK被自动释放
+
+```java
+// 框架内部实现(简化版)
+public void downloadMediaFile(String sdkFileId, ...) {
+ long sdk = initSdk(); // 获取或初始化SDK
+ configStorage.incrementMsgAuditSdkRefCount(sdk); // 引用计数 +1
+
+ try {
+ // 执行实际操作
+ getMediaFile(sdk, sdkFileId, ...);
+ } finally {
+ // 确保引用计数一定会减少
+ configStorage.decrementMsgAuditSdkRefCount(sdk); // 引用计数 -1
+ }
+}
+```
+
+### SDK缓存机制
+
+SDK初始化后会缓存7200秒(企业微信官方文档规定),避免频繁初始化:
+
+- **首次调用**:初始化新的SDK
+- **7200秒内**:复用缓存的SDK
+- **超过7200秒**:重新初始化SDK
+
+新API的引用计数机制与缓存机制完美配合,确保SDK不会被提前销毁。
+
+## 迁移指南
+
+### 第一步:使用新API替换旧API
+
+查找代码中的旧API调用:
+
+```java
+// 查找模式
+getChatDatas(
+getDecryptData(.*sdk
+getChatPlainText(.*sdk
+getMediaFile(.*sdk
+Finance.DestroySdk(
+```
+
+替换为对应的新API(参考前面的对照表)。
+
+### 第二步:移除手动SDK管理代码
+
+删除所有 `Finance.DestroySdk()` 调用,SDK生命周期由框架自动管理。
+
+### 第三步:测试验证
+
+1. 在测试环境验证新API功能正常
+2. 观察日志,确认没有SDK相关的错误
+3. 进行压力测试,验证多线程环境下的稳定性
+
+## 常见问题
+
+### Q1: 旧代码会立即停止工作吗?
+
+**A:** 不会。旧API被标记为 `@Deprecated`,但仍然可用,只是不推荐继续使用。建议尽快迁移到新API以避免潜在问题。
+
+### Q2: 如何知道SDK是否被正确释放?
+
+**A:** 框架会自动管理SDK生命周期,开发者无需关心。如果需要调试,可以查看配置存储中的引用计数。
+
+### Q3: 多线程环境下新API安全吗?
+
+**A:** 是的。新API使用了引用计数机制,配合 `synchronized` 关键字,确保多线程环境下的安全性。
+
+### Q4: 性能会受影响吗?
+
+**A:** 不会。新API在实现上增加了引用计数的开销,但这是轻量级的操作(原子操作),对性能影响可以忽略不计。SDK缓存机制保持不变。
+
+### Q5: 可以同时使用新旧API吗?
+
+**A:** 技术上可以,但强烈不推荐。混用可能导致SDK生命周期管理混乱,建议统一使用新API。
+
+## 相关链接
+
+- [企业微信会话存档官方文档](https://developer.work.weixin.qq.com/document/path/91360)
+- [WxJava GitHub 仓库](https://github.com/binarywang/WxJava)
+- [问题反馈](https://github.com/binarywang/WxJava/issues)
+
+## 版本要求
+
+- **最低版本**: 4.8.0
+- **推荐版本**: 最新版本
+
+## 反馈与支持
+
+如果在使用过程中遇到问题,请:
+
+1. 查看本文档的常见问题部分
+2. 在 GitHub 上提交 Issue
+3. 加入微信群获取社区支持
+
+---
+
+**最后更新时间**: 2026年01月14日
diff --git a/docs/HTTPCLIENT_UPGRADE_GUIDE.md b/docs/HTTPCLIENT_UPGRADE_GUIDE.md
new file mode 100644
index 0000000000..5cabb10674
--- /dev/null
+++ b/docs/HTTPCLIENT_UPGRADE_GUIDE.md
@@ -0,0 +1,199 @@
+# HttpClient 升级指南
+
+## 概述
+
+从 WxJava 4.7.x 版本开始,项目开始支持并推荐使用 **Apache HttpClient 5.x**(HttpComponents Client 5),同时保持对 HttpClient 4.x 的向后兼容。
+
+## 为什么升级?
+
+1. **Apache HttpClient 5.x 是最新稳定版本**:提供更好的性能和更多的功能
+2. **HttpClient 4.x 已经进入维护模式**:不再积极开发新功能
+3. **更好的安全性**:HttpClient 5.x 包含最新的安全更新和改进
+4. **向前兼容**:为未来的开发做好准备
+
+## 支持的 HTTP 客户端
+
+| HTTP 客户端 | 版本 | 配置值 | 状态 | 说明 |
+|------------|------|--------|------|------|
+| Apache HttpClient 5.x | 5.5 | `HttpComponents` | ⭐ 推荐 | 最新稳定版本 |
+| Apache HttpClient 4.x | 4.5.13 | `HttpClient` | ✅ 支持 | 向后兼容 |
+| OkHttp | 4.12.0 | `OkHttp` | ✅ 支持 | 需自行添加依赖 |
+| Jodd-http | 6.3.0 | `JoddHttp` | ✅ 支持 | 需自行添加依赖 |
+
+## 模块支持情况
+
+| 模块 | HttpClient 5.x 支持 | 默认客户端 |
+|------|-------------------|-----------|
+| weixin-java-mp(公众号) | ✅ 是 | HttpComponents (5.x) |
+| weixin-java-cp(企业微信) | ⚠️ 视集成方式而定 | 参考对应 starter 配置 |
+| weixin-java-channel(视频号) | ✅ 是 | HttpComponents (5.x) |
+| weixin-java-qidian(企点) | ✅ 是 | HttpComponents (5.x) |
+| weixin-java-miniapp(小程序) | ✅ 是 | HttpComponents (5.x) |
+| weixin-java-pay(支付) | ✅ 是 | HttpComponents (5.x) |
+| weixin-java-open(开放平台) | ✅ 是 | HttpComponents (5.x) |
+
+**注意**:
+- **weixin-java-cp 模块**的支持情况取决于具体使用的 Starter 版本,请参考对应模块文档。
+
+## 对现有项目的影响
+
+### 对新项目
+- **无需任何修改**,直接使用最新版本即可
+- 支持 HttpClient 5.x 的模块会自动使用 HttpComponents (5.x)
+
+### 对现有项目
+- **向后兼容**:不需要修改任何代码
+- 如果希望继续使用 HttpClient 4.x,只需在配置中显式指定,pay 模块会自动包含 httpclient4 依赖(因为某些接口必须使用 httpclient4)
+ 其他模块(mp、miniapp、cp、open、channel、qidian)如果需要使用 httpclient4,必须显式在项目中添加 httpclient4 依赖
+
+## 迁移步骤
+
+### 1. 更新 WxJava 版本
+
+在 `pom.xml` 中更新版本:
+
+```xml
+
+ com.github.binarywang
+ weixin-java-mp
+ 最新版本
+
+```
+
+### 2. 检查配置(可选)
+
+#### Spring Boot 项目
+
+在 `application.properties` 或 `application.yml` 中:
+
+```properties
+# 使用 HttpClient 5.x(推荐,无需配置,已经是默认值)
+wx.mp.config-storage.http-client-type=HttpComponents
+
+# 或者继续使用 HttpClient 4.x
+wx.mp.config-storage.http-client-type=HttpClient
+```
+
+#### 纯 Java 项目
+
+```java
+// 使用 HttpClient 5.x(推荐)
+WxMpService wxMpService = new WxMpServiceHttpComponentsImpl();
+
+// 或者继续使用 HttpClient 4.x
+WxMpService wxMpService = new WxMpServiceHttpClientImpl();
+```
+
+### 3. 测试应用
+
+升级后,建议进行全面测试以确保一切正常工作。
+
+## 常见问题
+
+### Q: 升级后会不会破坏现有代码?
+A: 不会。项目保持完全向后兼容,HttpClient 4.x 的所有实现都保持不变。
+
+### Q: 我需要修改代码吗?
+A: 大多数情况下不需要。如果希望继续使用 HttpClient 4.x,只需在配置中指定 `http-client-type=HttpClient` ,并引入 HttpClient 4.x 依赖即可。
+
+### Q: 我可以在同一个项目中同时使用两个版本吗?
+A: 可以。不同的模块可以配置使用不同的 HTTP 客户端。例如,MP 模块使用 HttpClient 5.x,pay 模块部分接口仍使用 HttpClient 4.x,但也可以按需配置为 HttpClient 5.x。
+
+### Q: 如何排除不需要的依赖?
+A: 如果只想使用一个版本,可以在 `pom.xml` 中排除另一个:
+
+```xml
+
+ com.github.binarywang
+ weixin-java-mp
+ 最新版本
+
+
+
+ org.apache.httpcomponents
+ httpclient
+
+
+ org.apache.httpcomponents
+ httpmime
+
+
+
+```
+
+## 配置参考
+
+### Spring Boot 完整配置示例
+
+```properties
+# 公众号配置
+wx.mp.app-id=your_app_id
+wx.mp.secret=your_secret
+wx.mp.token=your_token
+wx.mp.aes-key=your_aes_key
+
+# HTTP 客户端配置
+wx.mp.config-storage.http-client-type=HttpComponents # HttpComponents, HttpClient, OkHttp, JoddHttp
+
+# HTTP 代理配置(可选)
+wx.mp.config-storage.http-proxy-host=proxy.example.com
+wx.mp.config-storage.http-proxy-port=8080
+wx.mp.config-storage.http-proxy-username=proxy_user
+wx.mp.config-storage.http-proxy-password=proxy_pass
+
+# 超时配置(可选)
+wx.mp.config-storage.connection-timeout=5000
+wx.mp.config-storage.so-timeout=5000
+wx.mp.config-storage.connection-request-timeout=5000
+```
+
+## 技术细节
+
+### HttpClient 4.x 与 5.x 的主要区别
+
+1. **包名变更**:
+ - HttpClient 4.x: `org.apache.http.*`
+ - HttpClient 5.x: `org.apache.hc.client5.*`, `org.apache.hc.core5.*`
+
+2. **API 改进**:
+ - HttpClient 5.x 提供更现代的 API 设计
+ - 更好的异步支持
+ - 改进的连接池管理
+
+3. **性能优化**:
+ - HttpClient 5.x 包含多项性能优化
+ - 更好的资源管理
+
+### 项目中的实现
+
+WxJava 项目通过策略模式支持多种 HTTP 客户端:
+
+```
+weixin-java-common/
+├── util/http/
+│ ├── apache/ # HttpClient 4.x 实现
+│ ├── hc/ # HttpClient 5.x (HttpComponents) 实现
+│ ├── okhttp/ # OkHttp 实现
+│ └── jodd/ # Jodd-http 实现
+```
+
+每个模块都有对应的 Service 实现类:
+- `*ServiceHttpClientImpl` - 使用 HttpClient 4.x
+- `*ServiceHttpComponentsImpl` - 使用 HttpClient 5.x
+- `*ServiceOkHttpImpl` - 使用 OkHttp
+- `*ServiceJoddHttpImpl` - 使用 Jodd-http
+
+## 反馈与支持
+
+如果在升级过程中遇到问题,请:
+
+1. 查看 [项目 Wiki](https://github.com/binarywang/WxJava/wiki)
+2. 在 [GitHub Issues](https://github.com/binarywang/WxJava/issues) 中搜索或提交问题
+3. 加入技术交流群(见 README.md)
+
+## 总结
+
+- ✅ **推荐使用 HttpClient 5.x**:性能更好,功能更强
+- ✅ **向后兼容**:可以继续使用 HttpClient 4.x
+- ✅ **灵活配置**:支持多种 HTTP 客户端,按需选择
+- ✅ **平滑迁移**:无需修改代码,仅需配置,若不使用 HttpClient 5.x ,引入其他依赖即可
diff --git a/pom.xml b/pom.xml
index 0a33fbdf16..d7d93322b5 100644
--- a/pom.xml
+++ b/pom.xml
@@ -136,6 +136,7 @@
UTF-8
4.5.13
+ 5.5.2
9.4.57.v20241219
@@ -157,13 +158,14 @@
4.12.0
provided
+
org.apache.httpcomponents.client5
httpclient5
- 5.5
- provided
+ ${httpclient5.version}
+
org.apache.httpcomponents
httpclient
@@ -255,8 +257,8 @@
org.mockito
- mockito-all
- 1.10.19
+ mockito-core
+ 5.14.2
test
diff --git a/solon-plugins/wx-java-channel-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/channel/properties/WxChannelMultiProperties.java b/solon-plugins/wx-java-channel-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/channel/properties/WxChannelMultiProperties.java
index 2e2da1add7..ca99e522b9 100644
--- a/solon-plugins/wx-java-channel-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/channel/properties/WxChannelMultiProperties.java
+++ b/solon-plugins/wx-java-channel-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/channel/properties/WxChannelMultiProperties.java
@@ -55,7 +55,7 @@ public static class ConfigStorage implements Serializable {
/**
* http客户端类型.
*/
- private HttpClientType httpClientType = HttpClientType.HTTP_CLIENT;
+ private HttpClientType httpClientType = HttpClientType.HTTP_COMPONENTS;
/**
* http代理主机.
diff --git a/solon-plugins/wx-java-channel-solon-plugin/src/main/java/com/binarywang/solon/wxjava/channel/enums/HttpClientType.java b/solon-plugins/wx-java-channel-solon-plugin/src/main/java/com/binarywang/solon/wxjava/channel/enums/HttpClientType.java
index 0c00dbcaa7..5614f63e86 100644
--- a/solon-plugins/wx-java-channel-solon-plugin/src/main/java/com/binarywang/solon/wxjava/channel/enums/HttpClientType.java
+++ b/solon-plugins/wx-java-channel-solon-plugin/src/main/java/com/binarywang/solon/wxjava/channel/enums/HttpClientType.java
@@ -7,7 +7,11 @@
*/
public enum HttpClientType {
/**
- * HttpClient
+ * HttpClient.
*/
- HttpClient
+ HttpClient,
+ /**
+ * HttpComponents.
+ */
+ HttpComponents,
}
diff --git a/solon-plugins/wx-java-channel-solon-plugin/src/main/java/com/binarywang/solon/wxjava/channel/properties/WxChannelProperties.java b/solon-plugins/wx-java-channel-solon-plugin/src/main/java/com/binarywang/solon/wxjava/channel/properties/WxChannelProperties.java
index 6562a02e9d..89b81b7d9f 100644
--- a/solon-plugins/wx-java-channel-solon-plugin/src/main/java/com/binarywang/solon/wxjava/channel/properties/WxChannelProperties.java
+++ b/solon-plugins/wx-java-channel-solon-plugin/src/main/java/com/binarywang/solon/wxjava/channel/properties/WxChannelProperties.java
@@ -73,7 +73,7 @@ public static class ConfigStorage {
/**
* http客户端类型
*/
- private HttpClientType httpClientType = HttpClientType.HttpClient;
+ private HttpClientType httpClientType = HttpClientType.HttpComponents;
/**
* http代理主机
diff --git a/solon-plugins/wx-java-cp-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/cp_multi/properties/WxCpMultiProperties.java b/solon-plugins/wx-java-cp-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/cp_multi/properties/WxCpMultiProperties.java
index 821f885f98..2d4bffae66 100644
--- a/solon-plugins/wx-java-cp-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/cp_multi/properties/WxCpMultiProperties.java
+++ b/solon-plugins/wx-java-cp-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/cp_multi/properties/WxCpMultiProperties.java
@@ -52,7 +52,7 @@ public static class ConfigStorage implements Serializable {
/**
* http客户端类型.
*/
- private HttpClientType httpClientType = HttpClientType.HTTP_CLIENT;
+ private HttpClientType httpClientType = HttpClientType.HTTP_COMPONENTS;
/**
* http代理主机
diff --git a/solon-plugins/wx-java-miniapp-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/miniapp/configuration/services/AbstractWxMaConfiguration.java b/solon-plugins/wx-java-miniapp-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/miniapp/configuration/services/AbstractWxMaConfiguration.java
index fd94200e58..8ad85c96b8 100644
--- a/solon-plugins/wx-java-miniapp-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/miniapp/configuration/services/AbstractWxMaConfiguration.java
+++ b/solon-plugins/wx-java-miniapp-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/miniapp/configuration/services/AbstractWxMaConfiguration.java
@@ -2,6 +2,7 @@
import cn.binarywang.wx.miniapp.api.WxMaService;
import cn.binarywang.wx.miniapp.api.impl.WxMaServiceHttpClientImpl;
+import cn.binarywang.wx.miniapp.api.impl.WxMaServiceHttpComponentsImpl;
import cn.binarywang.wx.miniapp.api.impl.WxMaServiceImpl;
import cn.binarywang.wx.miniapp.api.impl.WxMaServiceJoddHttpImpl;
import cn.binarywang.wx.miniapp.api.impl.WxMaServiceOkHttpImpl;
@@ -89,6 +90,9 @@ public WxMaService wxMaService(WxMaConfig wxMaConfig, WxMaMultiProperties wxMaMu
case HTTP_CLIENT:
wxMaService = new WxMaServiceHttpClientImpl();
break;
+ case HTTP_COMPONENTS:
+ wxMaService = new WxMaServiceHttpComponentsImpl();
+ break;
default:
wxMaService = new WxMaServiceImpl();
break;
diff --git a/solon-plugins/wx-java-miniapp-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/miniapp/properties/WxMaMultiProperties.java b/solon-plugins/wx-java-miniapp-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/miniapp/properties/WxMaMultiProperties.java
index 87fcd42f03..f99d6280ec 100644
--- a/solon-plugins/wx-java-miniapp-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/miniapp/properties/WxMaMultiProperties.java
+++ b/solon-plugins/wx-java-miniapp-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/miniapp/properties/WxMaMultiProperties.java
@@ -77,7 +77,7 @@ public static class ConfigStorage implements Serializable {
/**
* http客户端类型.
*/
- private HttpClientType httpClientType = HttpClientType.HTTP_CLIENT;
+ private HttpClientType httpClientType = HttpClientType.HTTP_COMPONENTS;
/**
* http代理主机.
@@ -149,6 +149,10 @@ public enum HttpClientType {
/**
* JoddHttp
*/
- JODD_HTTP
+ JODD_HTTP,
+ /**
+ * HttpComponents
+ */
+ HTTP_COMPONENTS
}
}
diff --git a/solon-plugins/wx-java-miniapp-solon-plugin/src/main/java/com/binarywang/solon/wxjava/miniapp/config/WxMaServiceAutoConfiguration.java b/solon-plugins/wx-java-miniapp-solon-plugin/src/main/java/com/binarywang/solon/wxjava/miniapp/config/WxMaServiceAutoConfiguration.java
index 5463ec08e9..78f95380b2 100644
--- a/solon-plugins/wx-java-miniapp-solon-plugin/src/main/java/com/binarywang/solon/wxjava/miniapp/config/WxMaServiceAutoConfiguration.java
+++ b/solon-plugins/wx-java-miniapp-solon-plugin/src/main/java/com/binarywang/solon/wxjava/miniapp/config/WxMaServiceAutoConfiguration.java
@@ -2,6 +2,7 @@
import cn.binarywang.wx.miniapp.api.WxMaService;
import cn.binarywang.wx.miniapp.api.impl.WxMaServiceHttpClientImpl;
+import cn.binarywang.wx.miniapp.api.impl.WxMaServiceHttpComponentsImpl;
import cn.binarywang.wx.miniapp.api.impl.WxMaServiceImpl;
import cn.binarywang.wx.miniapp.api.impl.WxMaServiceJoddHttpImpl;
import cn.binarywang.wx.miniapp.api.impl.WxMaServiceOkHttpImpl;
@@ -44,6 +45,9 @@ public WxMaService wxMaService(WxMaConfig wxMaConfig) {
case HttpClient:
wxMaService = new WxMaServiceHttpClientImpl();
break;
+ case HttpComponents:
+ wxMaService = new WxMaServiceHttpComponentsImpl();
+ break;
default:
wxMaService = new WxMaServiceImpl();
break;
diff --git a/solon-plugins/wx-java-miniapp-solon-plugin/src/main/java/com/binarywang/solon/wxjava/miniapp/enums/HttpClientType.java b/solon-plugins/wx-java-miniapp-solon-plugin/src/main/java/com/binarywang/solon/wxjava/miniapp/enums/HttpClientType.java
index a4475a02c7..d116a30cf6 100644
--- a/solon-plugins/wx-java-miniapp-solon-plugin/src/main/java/com/binarywang/solon/wxjava/miniapp/enums/HttpClientType.java
+++ b/solon-plugins/wx-java-miniapp-solon-plugin/src/main/java/com/binarywang/solon/wxjava/miniapp/enums/HttpClientType.java
@@ -19,4 +19,8 @@ public enum HttpClientType {
* JoddHttp.
*/
JoddHttp,
+ /**
+ * HttpComponents.
+ */
+ HttpComponents,
}
diff --git a/solon-plugins/wx-java-miniapp-solon-plugin/src/main/java/com/binarywang/solon/wxjava/miniapp/properties/WxMaProperties.java b/solon-plugins/wx-java-miniapp-solon-plugin/src/main/java/com/binarywang/solon/wxjava/miniapp/properties/WxMaProperties.java
index 1c3e495f4e..4493b6aec5 100644
--- a/solon-plugins/wx-java-miniapp-solon-plugin/src/main/java/com/binarywang/solon/wxjava/miniapp/properties/WxMaProperties.java
+++ b/solon-plugins/wx-java-miniapp-solon-plugin/src/main/java/com/binarywang/solon/wxjava/miniapp/properties/WxMaProperties.java
@@ -76,7 +76,7 @@ public static class ConfigStorage {
/**
* http客户端类型.
*/
- private HttpClientType httpClientType = HttpClientType.HttpClient;
+ private HttpClientType httpClientType = HttpClientType.HttpComponents;
/**
* http代理主机.
diff --git a/solon-plugins/wx-java-mp-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/mp_multi/configuration/services/AbstractWxMpConfiguration.java b/solon-plugins/wx-java-mp-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/mp_multi/configuration/services/AbstractWxMpConfiguration.java
index d534b98746..a51c6eaaea 100644
--- a/solon-plugins/wx-java-mp-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/mp_multi/configuration/services/AbstractWxMpConfiguration.java
+++ b/solon-plugins/wx-java-mp-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/mp_multi/configuration/services/AbstractWxMpConfiguration.java
@@ -8,6 +8,7 @@
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.mp.api.WxMpService;
import me.chanjar.weixin.mp.api.impl.WxMpServiceHttpClientImpl;
+import me.chanjar.weixin.mp.api.impl.WxMpServiceHttpComponentsImpl;
import me.chanjar.weixin.mp.api.impl.WxMpServiceImpl;
import me.chanjar.weixin.mp.api.impl.WxMpServiceJoddHttpImpl;
import me.chanjar.weixin.mp.api.impl.WxMpServiceOkHttpImpl;
@@ -91,6 +92,9 @@ public WxMpService wxMpService(WxMpConfigStorage configStorage, WxMpMultiPropert
case HTTP_CLIENT:
wxMpService = new WxMpServiceHttpClientImpl();
break;
+ case HTTP_COMPONENTS:
+ wxMpService = new WxMpServiceHttpComponentsImpl();
+ break;
default:
wxMpService = new WxMpServiceImpl();
break;
diff --git a/solon-plugins/wx-java-mp-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/mp_multi/properties/WxMpMultiProperties.java b/solon-plugins/wx-java-mp-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/mp_multi/properties/WxMpMultiProperties.java
index 1929e92607..3d47f71381 100644
--- a/solon-plugins/wx-java-mp-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/mp_multi/properties/WxMpMultiProperties.java
+++ b/solon-plugins/wx-java-mp-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/mp_multi/properties/WxMpMultiProperties.java
@@ -77,7 +77,7 @@ public static class ConfigStorage implements Serializable {
/**
* http客户端类型.
*/
- private HttpClientType httpClientType = HttpClientType.HTTP_CLIENT;
+ private HttpClientType httpClientType = HttpClientType.HTTP_COMPONENTS;
/**
* http代理主机.
@@ -149,6 +149,10 @@ public enum HttpClientType {
/**
* JoddHttp
*/
- JODD_HTTP
+ JODD_HTTP,
+ /**
+ * HttpComponents
+ */
+ HTTP_COMPONENTS
}
}
diff --git a/solon-plugins/wx-java-mp-solon-plugin/pom.xml b/solon-plugins/wx-java-mp-solon-plugin/pom.xml
index d2507cc0db..d72a5f7fc4 100644
--- a/solon-plugins/wx-java-mp-solon-plugin/pom.xml
+++ b/solon-plugins/wx-java-mp-solon-plugin/pom.xml
@@ -22,7 +22,12 @@
redis.clients
jedis
- compile
+ provided
+
+
+ org.redisson
+ redisson
+ provided
org.jodd
diff --git a/solon-plugins/wx-java-mp-solon-plugin/src/main/java/com/binarywang/solon/wxjava/mp/config/WxMpServiceAutoConfiguration.java b/solon-plugins/wx-java-mp-solon-plugin/src/main/java/com/binarywang/solon/wxjava/mp/config/WxMpServiceAutoConfiguration.java
index 3e7a598494..334ccf7abe 100644
--- a/solon-plugins/wx-java-mp-solon-plugin/src/main/java/com/binarywang/solon/wxjava/mp/config/WxMpServiceAutoConfiguration.java
+++ b/solon-plugins/wx-java-mp-solon-plugin/src/main/java/com/binarywang/solon/wxjava/mp/config/WxMpServiceAutoConfiguration.java
@@ -4,6 +4,7 @@
import com.binarywang.solon.wxjava.mp.properties.WxMpProperties;
import me.chanjar.weixin.mp.api.WxMpService;
import me.chanjar.weixin.mp.api.impl.WxMpServiceHttpClientImpl;
+import me.chanjar.weixin.mp.api.impl.WxMpServiceHttpComponentsImpl;
import me.chanjar.weixin.mp.api.impl.WxMpServiceImpl;
import me.chanjar.weixin.mp.api.impl.WxMpServiceJoddHttpImpl;
import me.chanjar.weixin.mp.api.impl.WxMpServiceOkHttpImpl;
@@ -35,6 +36,9 @@ public WxMpService wxMpService(WxMpConfigStorage configStorage, WxMpProperties w
case HttpClient:
wxMpService = newWxMpServiceHttpClientImpl();
break;
+ case HttpComponents:
+ wxMpService = newWxMpServiceHttpComponentsImpl();
+ break;
default:
wxMpService = newWxMpServiceImpl();
break;
@@ -60,4 +64,8 @@ private WxMpService newWxMpServiceJoddHttpImpl() {
return new WxMpServiceJoddHttpImpl();
}
+ private WxMpService newWxMpServiceHttpComponentsImpl() {
+ return new WxMpServiceHttpComponentsImpl();
+ }
+
}
diff --git a/solon-plugins/wx-java-mp-solon-plugin/src/main/java/com/binarywang/solon/wxjava/mp/config/WxMpStorageAutoConfiguration.java b/solon-plugins/wx-java-mp-solon-plugin/src/main/java/com/binarywang/solon/wxjava/mp/config/WxMpStorageAutoConfiguration.java
deleted file mode 100644
index ac995dd1ec..0000000000
--- a/solon-plugins/wx-java-mp-solon-plugin/src/main/java/com/binarywang/solon/wxjava/mp/config/WxMpStorageAutoConfiguration.java
+++ /dev/null
@@ -1,128 +0,0 @@
-package com.binarywang.solon.wxjava.mp.config;
-
-import com.binarywang.solon.wxjava.mp.enums.StorageType;
-import com.binarywang.solon.wxjava.mp.properties.RedisProperties;
-import com.binarywang.solon.wxjava.mp.properties.WxMpProperties;
-import com.google.common.collect.Sets;
-import lombok.RequiredArgsConstructor;
-import lombok.extern.slf4j.Slf4j;
-import me.chanjar.weixin.common.redis.JedisWxRedisOps;
-import me.chanjar.weixin.common.redis.WxRedisOps;
-import me.chanjar.weixin.mp.config.WxMpConfigStorage;
-import me.chanjar.weixin.mp.config.WxMpHostConfig;
-import me.chanjar.weixin.mp.config.impl.WxMpDefaultConfigImpl;
-import me.chanjar.weixin.mp.config.impl.WxMpRedisConfigImpl;
-import org.apache.commons.lang3.StringUtils;
-import org.noear.solon.annotation.Bean;
-import org.noear.solon.annotation.Condition;
-import org.noear.solon.annotation.Configuration;
-import org.noear.solon.core.AppContext;
-import redis.clients.jedis.Jedis;
-import redis.clients.jedis.JedisPool;
-import redis.clients.jedis.JedisPoolConfig;
-import redis.clients.jedis.JedisSentinelPool;
-import redis.clients.jedis.util.Pool;
-
-import java.util.Set;
-
-/**
- * 微信公众号存储策略自动配置.
- *
- * @author Luo
- */
-@Slf4j
-@Configuration
-@RequiredArgsConstructor
-public class WxMpStorageAutoConfiguration {
- private final AppContext applicationContext;
-
- private final WxMpProperties wxMpProperties;
-
- @Bean
- @Condition(onMissingBean=WxMpConfigStorage.class)
- public WxMpConfigStorage wxMpConfigStorage() {
- StorageType type = wxMpProperties.getConfigStorage().getType();
- WxMpConfigStorage config;
- switch (type) {
- case Jedis:
- config = jedisConfigStorage();
- break;
- default:
- config = defaultConfigStorage();
- break;
- }
- // wx host config
- if (null != wxMpProperties.getHosts() && StringUtils.isNotEmpty(wxMpProperties.getHosts().getApiHost())) {
- WxMpHostConfig hostConfig = new WxMpHostConfig();
- hostConfig.setApiHost(wxMpProperties.getHosts().getApiHost());
- hostConfig.setMpHost(wxMpProperties.getHosts().getMpHost());
- hostConfig.setOpenHost(wxMpProperties.getHosts().getOpenHost());
- config.setHostConfig(hostConfig);
- }
- return config;
- }
-
- private WxMpConfigStorage defaultConfigStorage() {
- WxMpDefaultConfigImpl config = new WxMpDefaultConfigImpl();
- setWxMpInfo(config);
- return config;
- }
-
- private WxMpConfigStorage jedisConfigStorage() {
- Pool jedisPool;
- if (wxMpProperties.getConfigStorage() != null && wxMpProperties.getConfigStorage().getRedis() != null
- && StringUtils.isNotEmpty(wxMpProperties.getConfigStorage().getRedis().getHost())) {
- jedisPool = getJedisPool();
- } else {
- jedisPool = applicationContext.getBean(JedisPool.class);
- }
- WxRedisOps redisOps = new JedisWxRedisOps(jedisPool);
- WxMpRedisConfigImpl wxMpRedisConfig = new WxMpRedisConfigImpl(redisOps,
- wxMpProperties.getConfigStorage().getKeyPrefix());
- setWxMpInfo(wxMpRedisConfig);
- return wxMpRedisConfig;
- }
-
- private void setWxMpInfo(WxMpDefaultConfigImpl config) {
- WxMpProperties properties = wxMpProperties;
- WxMpProperties.ConfigStorage configStorageProperties = properties.getConfigStorage();
- config.setAppId(properties.getAppId());
- config.setSecret(properties.getSecret());
- config.setToken(properties.getToken());
- config.setAesKey(properties.getAesKey());
- config.setUseStableAccessToken(wxMpProperties.isUseStableAccessToken());
- config.setHttpProxyHost(configStorageProperties.getHttpProxyHost());
- config.setHttpProxyUsername(configStorageProperties.getHttpProxyUsername());
- config.setHttpProxyPassword(configStorageProperties.getHttpProxyPassword());
- if (configStorageProperties.getHttpProxyPort() != null) {
- config.setHttpProxyPort(configStorageProperties.getHttpProxyPort());
- }
- }
-
- private Pool getJedisPool() {
- RedisProperties redis = wxMpProperties.getConfigStorage().getRedis();
-
- JedisPoolConfig config = new JedisPoolConfig();
- if (redis.getMaxActive() != null) {
- config.setMaxTotal(redis.getMaxActive());
- }
- if (redis.getMaxIdle() != null) {
- config.setMaxIdle(redis.getMaxIdle());
- }
- if (redis.getMaxWaitMillis() != null) {
- config.setMaxWaitMillis(redis.getMaxWaitMillis());
- }
- if (redis.getMinIdle() != null) {
- config.setMinIdle(redis.getMinIdle());
- }
- config.setTestOnBorrow(true);
- config.setTestWhileIdle(true);
- if (StringUtils.isNotEmpty(redis.getSentinelIps())) {
- Set sentinels = Sets.newHashSet(redis.getSentinelIps().split(","));
- return new JedisSentinelPool(redis.getSentinelName(), sentinels);
- }
-
- return new JedisPool(config, redis.getHost(), redis.getPort(), redis.getTimeout(), redis.getPassword(),
- redis.getDatabase());
- }
-}
diff --git a/solon-plugins/wx-java-mp-solon-plugin/src/main/java/com/binarywang/solon/wxjava/mp/config/storage/AbstractWxMpConfigStorageConfiguration.java b/solon-plugins/wx-java-mp-solon-plugin/src/main/java/com/binarywang/solon/wxjava/mp/config/storage/AbstractWxMpConfigStorageConfiguration.java
new file mode 100644
index 0000000000..663bb13340
--- /dev/null
+++ b/solon-plugins/wx-java-mp-solon-plugin/src/main/java/com/binarywang/solon/wxjava/mp/config/storage/AbstractWxMpConfigStorageConfiguration.java
@@ -0,0 +1,27 @@
+package com.binarywang.solon.wxjava.mp.config.storage;
+
+import com.binarywang.solon.wxjava.mp.properties.WxMpProperties;
+import me.chanjar.weixin.mp.config.impl.WxMpDefaultConfigImpl;
+
+/**
+ * @author zhangyl
+ */
+public abstract class AbstractWxMpConfigStorageConfiguration {
+
+ protected WxMpDefaultConfigImpl config(WxMpDefaultConfigImpl config, WxMpProperties properties) {
+ config.setAppId(properties.getAppId());
+ config.setSecret(properties.getSecret());
+ config.setToken(properties.getToken());
+ config.setAesKey(properties.getAesKey());
+ config.setUseStableAccessToken(properties.isUseStableAccessToken());
+
+ WxMpProperties.ConfigStorage configStorageProperties = properties.getConfigStorage();
+ config.setHttpProxyHost(configStorageProperties.getHttpProxyHost());
+ config.setHttpProxyUsername(configStorageProperties.getHttpProxyUsername());
+ config.setHttpProxyPassword(configStorageProperties.getHttpProxyPassword());
+ if (configStorageProperties.getHttpProxyPort() != null) {
+ config.setHttpProxyPort(configStorageProperties.getHttpProxyPort());
+ }
+ return config;
+ }
+}
diff --git a/solon-plugins/wx-java-mp-solon-plugin/src/main/java/com/binarywang/solon/wxjava/mp/config/storage/WxMpInJedisConfigStorageConfiguration.java b/solon-plugins/wx-java-mp-solon-plugin/src/main/java/com/binarywang/solon/wxjava/mp/config/storage/WxMpInJedisConfigStorageConfiguration.java
new file mode 100644
index 0000000000..a949ccfaca
--- /dev/null
+++ b/solon-plugins/wx-java-mp-solon-plugin/src/main/java/com/binarywang/solon/wxjava/mp/config/storage/WxMpInJedisConfigStorageConfiguration.java
@@ -0,0 +1,76 @@
+package com.binarywang.solon.wxjava.mp.config.storage;
+
+import com.binarywang.solon.wxjava.mp.properties.RedisProperties;
+import com.binarywang.solon.wxjava.mp.properties.WxMpProperties;
+import lombok.RequiredArgsConstructor;
+import me.chanjar.weixin.common.redis.JedisWxRedisOps;
+import me.chanjar.weixin.common.redis.WxRedisOps;
+import me.chanjar.weixin.mp.config.WxMpConfigStorage;
+import me.chanjar.weixin.mp.config.impl.WxMpRedisConfigImpl;
+import org.apache.commons.lang3.StringUtils;
+import org.noear.solon.annotation.Bean;
+import org.noear.solon.annotation.Condition;
+import org.noear.solon.annotation.Configuration;
+import org.noear.solon.core.AppContext;
+import redis.clients.jedis.Jedis;
+import redis.clients.jedis.JedisPool;
+import redis.clients.jedis.JedisPoolConfig;
+
+/**
+ * @author zhangyl
+ */
+@Configuration
+@Condition(
+ onProperty = "${" + WxMpProperties.PREFIX + ".config-storage.type} = jedis",
+ onClass = Jedis.class
+)
+@RequiredArgsConstructor
+public class WxMpInJedisConfigStorageConfiguration extends AbstractWxMpConfigStorageConfiguration {
+ private final WxMpProperties properties;
+ private final AppContext applicationContext;
+
+ @Bean
+ @Condition(onMissingBean = WxMpConfigStorage.class)
+ public WxMpConfigStorage wxMpConfigStorage() {
+ WxMpRedisConfigImpl config = getWxMpRedisConfigImpl();
+ return this.config(config, properties);
+ }
+
+ private WxMpRedisConfigImpl getWxMpRedisConfigImpl() {
+ RedisProperties redisProperties = properties.getConfigStorage().getRedis();
+ JedisPool jedisPool;
+ if (redisProperties != null && StringUtils.isNotEmpty(redisProperties.getHost())) {
+ jedisPool = applicationContext.getBean("wxMpJedisPool");
+ } else {
+ jedisPool = applicationContext.getBean(JedisPool.class);
+ }
+ WxRedisOps redisOps = new JedisWxRedisOps(jedisPool);
+ return new WxMpRedisConfigImpl(redisOps, properties.getConfigStorage().getKeyPrefix());
+ }
+
+ @Bean
+ @Condition(onProperty = "${" + WxMpProperties.PREFIX + ".config-storage.redis.host}")
+ public JedisPool wxMpJedisPool() {
+ WxMpProperties.ConfigStorage storage = properties.getConfigStorage();
+ RedisProperties redis = storage.getRedis();
+
+ JedisPoolConfig config = new JedisPoolConfig();
+ if (redis.getMaxActive() != null) {
+ config.setMaxTotal(redis.getMaxActive());
+ }
+ if (redis.getMaxIdle() != null) {
+ config.setMaxIdle(redis.getMaxIdle());
+ }
+ if (redis.getMaxWaitMillis() != null) {
+ config.setMaxWaitMillis(redis.getMaxWaitMillis());
+ }
+ if (redis.getMinIdle() != null) {
+ config.setMinIdle(redis.getMinIdle());
+ }
+ config.setTestOnBorrow(true);
+ config.setTestWhileIdle(true);
+
+ return new JedisPool(config, redis.getHost(), redis.getPort(), redis.getTimeout(), redis.getPassword(),
+ redis.getDatabase());
+ }
+}
diff --git a/solon-plugins/wx-java-mp-solon-plugin/src/main/java/com/binarywang/solon/wxjava/mp/config/storage/WxMpInMemoryConfigStorageConfiguration.java b/solon-plugins/wx-java-mp-solon-plugin/src/main/java/com/binarywang/solon/wxjava/mp/config/storage/WxMpInMemoryConfigStorageConfiguration.java
new file mode 100644
index 0000000000..88994fcf42
--- /dev/null
+++ b/solon-plugins/wx-java-mp-solon-plugin/src/main/java/com/binarywang/solon/wxjava/mp/config/storage/WxMpInMemoryConfigStorageConfiguration.java
@@ -0,0 +1,29 @@
+package com.binarywang.solon.wxjava.mp.config.storage;
+
+import com.binarywang.solon.wxjava.mp.properties.WxMpProperties;
+import lombok.RequiredArgsConstructor;
+import me.chanjar.weixin.mp.config.WxMpConfigStorage;
+import me.chanjar.weixin.mp.config.impl.WxMpDefaultConfigImpl;
+import org.noear.solon.annotation.Bean;
+import org.noear.solon.annotation.Condition;
+import org.noear.solon.annotation.Configuration;
+
+/**
+ * @author zhangyl
+ */
+@Configuration
+@Condition(
+ onProperty = "${" + WxMpProperties.PREFIX + ".config-storage.type:memory} = memory"
+)
+@RequiredArgsConstructor
+public class WxMpInMemoryConfigStorageConfiguration extends AbstractWxMpConfigStorageConfiguration {
+ private final WxMpProperties properties;
+
+ @Bean
+ @Condition(onMissingBean = WxMpConfigStorage.class)
+ public WxMpConfigStorage wxMpConfigStorage() {
+ WxMpDefaultConfigImpl config = new WxMpDefaultConfigImpl();
+ config(config, properties);
+ return config;
+ }
+}
diff --git a/solon-plugins/wx-java-mp-solon-plugin/src/main/java/com/binarywang/solon/wxjava/mp/config/storage/WxMpInRedissonConfigStorageConfiguration.java b/solon-plugins/wx-java-mp-solon-plugin/src/main/java/com/binarywang/solon/wxjava/mp/config/storage/WxMpInRedissonConfigStorageConfiguration.java
new file mode 100644
index 0000000000..c1f5ebf0f3
--- /dev/null
+++ b/solon-plugins/wx-java-mp-solon-plugin/src/main/java/com/binarywang/solon/wxjava/mp/config/storage/WxMpInRedissonConfigStorageConfiguration.java
@@ -0,0 +1,65 @@
+package com.binarywang.solon.wxjava.mp.config.storage;
+
+import com.binarywang.solon.wxjava.mp.properties.RedisProperties;
+import com.binarywang.solon.wxjava.mp.properties.WxMpProperties;
+import lombok.RequiredArgsConstructor;
+import me.chanjar.weixin.mp.config.WxMpConfigStorage;
+import me.chanjar.weixin.mp.config.impl.WxMpRedissonConfigImpl;
+import org.apache.commons.lang3.StringUtils;
+import org.noear.solon.annotation.Bean;
+import org.noear.solon.annotation.Condition;
+import org.noear.solon.annotation.Configuration;
+import org.noear.solon.core.AppContext;
+import org.redisson.Redisson;
+import org.redisson.api.RedissonClient;
+import org.redisson.config.Config;
+import org.redisson.config.TransportMode;
+
+/**
+ * @author zhangyl
+ */
+@Configuration
+@Condition(
+ onProperty = "${" + WxMpProperties.PREFIX + ".config-storage.type} = redisson",
+ onClass = Redisson.class
+)
+@RequiredArgsConstructor
+public class WxMpInRedissonConfigStorageConfiguration extends AbstractWxMpConfigStorageConfiguration {
+ private final WxMpProperties properties;
+ private final AppContext applicationContext;
+
+ @Bean
+ @Condition(onMissingBean = WxMpConfigStorage.class)
+ public WxMpConfigStorage wxMaConfig() {
+ WxMpRedissonConfigImpl config = getWxMpInRedissonConfigStorage();
+ return this.config(config, properties);
+ }
+
+ private WxMpRedissonConfigImpl getWxMpInRedissonConfigStorage() {
+ RedisProperties redisProperties = properties.getConfigStorage().getRedis();
+ RedissonClient redissonClient;
+ if (redisProperties != null && StringUtils.isNotEmpty(redisProperties.getHost())) {
+ redissonClient = applicationContext.getBean("wxMpRedissonClient");
+ } else {
+ redissonClient = applicationContext.getBean(RedissonClient.class);
+ }
+ return new WxMpRedissonConfigImpl(redissonClient, properties.getConfigStorage().getKeyPrefix());
+ }
+
+ @Bean
+ @Condition(onProperty = "${" + WxMpProperties.PREFIX + ".config-storage.redis.host}")
+ public RedissonClient wxMpRedissonClient() {
+ WxMpProperties.ConfigStorage storage = properties.getConfigStorage();
+ RedisProperties redis = storage.getRedis();
+
+ Config config = new Config();
+ config.useSingleServer()
+ .setAddress("redis://" + redis.getHost() + ":" + redis.getPort())
+ .setDatabase(redis.getDatabase());
+ if (StringUtils.isNotBlank(redis.getPassword())) {
+ config.useSingleServer().setPassword(redis.getPassword());
+ }
+ config.setTransportMode(TransportMode.NIO);
+ return Redisson.create(config);
+ }
+}
diff --git a/solon-plugins/wx-java-mp-solon-plugin/src/main/java/com/binarywang/solon/wxjava/mp/enums/HttpClientType.java b/solon-plugins/wx-java-mp-solon-plugin/src/main/java/com/binarywang/solon/wxjava/mp/enums/HttpClientType.java
index 9b1a8ccbf4..2858d77e43 100644
--- a/solon-plugins/wx-java-mp-solon-plugin/src/main/java/com/binarywang/solon/wxjava/mp/enums/HttpClientType.java
+++ b/solon-plugins/wx-java-mp-solon-plugin/src/main/java/com/binarywang/solon/wxjava/mp/enums/HttpClientType.java
@@ -19,4 +19,8 @@ public enum HttpClientType {
* JoddHttp.
*/
JoddHttp,
+ /**
+ * HttpComponents.
+ */
+ HttpComponents,
}
diff --git a/solon-plugins/wx-java-mp-solon-plugin/src/main/java/com/binarywang/solon/wxjava/mp/integration/WxMpPluginImpl.java b/solon-plugins/wx-java-mp-solon-plugin/src/main/java/com/binarywang/solon/wxjava/mp/integration/WxMpPluginImpl.java
index 3368d34269..285d871f25 100644
--- a/solon-plugins/wx-java-mp-solon-plugin/src/main/java/com/binarywang/solon/wxjava/mp/integration/WxMpPluginImpl.java
+++ b/solon-plugins/wx-java-mp-solon-plugin/src/main/java/com/binarywang/solon/wxjava/mp/integration/WxMpPluginImpl.java
@@ -1,10 +1,13 @@
package com.binarywang.solon.wxjava.mp.integration;
import com.binarywang.solon.wxjava.mp.config.WxMpServiceAutoConfiguration;
-import com.binarywang.solon.wxjava.mp.config.WxMpStorageAutoConfiguration;
+import com.binarywang.solon.wxjava.mp.config.storage.WxMpInJedisConfigStorageConfiguration;
+import com.binarywang.solon.wxjava.mp.config.storage.WxMpInMemoryConfigStorageConfiguration;
+import com.binarywang.solon.wxjava.mp.config.storage.WxMpInRedissonConfigStorageConfiguration;
import com.binarywang.solon.wxjava.mp.properties.WxMpProperties;
import org.noear.solon.core.AppContext;
import org.noear.solon.core.Plugin;
+import org.noear.solon.core.util.ClassUtil;
/**
* @author noear 2024年9月2日 created
@@ -13,8 +16,14 @@ public class WxMpPluginImpl implements Plugin {
@Override
public void start(AppContext context) throws Throwable {
context.beanMake(WxMpProperties.class);
-
- context.beanMake(WxMpStorageAutoConfiguration.class);
context.beanMake(WxMpServiceAutoConfiguration.class);
+
+ context.beanMake(WxMpInMemoryConfigStorageConfiguration.class);
+ if (ClassUtil.loadClass("redis.clients.jedis.Jedis") != null) {
+ context.beanMake(WxMpInJedisConfigStorageConfiguration.class);
+ }
+ if (ClassUtil.loadClass("org.redisson.api.RedissonClient") != null) {
+ context.beanMake(WxMpInRedissonConfigStorageConfiguration.class);
+ }
}
}
diff --git a/solon-plugins/wx-java-mp-solon-plugin/src/main/java/com/binarywang/solon/wxjava/mp/properties/WxMpProperties.java b/solon-plugins/wx-java-mp-solon-plugin/src/main/java/com/binarywang/solon/wxjava/mp/properties/WxMpProperties.java
index cda0aa88e7..0dcc6b41d3 100644
--- a/solon-plugins/wx-java-mp-solon-plugin/src/main/java/com/binarywang/solon/wxjava/mp/properties/WxMpProperties.java
+++ b/solon-plugins/wx-java-mp-solon-plugin/src/main/java/com/binarywang/solon/wxjava/mp/properties/WxMpProperties.java
@@ -79,7 +79,7 @@ public static class ConfigStorage implements Serializable {
/**
* http客户端类型.
*/
- private HttpClientType httpClientType = HttpClientType.HttpClient;
+ private HttpClientType httpClientType = HttpClientType.HttpComponents;
/**
* http代理主机.
diff --git a/solon-plugins/wx-java-pay-solon-plugin/src/main/java/com/binarywang/solon/wxjava/pay/config/WxPayAutoConfiguration.java b/solon-plugins/wx-java-pay-solon-plugin/src/main/java/com/binarywang/solon/wxjava/pay/config/WxPayAutoConfiguration.java
index 94112c7d9d..3ef7456daa 100644
--- a/solon-plugins/wx-java-pay-solon-plugin/src/main/java/com/binarywang/solon/wxjava/pay/config/WxPayAutoConfiguration.java
+++ b/solon-plugins/wx-java-pay-solon-plugin/src/main/java/com/binarywang/solon/wxjava/pay/config/WxPayAutoConfiguration.java
@@ -47,6 +47,7 @@ public WxPayService wxPayService() {
payConfig.setKeyPath(StringUtils.trimToNull(this.properties.getKeyPath()));
payConfig.setUseSandboxEnv(this.properties.isUseSandboxEnv());
payConfig.setNotifyUrl(StringUtils.trimToNull(this.properties.getNotifyUrl()));
+ payConfig.setRefundNotifyUrl(StringUtils.trimToNull(this.properties.getRefundNotifyUrl()));
//以下是apiv3以及支付分相关
payConfig.setServiceId(StringUtils.trimToNull(this.properties.getServiceId()));
payConfig.setPayScoreNotifyUrl(StringUtils.trimToNull(this.properties.getPayScoreNotifyUrl()));
diff --git a/solon-plugins/wx-java-pay-solon-plugin/src/main/java/com/binarywang/solon/wxjava/pay/properties/WxPayProperties.java b/solon-plugins/wx-java-pay-solon-plugin/src/main/java/com/binarywang/solon/wxjava/pay/properties/WxPayProperties.java
index 0b035e983e..d394fefbd1 100644
--- a/solon-plugins/wx-java-pay-solon-plugin/src/main/java/com/binarywang/solon/wxjava/pay/properties/WxPayProperties.java
+++ b/solon-plugins/wx-java-pay-solon-plugin/src/main/java/com/binarywang/solon/wxjava/pay/properties/WxPayProperties.java
@@ -87,6 +87,11 @@ public class WxPayProperties {
*/
private String notifyUrl;
+ /**
+ * 退款结果异步回调地址,通知url必须为直接可访问的url,不能携带参数.
+ */
+ private String refundNotifyUrl;
+
/**
* 微信支付分授权回调地址
*/
diff --git a/solon-plugins/wx-java-qidian-solon-plugin/src/main/java/com/binarywang/solon/wxjava/qidian/config/WxQidianServiceAutoConfiguration.java b/solon-plugins/wx-java-qidian-solon-plugin/src/main/java/com/binarywang/solon/wxjava/qidian/config/WxQidianServiceAutoConfiguration.java
index f3dce59a73..02ec06cd25 100644
--- a/solon-plugins/wx-java-qidian-solon-plugin/src/main/java/com/binarywang/solon/wxjava/qidian/config/WxQidianServiceAutoConfiguration.java
+++ b/solon-plugins/wx-java-qidian-solon-plugin/src/main/java/com/binarywang/solon/wxjava/qidian/config/WxQidianServiceAutoConfiguration.java
@@ -4,6 +4,7 @@
import com.binarywang.solon.wxjava.qidian.properties.WxQidianProperties;
import me.chanjar.weixin.qidian.api.WxQidianService;
import me.chanjar.weixin.qidian.api.impl.WxQidianServiceHttpClientImpl;
+import me.chanjar.weixin.qidian.api.impl.WxQidianServiceHttpComponentsImpl;
import me.chanjar.weixin.qidian.api.impl.WxQidianServiceImpl;
import me.chanjar.weixin.qidian.api.impl.WxQidianServiceJoddHttpImpl;
import me.chanjar.weixin.qidian.api.impl.WxQidianServiceOkHttpImpl;
@@ -35,6 +36,9 @@ public WxQidianService wxQidianService(WxQidianConfigStorage configStorage, WxQi
case HttpClient:
wxQidianService = newWxQidianServiceHttpClientImpl();
break;
+ case HttpComponents:
+ wxQidianService = newWxQidianServiceHttpComponentsImpl();
+ break;
default:
wxQidianService = newWxQidianServiceImpl();
break;
@@ -60,4 +64,8 @@ private WxQidianService newWxQidianServiceJoddHttpImpl() {
return new WxQidianServiceJoddHttpImpl();
}
+ private WxQidianService newWxQidianServiceHttpComponentsImpl() {
+ return new WxQidianServiceHttpComponentsImpl();
+ }
+
}
diff --git a/solon-plugins/wx-java-qidian-solon-plugin/src/main/java/com/binarywang/solon/wxjava/qidian/enums/HttpClientType.java b/solon-plugins/wx-java-qidian-solon-plugin/src/main/java/com/binarywang/solon/wxjava/qidian/enums/HttpClientType.java
index 0b94821da7..5729ab8fda 100644
--- a/solon-plugins/wx-java-qidian-solon-plugin/src/main/java/com/binarywang/solon/wxjava/qidian/enums/HttpClientType.java
+++ b/solon-plugins/wx-java-qidian-solon-plugin/src/main/java/com/binarywang/solon/wxjava/qidian/enums/HttpClientType.java
@@ -19,4 +19,8 @@ public enum HttpClientType {
* JoddHttp.
*/
JoddHttp,
+ /**
+ * HttpComponents.
+ */
+ HttpComponents,
}
diff --git a/solon-plugins/wx-java-qidian-solon-plugin/src/main/java/com/binarywang/solon/wxjava/qidian/properties/WxQidianProperties.java b/solon-plugins/wx-java-qidian-solon-plugin/src/main/java/com/binarywang/solon/wxjava/qidian/properties/WxQidianProperties.java
index 67c4dba543..e99f882e6f 100644
--- a/solon-plugins/wx-java-qidian-solon-plugin/src/main/java/com/binarywang/solon/wxjava/qidian/properties/WxQidianProperties.java
+++ b/solon-plugins/wx-java-qidian-solon-plugin/src/main/java/com/binarywang/solon/wxjava/qidian/properties/WxQidianProperties.java
@@ -74,7 +74,7 @@ public static class ConfigStorage implements Serializable {
/**
* http客户端类型.
*/
- private HttpClientType httpClientType = HttpClientType.HttpClient;
+ private HttpClientType httpClientType = HttpClientType.HttpComponents;
/**
* http代理主机.
diff --git a/spring-boot-starters/pom.xml b/spring-boot-starters/pom.xml
index 1f4881e9dd..8b000ff8c2 100644
--- a/spring-boot-starters/pom.xml
+++ b/spring-boot-starters/pom.xml
@@ -23,6 +23,8 @@
wx-java-mp-multi-spring-boot-starter
wx-java-mp-spring-boot-starter
wx-java-pay-spring-boot-starter
+ wx-java-pay-multi-spring-boot-starter
+ wx-java-open-multi-spring-boot-starter
wx-java-open-spring-boot-starter
wx-java-qidian-spring-boot-starter
wx-java-cp-multi-spring-boot-starter
diff --git a/spring-boot-starters/wx-java-channel-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/channel/configuration/services/AbstractWxChannelConfiguration.java b/spring-boot-starters/wx-java-channel-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/channel/configuration/services/AbstractWxChannelConfiguration.java
index 3462bbc25e..e2f9f87f5a 100644
--- a/spring-boot-starters/wx-java-channel-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/channel/configuration/services/AbstractWxChannelConfiguration.java
+++ b/spring-boot-starters/wx-java-channel-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/channel/configuration/services/AbstractWxChannelConfiguration.java
@@ -9,6 +9,7 @@
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.channel.api.WxChannelService;
import me.chanjar.weixin.channel.api.impl.WxChannelServiceHttpClientImpl;
+import me.chanjar.weixin.channel.api.impl.WxChannelServiceHttpComponentsImpl;
import me.chanjar.weixin.channel.api.impl.WxChannelServiceImpl;
import me.chanjar.weixin.channel.config.WxChannelConfig;
import me.chanjar.weixin.channel.config.impl.WxChannelDefaultConfigImpl;
@@ -84,6 +85,9 @@ public WxChannelService wxChannelService(WxChannelConfig wxChannelConfig, WxChan
case HTTP_CLIENT:
wxChannelService = new WxChannelServiceHttpClientImpl();
break;
+ case HTTP_COMPONENTS:
+ wxChannelService = new WxChannelServiceHttpComponentsImpl();
+ break;
default:
wxChannelService = new WxChannelServiceImpl();
break;
diff --git a/spring-boot-starters/wx-java-channel-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/channel/enums/HttpClientType.java b/spring-boot-starters/wx-java-channel-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/channel/enums/HttpClientType.java
index 6ca09354a3..adc8a2b52b 100644
--- a/spring-boot-starters/wx-java-channel-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/channel/enums/HttpClientType.java
+++ b/spring-boot-starters/wx-java-channel-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/channel/enums/HttpClientType.java
@@ -8,7 +8,7 @@
*/
public enum HttpClientType {
/**
- * HttpClient
+ * HttpClient.
*/
HTTP_CLIENT,
// WxChannelServiceOkHttpImpl 实现经测试无法正常完成业务固暂不支持OK_HTTP方式
@@ -16,4 +16,8 @@ public enum HttpClientType {
// * OkHttp.
// */
// OK_HTTP,
+ /**
+ * HttpComponents.
+ */
+ HTTP_COMPONENTS,
}
diff --git a/spring-boot-starters/wx-java-channel-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/channel/enums/HttpClientType.java b/spring-boot-starters/wx-java-channel-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/channel/enums/HttpClientType.java
index 63a7bf0c24..e4b3f3ad16 100644
--- a/spring-boot-starters/wx-java-channel-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/channel/enums/HttpClientType.java
+++ b/spring-boot-starters/wx-java-channel-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/channel/enums/HttpClientType.java
@@ -7,7 +7,11 @@
*/
public enum HttpClientType {
/**
- * HttpClient
+ * HttpClient.
*/
- HttpClient
+ HttpClient,
+ /**
+ * HttpComponents.
+ */
+ HttpComponents,
}
diff --git a/spring-boot-starters/wx-java-channel-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/channel/properties/WxChannelProperties.java b/spring-boot-starters/wx-java-channel-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/channel/properties/WxChannelProperties.java
index 43c35fbd1d..f43d297e0b 100644
--- a/spring-boot-starters/wx-java-channel-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/channel/properties/WxChannelProperties.java
+++ b/spring-boot-starters/wx-java-channel-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/channel/properties/WxChannelProperties.java
@@ -85,7 +85,7 @@ public static class ConfigStorage {
/**
* http客户端类型
*/
- private HttpClientType httpClientType = HttpClientType.HttpClient;
+ private HttpClientType httpClientType = HttpClientType.HttpComponents;
/**
* http代理主机
diff --git a/spring-boot-starters/wx-java-miniapp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/miniapp/config/WxMaServiceAutoConfiguration.java b/spring-boot-starters/wx-java-miniapp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/miniapp/config/WxMaServiceAutoConfiguration.java
index 79c16fb053..f03d3f1493 100644
--- a/spring-boot-starters/wx-java-miniapp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/miniapp/config/WxMaServiceAutoConfiguration.java
+++ b/spring-boot-starters/wx-java-miniapp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/miniapp/config/WxMaServiceAutoConfiguration.java
@@ -2,6 +2,7 @@
import cn.binarywang.wx.miniapp.api.WxMaService;
import cn.binarywang.wx.miniapp.api.impl.WxMaServiceHttpClientImpl;
+import cn.binarywang.wx.miniapp.api.impl.WxMaServiceHttpComponentsImpl;
import cn.binarywang.wx.miniapp.api.impl.WxMaServiceImpl;
import cn.binarywang.wx.miniapp.api.impl.WxMaServiceJoddHttpImpl;
import cn.binarywang.wx.miniapp.api.impl.WxMaServiceOkHttpImpl;
@@ -46,6 +47,9 @@ public WxMaService wxMaService(WxMaConfig wxMaConfig) {
case HttpClient:
wxMaService = new WxMaServiceHttpClientImpl();
break;
+ case HttpComponents:
+ wxMaService = new WxMaServiceHttpComponentsImpl();
+ break;
default:
wxMaService = new WxMaServiceImpl();
break;
diff --git a/spring-boot-starters/wx-java-miniapp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/miniapp/enums/HttpClientType.java b/spring-boot-starters/wx-java-miniapp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/miniapp/enums/HttpClientType.java
index b3e4b464fe..48549e4399 100644
--- a/spring-boot-starters/wx-java-miniapp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/miniapp/enums/HttpClientType.java
+++ b/spring-boot-starters/wx-java-miniapp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/miniapp/enums/HttpClientType.java
@@ -8,7 +8,7 @@
*/
public enum HttpClientType {
/**
- * HttpClient.
+ * HttpClient (Apache HttpClient 4.x).
*/
HttpClient,
/**
@@ -19,4 +19,8 @@ public enum HttpClientType {
* JoddHttp.
*/
JoddHttp,
+ /**
+ * HttpComponents (Apache HttpClient 5.x).
+ */
+ HttpComponents,
}
diff --git a/spring-boot-starters/wx-java-miniapp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/miniapp/properties/WxMaProperties.java b/spring-boot-starters/wx-java-miniapp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/miniapp/properties/WxMaProperties.java
index 7e88db904f..82f1500941 100644
--- a/spring-boot-starters/wx-java-miniapp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/miniapp/properties/WxMaProperties.java
+++ b/spring-boot-starters/wx-java-miniapp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/miniapp/properties/WxMaProperties.java
@@ -88,7 +88,7 @@ public static class ConfigStorage {
/**
* http客户端类型.
*/
- private HttpClientType httpClientType = HttpClientType.HttpClient;
+ private HttpClientType httpClientType = HttpClientType.HttpComponents;
/**
* http代理主机.
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 273364c9a7..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
@@ -22,7 +22,7 @@
redis.clients
jedis
- compile
+ provided
org.springframework.data
@@ -44,6 +44,11 @@
httpclient5
provided
+
+ org.redisson
+ redisson
+ provided
+
diff --git a/spring-boot-starters/wx-java-mp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/config/WxMpStorageAutoConfiguration.java b/spring-boot-starters/wx-java-mp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/config/WxMpStorageAutoConfiguration.java
index 4c0938454a..cab3cb17b2 100644
--- a/spring-boot-starters/wx-java-mp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/config/WxMpStorageAutoConfiguration.java
+++ b/spring-boot-starters/wx-java-mp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/config/WxMpStorageAutoConfiguration.java
@@ -1,175 +1,26 @@
package com.binarywang.spring.starter.wxjava.mp.config;
-import com.binarywang.spring.starter.wxjava.mp.enums.StorageType;
-import com.binarywang.spring.starter.wxjava.mp.properties.RedisProperties;
-import com.binarywang.spring.starter.wxjava.mp.properties.WxMpProperties;
-import com.google.common.collect.Sets;
+import com.binarywang.spring.starter.wxjava.mp.config.storage.WxMpInJedisConfigStorageConfiguration;
+import com.binarywang.spring.starter.wxjava.mp.config.storage.WxMpInMemoryConfigStorageConfiguration;
+import com.binarywang.spring.starter.wxjava.mp.config.storage.WxMpInRedisTemplateConfigStorageConfiguration;
+import com.binarywang.spring.starter.wxjava.mp.config.storage.WxMpInRedissonConfigStorageConfiguration;
import lombok.RequiredArgsConstructor;
-import lombok.extern.slf4j.Slf4j;
-import me.chanjar.weixin.common.redis.JedisWxRedisOps;
-import me.chanjar.weixin.common.redis.RedisTemplateWxRedisOps;
-import me.chanjar.weixin.common.redis.WxRedisOps;
-import me.chanjar.weixin.common.util.http.apache.ApacheHttpClientBuilder;
-import me.chanjar.weixin.common.util.http.apache.DefaultApacheHttpClientBuilder;
-import me.chanjar.weixin.mp.config.WxMpConfigStorage;
-import me.chanjar.weixin.mp.config.WxMpHostConfig;
-import me.chanjar.weixin.mp.config.impl.WxMpDefaultConfigImpl;
-import me.chanjar.weixin.mp.config.impl.WxMpRedisConfigImpl;
-import org.apache.commons.lang3.StringUtils;
-import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
-import org.springframework.context.ApplicationContext;
-import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
-import org.springframework.data.redis.core.StringRedisTemplate;
-import redis.clients.jedis.Jedis;
-import redis.clients.jedis.JedisPool;
-import redis.clients.jedis.JedisPoolConfig;
-import redis.clients.jedis.JedisSentinelPool;
-import redis.clients.jedis.util.Pool;
-
-import java.util.Set;
+import org.springframework.context.annotation.Import;
/**
* 微信公众号存储策略自动配置.
*
* @author Luo
*/
-@Slf4j
@Configuration
+@Import({
+ WxMpInMemoryConfigStorageConfiguration.class,
+ WxMpInJedisConfigStorageConfiguration.class,
+ WxMpInRedisTemplateConfigStorageConfiguration.class,
+ WxMpInRedissonConfigStorageConfiguration.class
+})
@RequiredArgsConstructor
public class WxMpStorageAutoConfiguration {
- private final ApplicationContext applicationContext;
-
- private final WxMpProperties wxMpProperties;
-
- @Bean
- @ConditionalOnMissingBean(WxMpConfigStorage.class)
- public WxMpConfigStorage wxMpConfigStorage() {
- StorageType type = wxMpProperties.getConfigStorage().getType();
- WxMpConfigStorage config;
- switch (type) {
- case Jedis:
- config = jedisConfigStorage();
- break;
- case RedisTemplate:
- config = redisTemplateConfigStorage();
- break;
- default:
- config = defaultConfigStorage();
- break;
- }
- // wx host config
- if (null != wxMpProperties.getHosts() && StringUtils.isNotEmpty(wxMpProperties.getHosts().getApiHost())) {
- WxMpHostConfig hostConfig = new WxMpHostConfig();
- hostConfig.setApiHost(wxMpProperties.getHosts().getApiHost());
- hostConfig.setMpHost(wxMpProperties.getHosts().getMpHost());
- hostConfig.setOpenHost(wxMpProperties.getHosts().getOpenHost());
- config.setHostConfig(hostConfig);
- }
- return config;
- }
-
- private WxMpConfigStorage defaultConfigStorage() {
- WxMpDefaultConfigImpl config = new WxMpDefaultConfigImpl();
- setWxMpInfo(config);
- return config;
- }
-
- private WxMpConfigStorage jedisConfigStorage() {
- Pool jedisPool;
- if (wxMpProperties.getConfigStorage() != null && wxMpProperties.getConfigStorage().getRedis() != null
- && StringUtils.isNotEmpty(wxMpProperties.getConfigStorage().getRedis().getHost())) {
- jedisPool = getJedisPool();
- } else {
- jedisPool = applicationContext.getBean(JedisPool.class);
- }
- WxRedisOps redisOps = new JedisWxRedisOps(jedisPool);
- WxMpRedisConfigImpl wxMpRedisConfig = new WxMpRedisConfigImpl(redisOps,
- wxMpProperties.getConfigStorage().getKeyPrefix());
- setWxMpInfo(wxMpRedisConfig);
- return wxMpRedisConfig;
- }
-
- private WxMpConfigStorage redisTemplateConfigStorage() {
- StringRedisTemplate redisTemplate = null;
- try {
- redisTemplate = applicationContext.getBean(StringRedisTemplate.class);
- } catch (Exception e) {
- log.error(e.getMessage(), e);
- }
- try {
- if (null == redisTemplate) {
- redisTemplate = (StringRedisTemplate) applicationContext.getBean("stringRedisTemplate");
- }
- } catch (Exception e) {
- log.error(e.getMessage(), e);
- }
-
- if (null == redisTemplate) {
- redisTemplate = (StringRedisTemplate) applicationContext.getBean("redisTemplate");
- }
-
- WxRedisOps redisOps = new RedisTemplateWxRedisOps(redisTemplate);
- WxMpRedisConfigImpl wxMpRedisConfig = new WxMpRedisConfigImpl(redisOps,
- wxMpProperties.getConfigStorage().getKeyPrefix());
-
- setWxMpInfo(wxMpRedisConfig);
- return wxMpRedisConfig;
- }
-
- private void setWxMpInfo(WxMpDefaultConfigImpl config) {
- WxMpProperties properties = wxMpProperties;
- WxMpProperties.ConfigStorage configStorageProperties = properties.getConfigStorage();
- config.setAppId(properties.getAppId());
- config.setSecret(properties.getSecret());
- config.setToken(properties.getToken());
- config.setAesKey(properties.getAesKey());
- WxMpProperties.ConfigStorage storage = properties.getConfigStorage();
- // 设置自定义的HttpClient超时配置
- ApacheHttpClientBuilder clientBuilder = config.getApacheHttpClientBuilder();
- if (clientBuilder == null) {
- clientBuilder = DefaultApacheHttpClientBuilder.get();
- }
- if (clientBuilder instanceof DefaultApacheHttpClientBuilder) {
- DefaultApacheHttpClientBuilder defaultBuilder = (DefaultApacheHttpClientBuilder) clientBuilder;
- defaultBuilder.setConnectionTimeout(storage.getConnectionTimeout());
- defaultBuilder.setSoTimeout(storage.getSoTimeout());
- defaultBuilder.setConnectionRequestTimeout(storage.getConnectionRequestTimeout());
- config.setApacheHttpClientBuilder(defaultBuilder);
- }
- config.setUseStableAccessToken(wxMpProperties.isUseStableAccessToken());
- config.setHttpProxyHost(configStorageProperties.getHttpProxyHost());
- config.setHttpProxyUsername(configStorageProperties.getHttpProxyUsername());
- config.setHttpProxyPassword(configStorageProperties.getHttpProxyPassword());
- if (configStorageProperties.getHttpProxyPort() != null) {
- config.setHttpProxyPort(configStorageProperties.getHttpProxyPort());
- }
- }
-
- private Pool getJedisPool() {
- RedisProperties redis = wxMpProperties.getConfigStorage().getRedis();
-
- JedisPoolConfig config = new JedisPoolConfig();
- if (redis.getMaxActive() != null) {
- config.setMaxTotal(redis.getMaxActive());
- }
- if (redis.getMaxIdle() != null) {
- config.setMaxIdle(redis.getMaxIdle());
- }
- if (redis.getMaxWaitMillis() != null) {
- config.setMaxWaitMillis(redis.getMaxWaitMillis());
- }
- if (redis.getMinIdle() != null) {
- config.setMinIdle(redis.getMinIdle());
- }
- config.setTestOnBorrow(true);
- config.setTestWhileIdle(true);
- if (StringUtils.isNotEmpty(redis.getSentinelIps())) {
- Set sentinels = Sets.newHashSet(redis.getSentinelIps().split(","));
- return new JedisSentinelPool(redis.getSentinelName(), sentinels);
- }
- return new JedisPool(config, redis.getHost(), redis.getPort(), redis.getTimeout(), redis.getPassword(),
- redis.getDatabase());
- }
}
diff --git a/spring-boot-starters/wx-java-mp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/config/storage/AbstractWxMpConfigStorageConfiguration.java b/spring-boot-starters/wx-java-mp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/config/storage/AbstractWxMpConfigStorageConfiguration.java
new file mode 100644
index 0000000000..e39a8bf4d9
--- /dev/null
+++ b/spring-boot-starters/wx-java-mp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/config/storage/AbstractWxMpConfigStorageConfiguration.java
@@ -0,0 +1,54 @@
+package com.binarywang.spring.starter.wxjava.mp.config.storage;
+
+import com.binarywang.spring.starter.wxjava.mp.properties.WxMpProperties;
+import me.chanjar.weixin.common.util.http.apache.ApacheHttpClientBuilder;
+import me.chanjar.weixin.common.util.http.apache.DefaultApacheHttpClientBuilder;
+import me.chanjar.weixin.mp.config.WxMpHostConfig;
+import me.chanjar.weixin.mp.config.impl.WxMpDefaultConfigImpl;
+import org.apache.commons.lang3.StringUtils;
+
+/**
+ * @author zhangyl
+ */
+public abstract class AbstractWxMpConfigStorageConfiguration {
+
+ protected WxMpDefaultConfigImpl config(WxMpDefaultConfigImpl config, WxMpProperties properties) {
+ config.setAppId(properties.getAppId());
+ config.setSecret(properties.getSecret());
+ config.setToken(properties.getToken());
+ config.setAesKey(properties.getAesKey());
+ config.setUseStableAccessToken(properties.isUseStableAccessToken());
+
+ WxMpProperties.ConfigStorage configStorageProperties = properties.getConfigStorage();
+ config.setHttpProxyHost(configStorageProperties.getHttpProxyHost());
+ config.setHttpProxyUsername(configStorageProperties.getHttpProxyUsername());
+ config.setHttpProxyPassword(configStorageProperties.getHttpProxyPassword());
+ if (configStorageProperties.getHttpProxyPort() != null) {
+ config.setHttpProxyPort(configStorageProperties.getHttpProxyPort());
+ }
+
+ // 设置自定义的 HttpClient 超时配置
+ ApacheHttpClientBuilder clientBuilder = config.getApacheHttpClientBuilder();
+ if (clientBuilder == null) {
+ clientBuilder = DefaultApacheHttpClientBuilder.get();
+ }
+ if (clientBuilder instanceof DefaultApacheHttpClientBuilder) {
+ DefaultApacheHttpClientBuilder defaultBuilder = (DefaultApacheHttpClientBuilder) clientBuilder;
+ defaultBuilder.setConnectionTimeout(configStorageProperties.getConnectionTimeout());
+ defaultBuilder.setSoTimeout(configStorageProperties.getSoTimeout());
+ defaultBuilder.setConnectionRequestTimeout(configStorageProperties.getConnectionRequestTimeout());
+ config.setApacheHttpClientBuilder(defaultBuilder);
+ }
+
+ // wx host config
+ if (null != properties.getHosts() && StringUtils.isNotEmpty(properties.getHosts().getApiHost())) {
+ WxMpHostConfig hostConfig = new WxMpHostConfig();
+ hostConfig.setApiHost(properties.getHosts().getApiHost());
+ hostConfig.setOpenHost(properties.getHosts().getOpenHost());
+ hostConfig.setMpHost(properties.getHosts().getMpHost());
+ config.setHostConfig(hostConfig);
+ }
+
+ return config;
+ }
+}
diff --git a/spring-boot-starters/wx-java-mp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/config/storage/WxMpInJedisConfigStorageConfiguration.java b/spring-boot-starters/wx-java-mp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/config/storage/WxMpInJedisConfigStorageConfiguration.java
new file mode 100644
index 0000000000..c21418a6f6
--- /dev/null
+++ b/spring-boot-starters/wx-java-mp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/config/storage/WxMpInJedisConfigStorageConfiguration.java
@@ -0,0 +1,80 @@
+package com.binarywang.spring.starter.wxjava.mp.config.storage;
+
+import com.binarywang.spring.starter.wxjava.mp.properties.RedisProperties;
+import com.binarywang.spring.starter.wxjava.mp.properties.WxMpProperties;
+import lombok.RequiredArgsConstructor;
+import me.chanjar.weixin.common.redis.JedisWxRedisOps;
+import me.chanjar.weixin.common.redis.WxRedisOps;
+import me.chanjar.weixin.mp.config.WxMpConfigStorage;
+import me.chanjar.weixin.mp.config.impl.WxMpRedisConfigImpl;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import redis.clients.jedis.Jedis;
+import redis.clients.jedis.JedisPool;
+import redis.clients.jedis.JedisPoolConfig;
+
+/**
+ * @author zhangyl
+ */
+@Configuration
+@ConditionalOnProperty(
+ prefix = WxMpProperties.PREFIX + ".config-storage",
+ name = "type",
+ havingValue = "jedis"
+)
+@ConditionalOnClass(Jedis.class)
+@RequiredArgsConstructor
+public class WxMpInJedisConfigStorageConfiguration extends AbstractWxMpConfigStorageConfiguration {
+ private final WxMpProperties properties;
+ private final ApplicationContext applicationContext;
+
+ @Bean
+ @ConditionalOnMissingBean(WxMpConfigStorage.class)
+ public WxMpConfigStorage wxMpConfigStorage() {
+ WxMpRedisConfigImpl config = getWxMpRedisConfigImpl();
+ return this.config(config, properties);
+ }
+
+ private WxMpRedisConfigImpl getWxMpRedisConfigImpl() {
+ RedisProperties redisProperties = properties.getConfigStorage().getRedis();
+ JedisPool jedisPool;
+ if (redisProperties != null && StringUtils.isNotEmpty(redisProperties.getHost())) {
+ jedisPool = applicationContext.getBean("wxMpJedisPool", JedisPool.class);
+ } else {
+ jedisPool = applicationContext.getBean(JedisPool.class);
+ }
+ WxRedisOps redisOps = new JedisWxRedisOps(jedisPool);
+ return new WxMpRedisConfigImpl(redisOps, properties.getConfigStorage().getKeyPrefix());
+ }
+
+ @Bean
+ @ConditionalOnProperty(prefix = WxMpProperties.PREFIX + ".config-storage.redis", name = "host")
+ public JedisPool wxMpJedisPool() {
+ WxMpProperties.ConfigStorage storage = properties.getConfigStorage();
+ RedisProperties redis = storage.getRedis();
+
+ JedisPoolConfig config = new JedisPoolConfig();
+ if (redis.getMaxActive() != null) {
+ config.setMaxTotal(redis.getMaxActive());
+ }
+ if (redis.getMaxIdle() != null) {
+ config.setMaxIdle(redis.getMaxIdle());
+ }
+ if (redis.getMaxWaitMillis() != null) {
+ config.setMaxWaitMillis(redis.getMaxWaitMillis());
+ }
+ if (redis.getMinIdle() != null) {
+ config.setMinIdle(redis.getMinIdle());
+ }
+ config.setTestOnBorrow(true);
+ config.setTestWhileIdle(true);
+
+ return new JedisPool(config, redis.getHost(), redis.getPort(), redis.getTimeout(), redis.getPassword(),
+ redis.getDatabase());
+ }
+}
diff --git a/spring-boot-starters/wx-java-mp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/config/storage/WxMpInMemoryConfigStorageConfiguration.java b/spring-boot-starters/wx-java-mp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/config/storage/WxMpInMemoryConfigStorageConfiguration.java
new file mode 100644
index 0000000000..16eada73ae
--- /dev/null
+++ b/spring-boot-starters/wx-java-mp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/config/storage/WxMpInMemoryConfigStorageConfiguration.java
@@ -0,0 +1,33 @@
+package com.binarywang.spring.starter.wxjava.mp.config.storage;
+
+import com.binarywang.spring.starter.wxjava.mp.properties.WxMpProperties;
+import lombok.RequiredArgsConstructor;
+import me.chanjar.weixin.mp.config.WxMpConfigStorage;
+import me.chanjar.weixin.mp.config.impl.WxMpDefaultConfigImpl;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * @author zhangyl
+ */
+@Configuration
+@ConditionalOnProperty(
+ prefix = WxMpProperties.PREFIX + ".config-storage",
+ name = "type",
+ havingValue = "memory",
+ matchIfMissing = true
+)
+@RequiredArgsConstructor
+public class WxMpInMemoryConfigStorageConfiguration extends AbstractWxMpConfigStorageConfiguration {
+ private final WxMpProperties properties;
+
+ @Bean
+ @ConditionalOnMissingBean(WxMpConfigStorage.class)
+ public WxMpConfigStorage wxMpConfigStorage() {
+ WxMpDefaultConfigImpl config = new WxMpDefaultConfigImpl();
+ config(config, properties);
+ return config;
+ }
+}
diff --git a/spring-boot-starters/wx-java-mp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/config/storage/WxMpInRedisTemplateConfigStorageConfiguration.java b/spring-boot-starters/wx-java-mp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/config/storage/WxMpInRedisTemplateConfigStorageConfiguration.java
new file mode 100644
index 0000000000..0305ca4f8e
--- /dev/null
+++ b/spring-boot-starters/wx-java-mp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/config/storage/WxMpInRedisTemplateConfigStorageConfiguration.java
@@ -0,0 +1,46 @@
+package com.binarywang.spring.starter.wxjava.mp.config.storage;
+
+import com.binarywang.spring.starter.wxjava.mp.properties.WxMpProperties;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import me.chanjar.weixin.common.redis.RedisTemplateWxRedisOps;
+import me.chanjar.weixin.common.redis.WxRedisOps;
+import me.chanjar.weixin.mp.config.WxMpConfigStorage;
+import me.chanjar.weixin.mp.config.impl.WxMpRedisConfigImpl;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.data.redis.core.StringRedisTemplate;
+
+/**
+ * @author zhangyl
+ */
+@Slf4j
+@Configuration
+@ConditionalOnProperty(
+ prefix = WxMpProperties.PREFIX + ".config-storage",
+ name = "type",
+ havingValue = "redistemplate"
+)
+@ConditionalOnClass(StringRedisTemplate.class)
+@RequiredArgsConstructor
+public class WxMpInRedisTemplateConfigStorageConfiguration extends AbstractWxMpConfigStorageConfiguration {
+ private final WxMpProperties properties;
+ private final ApplicationContext applicationContext;
+
+ @Bean
+ @ConditionalOnMissingBean(WxMpConfigStorage.class)
+ public WxMpConfigStorage wxMpConfigStorage() {
+ WxMpRedisConfigImpl config = getWxMpInRedisTemplateConfigStorage();
+ return this.config(config, properties);
+ }
+
+ private WxMpRedisConfigImpl getWxMpInRedisTemplateConfigStorage() {
+ StringRedisTemplate redisTemplate = applicationContext.getBean(StringRedisTemplate.class);
+ WxRedisOps redisOps = new RedisTemplateWxRedisOps(redisTemplate);
+ return new WxMpRedisConfigImpl(redisOps, properties.getConfigStorage().getKeyPrefix());
+ }
+}
diff --git a/spring-boot-starters/wx-java-mp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/config/storage/WxMpInRedissonConfigStorageConfiguration.java b/spring-boot-starters/wx-java-mp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/config/storage/WxMpInRedissonConfigStorageConfiguration.java
new file mode 100644
index 0000000000..75b736f53f
--- /dev/null
+++ b/spring-boot-starters/wx-java-mp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/config/storage/WxMpInRedissonConfigStorageConfiguration.java
@@ -0,0 +1,69 @@
+package com.binarywang.spring.starter.wxjava.mp.config.storage;
+
+import com.binarywang.spring.starter.wxjava.mp.properties.RedisProperties;
+import com.binarywang.spring.starter.wxjava.mp.properties.WxMpProperties;
+import lombok.RequiredArgsConstructor;
+import me.chanjar.weixin.mp.config.WxMpConfigStorage;
+import me.chanjar.weixin.mp.config.impl.WxMpRedissonConfigImpl;
+import org.apache.commons.lang3.StringUtils;
+import org.redisson.Redisson;
+import org.redisson.api.RedissonClient;
+import org.redisson.config.Config;
+import org.redisson.config.TransportMode;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * @author zhangyl
+ */
+@Configuration
+@ConditionalOnProperty(
+ prefix = WxMpProperties.PREFIX + ".config-storage",
+ name = "type",
+ havingValue = "redisson"
+)
+@ConditionalOnClass({Redisson.class, RedissonClient.class})
+@RequiredArgsConstructor
+public class WxMpInRedissonConfigStorageConfiguration extends AbstractWxMpConfigStorageConfiguration {
+ private final WxMpProperties properties;
+ private final ApplicationContext applicationContext;
+
+ @Bean
+ @ConditionalOnMissingBean(WxMpConfigStorage.class)
+ public WxMpConfigStorage wxMpConfigStorage() {
+ WxMpRedissonConfigImpl config = getWxMpInRedissonConfigStorage();
+ return this.config(config, properties);
+ }
+
+ private WxMpRedissonConfigImpl getWxMpInRedissonConfigStorage() {
+ RedisProperties redisProperties = properties.getConfigStorage().getRedis();
+ RedissonClient redissonClient;
+ if (redisProperties != null && StringUtils.isNotEmpty(redisProperties.getHost())) {
+ redissonClient = applicationContext.getBean("wxMpRedissonClient", RedissonClient.class);
+ } else {
+ redissonClient = applicationContext.getBean(RedissonClient.class);
+ }
+ return new WxMpRedissonConfigImpl(redissonClient, properties.getConfigStorage().getKeyPrefix());
+ }
+
+ @Bean
+ @ConditionalOnProperty(prefix = WxMpProperties.PREFIX + ".config-storage.redis", name = "host")
+ public RedissonClient wxMpRedissonClient() {
+ WxMpProperties.ConfigStorage storage = properties.getConfigStorage();
+ RedisProperties redis = storage.getRedis();
+
+ Config config = new Config();
+ config.useSingleServer()
+ .setAddress("redis://" + redis.getHost() + ":" + redis.getPort())
+ .setDatabase(redis.getDatabase());
+ if (StringUtils.isNotBlank(redis.getPassword())) {
+ config.useSingleServer().setPassword(redis.getPassword());
+ }
+ config.setTransportMode(TransportMode.NIO);
+ return Redisson.create(config);
+ }
+}
diff --git a/spring-boot-starters/wx-java-mp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/properties/WxMpProperties.java b/spring-boot-starters/wx-java-mp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/properties/WxMpProperties.java
index 377fb5b6ab..a6c6e3b6bd 100644
--- a/spring-boot-starters/wx-java-mp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/properties/WxMpProperties.java
+++ b/spring-boot-starters/wx-java-mp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/properties/WxMpProperties.java
@@ -80,7 +80,7 @@ public static class ConfigStorage implements Serializable {
/**
* http客户端类型.
*/
- private HttpClientType httpClientType = HttpClientType.HttpClient;
+ private HttpClientType httpClientType = HttpClientType.HttpComponents;
/**
* http代理主机.
diff --git a/spring-boot-starters/wx-java-open-multi-spring-boot-starter/README.md b/spring-boot-starters/wx-java-open-multi-spring-boot-starter/README.md
new file mode 100644
index 0000000000..ab5afa5449
--- /dev/null
+++ b/spring-boot-starters/wx-java-open-multi-spring-boot-starter/README.md
@@ -0,0 +1,98 @@
+# wx-java-open-multi-spring-boot-starter
+
+## 快速开始
+
+1. 引入依赖
+ ```xml
+
+ com.github.binarywang
+ wx-java-open-multi-spring-boot-starter
+ ${version}
+
+ ```
+2. 添加配置(application.properties)
+ ```properties
+ # 开放平台配置
+ ## 应用 1 配置(必填)
+ wx.open.apps.tenantId1.app-id=appId
+ wx.open.apps.tenantId1.secret=@secret
+ ## 选填
+ wx.open.apps.tenantId1.token=@token
+ wx.open.apps.tenantId1.aes-key=@aesKey
+ wx.open.apps.tenantId1.api-host-url=@apiHostUrl
+ wx.open.apps.tenantId1.access-token-url=@accessTokenUrl
+ ## 应用 2 配置(必填)
+ wx.open.apps.tenantId2.app-id=@appId
+ wx.open.apps.tenantId2.secret=@secret
+ ## 选填
+ wx.open.apps.tenantId2.token=@token
+ wx.open.apps.tenantId2.aes-key=@aesKey
+ wx.open.apps.tenantId2.api-host-url=@apiHostUrl
+ wx.open.apps.tenantId2.access-token-url=@accessTokenUrl
+
+ # ConfigStorage 配置(选填)
+ ## 配置类型: memory(默认), jedis, redisson, redistemplate
+ wx.open.config-storage.type=memory
+ ## 相关redis前缀配置: wx:open:multi(默认)
+ wx.open.config-storage.key-prefix=wx:open:multi
+ wx.open.config-storage.redis.host=127.0.0.1
+ wx.open.config-storage.redis.port=6379
+ ## 注意:当前版本暂不支持 sentinel 配置,以下配置仅作为预留
+ # wx.open.config-storage.redis.sentinel-ips=127.0.0.1:16379,127.0.0.1:26379
+ # wx.open.config-storage.redis.sentinel-name=mymaster
+
+ # http 客户端配置(选填)
+ wx.open.config-storage.http-proxy-host=
+ wx.open.config-storage.http-proxy-port=
+ wx.open.config-storage.http-proxy-username=
+ wx.open.config-storage.http-proxy-password=
+ ## 最大重试次数,默认:5 次,如果小于 0,则为 0
+ wx.open.config-storage.max-retry-times=5
+ ## 重试时间间隔步进,默认:1000 毫秒,如果小于 0,则为 1000
+ wx.open.config-storage.retry-sleep-millis=1000
+ ## 连接超时时间,单位毫秒,默认:5000
+ wx.open.config-storage.connection-timeout=5000
+ ## 读数据超时时间,即socketTimeout,单位毫秒,默认:5000
+ wx.open.config-storage.so-timeout=5000
+ ## 从连接池获取链接的超时时间,单位毫秒,默认:5000
+ wx.open.config-storage.connection-request-timeout=5000
+ ```
+3. 自动注入的类型:`WxOpenMultiServices`
+
+4. 使用样例
+
+```java
+import com.binarywang.spring.starter.wxjava.open.service.WxOpenMultiServices;
+import me.chanjar.weixin.open.api.WxOpenService;
+import me.chanjar.weixin.open.api.WxOpenComponentService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+@Service
+public class DemoService {
+ @Autowired
+ private WxOpenMultiServices wxOpenMultiServices;
+
+ public void test() {
+ // 应用 1 的 WxOpenService
+ WxOpenService wxOpenService1 = wxOpenMultiServices.getWxOpenService("tenantId1");
+ WxOpenComponentService componentService1 = wxOpenService1.getWxOpenComponentService();
+ // todo ...
+
+ // 应用 2 的 WxOpenService
+ WxOpenService wxOpenService2 = wxOpenMultiServices.getWxOpenService("tenantId2");
+ WxOpenComponentService componentService2 = wxOpenService2.getWxOpenComponentService();
+ // todo ...
+
+ // 应用 3 的 WxOpenService
+ WxOpenService wxOpenService3 = wxOpenMultiServices.getWxOpenService("tenantId3");
+ // 判断是否为空
+ if (wxOpenService3 == null) {
+ // todo wxOpenService3 为空,请先配置 tenantId3 微信开放平台应用参数
+ return;
+ }
+ WxOpenComponentService componentService3 = wxOpenService3.getWxOpenComponentService();
+ // todo ...
+ }
+}
+```
diff --git a/spring-boot-starters/wx-java-open-multi-spring-boot-starter/pom.xml b/spring-boot-starters/wx-java-open-multi-spring-boot-starter/pom.xml
new file mode 100644
index 0000000000..1ad7a5e8e1
--- /dev/null
+++ b/spring-boot-starters/wx-java-open-multi-spring-boot-starter/pom.xml
@@ -0,0 +1,62 @@
+
+
+
+ wx-java-spring-boot-starters
+ com.github.binarywang
+ 4.8.0
+
+ 4.0.0
+
+ wx-java-open-multi-spring-boot-starter
+ WxJava - Spring Boot Starter for WxOpen::支持多账号配置
+ 微信开放平台开发的 Spring Boot Starter::支持多账号配置
+
+
+
+ com.github.binarywang
+ weixin-java-open
+ ${project.version}
+
+
+ redis.clients
+ jedis
+ provided
+
+
+ org.redisson
+ redisson
+ provided
+
+
+ org.springframework.data
+ spring-data-redis
+ provided
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+ ${spring.boot.version}
+
+
+ org.apache.maven.plugins
+ maven-source-plugin
+ 2.2.1
+
+
+ attach-sources
+
+ jar-no-fork
+
+
+
+
+
+
+
+
diff --git a/spring-boot-starters/wx-java-open-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/open/autoconfigure/WxOpenMultiAutoConfiguration.java b/spring-boot-starters/wx-java-open-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/open/autoconfigure/WxOpenMultiAutoConfiguration.java
new file mode 100644
index 0000000000..749130f517
--- /dev/null
+++ b/spring-boot-starters/wx-java-open-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/open/autoconfigure/WxOpenMultiAutoConfiguration.java
@@ -0,0 +1,15 @@
+package com.binarywang.spring.starter.wxjava.open.autoconfigure;
+
+import com.binarywang.spring.starter.wxjava.open.configuration.WxOpenMultiServiceConfiguration;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Import;
+
+/**
+ * 微信开放平台多账号自动配置
+ *
+ * @author Binary Wang
+ */
+@Configuration
+@Import(WxOpenMultiServiceConfiguration.class)
+public class WxOpenMultiAutoConfiguration {
+}
diff --git a/spring-boot-starters/wx-java-open-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/open/configuration/WxOpenMultiServiceConfiguration.java b/spring-boot-starters/wx-java-open-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/open/configuration/WxOpenMultiServiceConfiguration.java
new file mode 100644
index 0000000000..e858185e30
--- /dev/null
+++ b/spring-boot-starters/wx-java-open-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/open/configuration/WxOpenMultiServiceConfiguration.java
@@ -0,0 +1,26 @@
+package com.binarywang.spring.starter.wxjava.open.configuration;
+
+import com.binarywang.spring.starter.wxjava.open.configuration.services.WxOpenInJedisConfiguration;
+import com.binarywang.spring.starter.wxjava.open.configuration.services.WxOpenInMemoryConfiguration;
+import com.binarywang.spring.starter.wxjava.open.configuration.services.WxOpenInRedisTemplateConfiguration;
+import com.binarywang.spring.starter.wxjava.open.configuration.services.WxOpenInRedissonConfiguration;
+import com.binarywang.spring.starter.wxjava.open.properties.WxOpenMultiProperties;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Import;
+
+/**
+ * 微信开放平台相关服务自动注册
+ *
+ * @author Binary Wang
+ */
+@Configuration
+@EnableConfigurationProperties(WxOpenMultiProperties.class)
+@Import({
+ WxOpenInJedisConfiguration.class,
+ WxOpenInMemoryConfiguration.class,
+ WxOpenInRedissonConfiguration.class,
+ WxOpenInRedisTemplateConfiguration.class
+})
+public class WxOpenMultiServiceConfiguration {
+}
diff --git a/spring-boot-starters/wx-java-open-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/open/configuration/services/AbstractWxOpenConfiguration.java b/spring-boot-starters/wx-java-open-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/open/configuration/services/AbstractWxOpenConfiguration.java
new file mode 100644
index 0000000000..0c63878783
--- /dev/null
+++ b/spring-boot-starters/wx-java-open-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/open/configuration/services/AbstractWxOpenConfiguration.java
@@ -0,0 +1,153 @@
+package com.binarywang.spring.starter.wxjava.open.configuration.services;
+
+import com.binarywang.spring.starter.wxjava.open.properties.WxOpenMultiProperties;
+import com.binarywang.spring.starter.wxjava.open.properties.WxOpenSingleProperties;
+import com.binarywang.spring.starter.wxjava.open.service.WxOpenMultiServices;
+import com.binarywang.spring.starter.wxjava.open.service.WxOpenMultiServicesImpl;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import me.chanjar.weixin.common.util.http.apache.ApacheHttpClientBuilder;
+import me.chanjar.weixin.common.util.http.apache.DefaultApacheHttpClientBuilder;
+import me.chanjar.weixin.open.api.WxOpenConfigStorage;
+import me.chanjar.weixin.open.api.WxOpenService;
+import me.chanjar.weixin.open.api.impl.WxOpenInMemoryConfigStorage;
+import me.chanjar.weixin.open.api.impl.WxOpenServiceImpl;
+import org.apache.commons.lang3.StringUtils;
+
+import java.util.Collection;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+/**
+ * WxOpenConfigStorage 抽象配置类
+ *
+ * @author Binary Wang
+ */
+@RequiredArgsConstructor
+@Slf4j
+public abstract class AbstractWxOpenConfiguration {
+
+ protected WxOpenMultiServices wxOpenMultiServices(WxOpenMultiProperties wxOpenMultiProperties) {
+ Map appsMap = wxOpenMultiProperties.getApps();
+ if (appsMap == null || appsMap.isEmpty()) {
+ log.warn("微信开放平台应用参数未配置,通过 WxOpenMultiServices#getWxOpenService(\"tenantId\")获取实例将返回空");
+ return new WxOpenMultiServicesImpl();
+ }
+ /**
+ * 校验 appId 是否唯一,避免使用 redis 缓存 token、ticket 时错乱。
+ */
+ Collection apps = appsMap.values();
+ if (apps.size()> 1) {
+ // 校验 appId 是否唯一
+ String nullAppIdPlaceholder = "__NULL_APP_ID__";
+ boolean multi = apps.stream()
+ // 没有 appId,如果不判断是否为空,这里会报 NPE 异常
+ .collect(Collectors.groupingBy(c -> c.getAppId() == null ? nullAppIdPlaceholder : c.getAppId(), Collectors.counting()))
+ .entrySet().stream().anyMatch(e -> e.getValue()> 1);
+ if (multi) {
+ throw new RuntimeException("请确保微信开放平台配置 appId 的唯一性");
+ }
+ }
+ WxOpenMultiServicesImpl services = new WxOpenMultiServicesImpl();
+
+ Set> entries = appsMap.entrySet();
+ for (Map.Entry entry : entries) {
+ String tenantId = entry.getKey();
+ WxOpenSingleProperties wxOpenSingleProperties = entry.getValue();
+ WxOpenInMemoryConfigStorage storage = this.wxOpenConfigStorage(wxOpenMultiProperties);
+ this.configApp(storage, wxOpenSingleProperties);
+ this.configHttp(storage, wxOpenMultiProperties.getConfigStorage());
+ WxOpenService wxOpenService = this.wxOpenService(storage, wxOpenMultiProperties);
+ services.addWxOpenService(tenantId, wxOpenService);
+ }
+ return services;
+ }
+
+ /**
+ * 配置 WxOpenInMemoryConfigStorage
+ *
+ * @param wxOpenMultiProperties 参数
+ * @return WxOpenInMemoryConfigStorage
+ */
+ protected abstract WxOpenInMemoryConfigStorage wxOpenConfigStorage(WxOpenMultiProperties wxOpenMultiProperties);
+
+ public WxOpenService wxOpenService(WxOpenConfigStorage configStorage, WxOpenMultiProperties wxOpenMultiProperties) {
+ WxOpenService wxOpenService = new WxOpenServiceImpl();
+ wxOpenService.setWxOpenConfigStorage(configStorage);
+ return wxOpenService;
+ }
+
+ private void configApp(WxOpenInMemoryConfigStorage config, WxOpenSingleProperties appProperties) {
+ String appId = appProperties.getAppId();
+ String secret = appProperties.getSecret();
+ String token = appProperties.getToken();
+ String aesKey = appProperties.getAesKey();
+ String apiHostUrl = appProperties.getApiHostUrl();
+ String accessTokenUrl = appProperties.getAccessTokenUrl();
+
+ // appId 和 secret 是必需的
+ if (StringUtils.isBlank(appId)) {
+ throw new IllegalArgumentException("微信开放平台 appId 不能为空");
+ }
+ if (StringUtils.isBlank(secret)) {
+ throw new IllegalArgumentException("微信开放平台 secret 不能为空");
+ }
+
+ config.setComponentAppId(appId);
+ config.setComponentAppSecret(secret);
+ if (StringUtils.isNotBlank(token)) {
+ config.setComponentToken(token);
+ }
+ if (StringUtils.isNotBlank(aesKey)) {
+ config.setComponentAesKey(aesKey);
+ }
+ // 设置URL配置
+ config.setApiHostUrl(StringUtils.trimToNull(apiHostUrl));
+ config.setAccessTokenUrl(StringUtils.trimToNull(accessTokenUrl));
+ }
+
+ private void configHttp(WxOpenInMemoryConfigStorage config, WxOpenMultiProperties.ConfigStorage storage) {
+ String httpProxyHost = storage.getHttpProxyHost();
+ Integer httpProxyPort = storage.getHttpProxyPort();
+ String httpProxyUsername = storage.getHttpProxyUsername();
+ String httpProxyPassword = storage.getHttpProxyPassword();
+ if (StringUtils.isNotBlank(httpProxyHost)) {
+ config.setHttpProxyHost(httpProxyHost);
+ if (httpProxyPort != null) {
+ config.setHttpProxyPort(httpProxyPort);
+ }
+ if (StringUtils.isNotBlank(httpProxyUsername)) {
+ config.setHttpProxyUsername(httpProxyUsername);
+ }
+ if (StringUtils.isNotBlank(httpProxyPassword)) {
+ config.setHttpProxyPassword(httpProxyPassword);
+ }
+ }
+
+ // 设置重试配置
+ int maxRetryTimes = storage.getMaxRetryTimes();
+ if (maxRetryTimes < 0) { + maxRetryTimes = 0; + } + int retrySleepMillis = storage.getRetrySleepMillis(); + if (retrySleepMillis < 0) { + retrySleepMillis = 1000; + } + config.setRetrySleepMillis(retrySleepMillis); + config.setMaxRetryTimes(maxRetryTimes); + + // 设置自定义的HttpClient超时配置 + ApacheHttpClientBuilder clientBuilder = config.getApacheHttpClientBuilder(); + if (clientBuilder == null) { + clientBuilder = DefaultApacheHttpClientBuilder.get(); + } + if (clientBuilder instanceof DefaultApacheHttpClientBuilder) { + DefaultApacheHttpClientBuilder defaultBuilder = (DefaultApacheHttpClientBuilder) clientBuilder; + defaultBuilder.setConnectionTimeout(storage.getConnectionTimeout()); + defaultBuilder.setSoTimeout(storage.getSoTimeout()); + defaultBuilder.setConnectionRequestTimeout(storage.getConnectionRequestTimeout()); + config.setApacheHttpClientBuilder(defaultBuilder); + } + } +} diff --git a/spring-boot-starters/wx-java-open-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/open/configuration/services/WxOpenInJedisConfiguration.java b/spring-boot-starters/wx-java-open-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/open/configuration/services/WxOpenInJedisConfiguration.java new file mode 100644 index 0000000000..bb9577b99b --- /dev/null +++ b/spring-boot-starters/wx-java-open-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/open/configuration/services/WxOpenInJedisConfiguration.java @@ -0,0 +1,78 @@ +package com.binarywang.spring.starter.wxjava.open.configuration.services; + +import com.binarywang.spring.starter.wxjava.open.properties.WxOpenMultiProperties; +import com.binarywang.spring.starter.wxjava.open.properties.WxOpenMultiRedisProperties; +import com.binarywang.spring.starter.wxjava.open.service.WxOpenMultiServices; +import lombok.RequiredArgsConstructor; +import me.chanjar.weixin.open.api.impl.WxOpenInMemoryConfigStorage; +import me.chanjar.weixin.open.api.impl.WxOpenInRedisConfigStorage; +import org.apache.commons.lang3.StringUtils; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import redis.clients.jedis.JedisPool; +import redis.clients.jedis.JedisPoolConfig; + + +/** + * 自动装配基于 jedis 策略配置 + * + * @author Binary Wang + */ +@Configuration +@ConditionalOnProperty( + prefix = WxOpenMultiProperties.PREFIX + ".config-storage", name = "type", havingValue = "JEDIS" +) +@ConditionalOnClass({JedisPool.class, JedisPoolConfig.class}) +@RequiredArgsConstructor +public class WxOpenInJedisConfiguration extends AbstractWxOpenConfiguration { + private final WxOpenMultiProperties wxOpenMultiProperties; + private final ApplicationContext applicationContext; + + @Bean + public WxOpenMultiServices wxOpenMultiServices() { + return this.wxOpenMultiServices(wxOpenMultiProperties); + } + + @Override + protected WxOpenInMemoryConfigStorage wxOpenConfigStorage(WxOpenMultiProperties wxOpenMultiProperties) { + return this.configJedis(wxOpenMultiProperties); + } + + private WxOpenInRedisConfigStorage configJedis(WxOpenMultiProperties wxOpenMultiProperties) { + WxOpenMultiRedisProperties redisProperties = wxOpenMultiProperties.getConfigStorage().getRedis(); + JedisPool jedisPool; + if (redisProperties != null && StringUtils.isNotEmpty(redisProperties.getHost())) { + jedisPool = getJedisPool(wxOpenMultiProperties); + } else { + jedisPool = applicationContext.getBean(JedisPool.class); + } + return new WxOpenInRedisConfigStorage(jedisPool, wxOpenMultiProperties.getConfigStorage().getKeyPrefix()); + } + + private JedisPool getJedisPool(WxOpenMultiProperties wxOpenMultiProperties) { + WxOpenMultiProperties.ConfigStorage storage = wxOpenMultiProperties.getConfigStorage(); + WxOpenMultiRedisProperties redis = storage.getRedis(); + + JedisPoolConfig config = new JedisPoolConfig(); + if (redis.getMaxActive() != null) { + config.setMaxTotal(redis.getMaxActive()); + } + if (redis.getMaxIdle() != null) { + config.setMaxIdle(redis.getMaxIdle()); + } + if (redis.getMaxWaitMillis() != null) { + config.setMaxWaitMillis(redis.getMaxWaitMillis()); + } + if (redis.getMinIdle() != null) { + config.setMinIdle(redis.getMinIdle()); + } + config.setTestOnBorrow(true); + config.setTestWhileIdle(true); + + return new JedisPool(config, redis.getHost(), redis.getPort(), + redis.getTimeout(), redis.getPassword(), redis.getDatabase()); + } +} diff --git a/spring-boot-starters/wx-java-open-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/open/configuration/services/WxOpenInMemoryConfiguration.java b/spring-boot-starters/wx-java-open-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/open/configuration/services/WxOpenInMemoryConfiguration.java new file mode 100644 index 0000000000..f7448a0875 --- /dev/null +++ b/spring-boot-starters/wx-java-open-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/open/configuration/services/WxOpenInMemoryConfiguration.java @@ -0,0 +1,33 @@ +package com.binarywang.spring.starter.wxjava.open.configuration.services; + +import com.binarywang.spring.starter.wxjava.open.properties.WxOpenMultiProperties; +import com.binarywang.spring.starter.wxjava.open.service.WxOpenMultiServices; +import lombok.RequiredArgsConstructor; +import me.chanjar.weixin.open.api.impl.WxOpenInMemoryConfigStorage; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * 自动装配基于内存策略配置 + * + * @author someone + */ +@Configuration +@ConditionalOnProperty( + prefix = WxOpenMultiProperties.PREFIX + ".config-storage", name = "type", havingValue = "MEMORY", matchIfMissing = true +) +@RequiredArgsConstructor +public class WxOpenInMemoryConfiguration extends AbstractWxOpenConfiguration { + private final WxOpenMultiProperties wxOpenMultiProperties; + + @Bean + public WxOpenMultiServices wxOpenMultiServices() { + return this.wxOpenMultiServices(wxOpenMultiProperties); + } + + @Override + protected WxOpenInMemoryConfigStorage wxOpenConfigStorage(WxOpenMultiProperties wxOpenMultiProperties) { + return new WxOpenInMemoryConfigStorage(); + } +} diff --git a/spring-boot-starters/wx-java-open-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/open/configuration/services/WxOpenInRedisTemplateConfiguration.java b/spring-boot-starters/wx-java-open-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/open/configuration/services/WxOpenInRedisTemplateConfiguration.java new file mode 100644 index 0000000000..6208c90fe5 --- /dev/null +++ b/spring-boot-starters/wx-java-open-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/open/configuration/services/WxOpenInRedisTemplateConfiguration.java @@ -0,0 +1,44 @@ +package com.binarywang.spring.starter.wxjava.open.configuration.services; + +import com.binarywang.spring.starter.wxjava.open.properties.WxOpenMultiProperties; +import com.binarywang.spring.starter.wxjava.open.service.WxOpenMultiServices; +import lombok.RequiredArgsConstructor; +import me.chanjar.weixin.open.api.impl.WxOpenInMemoryConfigStorage; +import me.chanjar.weixin.open.api.impl.WxOpenInRedisTemplateConfigStorage; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.core.StringRedisTemplate; + +/** + * 自动装配基于 redis template 策略配置 + * + * @author Binary Wang + */ +@Configuration +@ConditionalOnProperty( + prefix = WxOpenMultiProperties.PREFIX + ".config-storage", name = "type", havingValue = "redistemplate" +) +@ConditionalOnClass(StringRedisTemplate.class) +@RequiredArgsConstructor +public class WxOpenInRedisTemplateConfiguration extends AbstractWxOpenConfiguration { + private final WxOpenMultiProperties wxOpenMultiProperties; + private final ApplicationContext applicationContext; + + @Bean + public WxOpenMultiServices wxOpenMultiServices() { + return this.wxOpenMultiServices(wxOpenMultiProperties); + } + + @Override + protected WxOpenInMemoryConfigStorage wxOpenConfigStorage(WxOpenMultiProperties wxOpenMultiProperties) { + return this.configRedisTemplate(); + } + + private WxOpenInRedisTemplateConfigStorage configRedisTemplate() { + StringRedisTemplate redisTemplate = applicationContext.getBean(StringRedisTemplate.class); + return new WxOpenInRedisTemplateConfigStorage(redisTemplate, wxOpenMultiProperties.getConfigStorage().getKeyPrefix()); + } +} diff --git a/spring-boot-starters/wx-java-open-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/open/configuration/services/WxOpenInRedissonConfiguration.java b/spring-boot-starters/wx-java-open-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/open/configuration/services/WxOpenInRedissonConfiguration.java new file mode 100644 index 0000000000..97569f3baf --- /dev/null +++ b/spring-boot-starters/wx-java-open-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/open/configuration/services/WxOpenInRedissonConfiguration.java @@ -0,0 +1,68 @@ +package com.binarywang.spring.starter.wxjava.open.configuration.services; + +import com.binarywang.spring.starter.wxjava.open.properties.WxOpenMultiProperties; +import com.binarywang.spring.starter.wxjava.open.properties.WxOpenMultiRedisProperties; +import com.binarywang.spring.starter.wxjava.open.service.WxOpenMultiServices; +import lombok.RequiredArgsConstructor; +import me.chanjar.weixin.open.api.impl.WxOpenInMemoryConfigStorage; +import me.chanjar.weixin.open.api.impl.WxOpenInRedissonConfigStorage; +import org.apache.commons.lang3.StringUtils; +import org.redisson.Redisson; +import org.redisson.api.RedissonClient; +import org.redisson.config.Config; +import org.redisson.config.TransportMode; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * 自动装配基于 redisson 策略配置 + * + * @author Binary Wang + */ +@Configuration +@ConditionalOnProperty( + prefix = WxOpenMultiProperties.PREFIX + ".config-storage", name = "type", havingValue = "redisson" +) +@ConditionalOnClass({Redisson.class, RedissonClient.class}) +@RequiredArgsConstructor +public class WxOpenInRedissonConfiguration extends AbstractWxOpenConfiguration { + private final WxOpenMultiProperties wxOpenMultiProperties; + private final ApplicationContext applicationContext; + + @Bean + public WxOpenMultiServices wxOpenMultiServices() { + return this.wxOpenMultiServices(wxOpenMultiProperties); + } + + @Override + protected WxOpenInMemoryConfigStorage wxOpenConfigStorage(WxOpenMultiProperties wxOpenMultiProperties) { + return this.configRedisson(wxOpenMultiProperties); + } + + private WxOpenInRedissonConfigStorage configRedisson(WxOpenMultiProperties wxOpenMultiProperties) { + WxOpenMultiRedisProperties redisProperties = wxOpenMultiProperties.getConfigStorage().getRedis(); + RedissonClient redissonClient; + if (redisProperties != null && StringUtils.isNotEmpty(redisProperties.getHost())) { + redissonClient = getRedissonClient(wxOpenMultiProperties); + } else { + redissonClient = applicationContext.getBean(RedissonClient.class); + } + return new WxOpenInRedissonConfigStorage(redissonClient, wxOpenMultiProperties.getConfigStorage().getKeyPrefix()); + } + + private RedissonClient getRedissonClient(WxOpenMultiProperties wxOpenMultiProperties) { + WxOpenMultiProperties.ConfigStorage storage = wxOpenMultiProperties.getConfigStorage(); + WxOpenMultiRedisProperties redis = storage.getRedis(); + + Config config = new Config(); + config.useSingleServer() + .setAddress("redis://" + redis.getHost() + ":" + redis.getPort()) + .setDatabase(redis.getDatabase()) + .setPassword(redis.getPassword()); + config.setTransportMode(TransportMode.NIO); + return Redisson.create(config); + } +} diff --git a/spring-boot-starters/wx-java-open-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/open/properties/WxOpenMultiProperties.java b/spring-boot-starters/wx-java-open-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/open/properties/WxOpenMultiProperties.java new file mode 100644 index 0000000000..95e5b66712 --- /dev/null +++ b/spring-boot-starters/wx-java-open-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/open/properties/WxOpenMultiProperties.java @@ -0,0 +1,125 @@ +package com.binarywang.spring.starter.wxjava.open.properties; + +import lombok.Data; +import lombok.NoArgsConstructor; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.NestedConfigurationProperty; + +import java.io.Serializable; +import java.util.HashMap; +import java.util.Map; + +/** + * 微信开放平台多账号配置属性. + * + * @author Binary Wang + */ +@Data +@NoArgsConstructor +@ConfigurationProperties(WxOpenMultiProperties.PREFIX) +public class WxOpenMultiProperties implements Serializable { + private static final long serialVersionUID = -5358245184407791011L; + public static final String PREFIX = "wx.open"; + + private Map apps = new HashMap();
+
+ /**
+ * 存储策略
+ */
+ private final ConfigStorage configStorage = new ConfigStorage();
+
+ @Data
+ @NoArgsConstructor
+ public static class ConfigStorage implements Serializable {
+ private static final long serialVersionUID = 4815731027000065434L;
+
+ /**
+ * 存储类型.
+ */
+ private StorageType type = StorageType.memory;
+
+ /**
+ * 指定key前缀.
+ */
+ private String keyPrefix = "wx:open:multi";
+
+ /**
+ * redis连接配置.
+ */
+ @NestedConfigurationProperty
+ private final WxOpenMultiRedisProperties redis = new WxOpenMultiRedisProperties();
+
+ /**
+ * http代理主机.
+ */
+ private String httpProxyHost;
+
+ /**
+ * http代理端口.
+ */
+ private Integer httpProxyPort;
+
+ /**
+ * http代理用户名.
+ */
+ private String httpProxyUsername;
+
+ /**
+ * http代理密码.
+ */
+ private String httpProxyPassword;
+
+ /**
+ * http 请求最大重试次数
+ *
+ * {@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 services = new ConcurrentHashMap();
+
+ @Override
+ public WxOpenService getWxOpenService(String tenantId) {
+ return this.services.get(tenantId);
+ }
+
+ /**
+ * 根据租户 Id,添加一个 WxOpenService 到列表
+ *
+ * @param tenantId 租户 Id
+ * @param wxOpenService WxOpenService 实例
+ */
+ public void addWxOpenService(String tenantId, WxOpenService wxOpenService) {
+ this.services.put(tenantId, wxOpenService);
+ }
+
+ @Override
+ public void removeWxOpenService(String tenantId) {
+ this.services.remove(tenantId);
+ }
+}
diff --git a/spring-boot-starters/wx-java-open-multi-spring-boot-starter/src/main/resources/META-INF/spring.factories b/spring-boot-starters/wx-java-open-multi-spring-boot-starter/src/main/resources/META-INF/spring.factories
new file mode 100644
index 0000000000..a61d0018db
--- /dev/null
+++ b/spring-boot-starters/wx-java-open-multi-spring-boot-starter/src/main/resources/META-INF/spring.factories
@@ -0,0 +1,2 @@
+org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
+com.binarywang.spring.starter.wxjava.open.autoconfigure.WxOpenMultiAutoConfiguration
\ No newline at end of file
diff --git a/spring-boot-starters/wx-java-open-multi-spring-boot-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/spring-boot-starters/wx-java-open-multi-spring-boot-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
new file mode 100644
index 0000000000..ddc66af02c
--- /dev/null
+++ b/spring-boot-starters/wx-java-open-multi-spring-boot-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
@@ -0,0 +1 @@
+com.binarywang.spring.starter.wxjava.open.autoconfigure.WxOpenMultiAutoConfiguration
\ No newline at end of file
diff --git a/spring-boot-starters/wx-java-pay-multi-spring-boot-starter/README.md b/spring-boot-starters/wx-java-pay-multi-spring-boot-starter/README.md
new file mode 100644
index 0000000000..d8d41b7de8
--- /dev/null
+++ b/spring-boot-starters/wx-java-pay-multi-spring-boot-starter/README.md
@@ -0,0 +1,316 @@
+# wx-java-pay-multi-spring-boot-starter
+
+## 快速开始
+
+本starter支持微信支付多公众号关联配置,适用于以下场景:
+- 一个服务商需要为多个公众号提供支付服务
+- 一个系统需要支持多个公众号的支付业务
+- 需要根据不同的appId动态切换支付配置
+
+## 使用说明
+
+### 1. 引入依赖
+
+在项目的 `pom.xml` 中添加以下依赖:
+
+```xml
+
+ com.github.binarywang
+ wx-java-pay-multi-spring-boot-starter
+ ${version}
+
+```
+
+### 2. 添加配置
+
+在 `application.yml` 或 `application.properties` 中配置多个公众号的支付信息。
+
+#### 配置示例(application.yml)
+
+##### V2版本配置
+```yml
+wx:
+ pay:
+ configs:
+ # 配置1 - 可以使用appId作为key
+ wx1234567890abcdef:
+ appId: wx1234567890abcdef
+ mchId: 1234567890
+ mchKey: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+ keyPath: classpath:cert/app1/apiclient_cert.p12
+ notifyUrl: https://example.com/pay/notify
+ # 配置2 - 也可以使用自定义标识作为key
+ config2:
+ appId: wx9876543210fedcba
+ mchId: 9876543210
+ mchKey: yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
+ keyPath: classpath:cert/app2/apiclient_cert.p12
+ notifyUrl: https://example.com/pay/notify
+```
+
+##### V3版本配置
+```yml
+wx:
+ pay:
+ configs:
+ # 公众号1配置
+ wx1234567890abcdef:
+ appId: wx1234567890abcdef
+ mchId: 1234567890
+ apiV3Key: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+ certSerialNo: 62C6CEAA360BCxxxxxxxxxxxxxxx
+ privateKeyPath: classpath:cert/app1/apiclient_key.pem
+ privateCertPath: classpath:cert/app1/apiclient_cert.pem
+ notifyUrl: https://example.com/pay/notify
+ # 公众号2配置
+ wx9876543210fedcba:
+ appId: wx9876543210fedcba
+ mchId: 9876543210
+ apiV3Key: yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
+ certSerialNo: 73D7DFBB471CDxxxxxxxxxxxxxxx
+ privateKeyPath: classpath:cert/app2/apiclient_key.pem
+ privateCertPath: classpath:cert/app2/apiclient_cert.pem
+ notifyUrl: https://example.com/pay/notify
+```
+
+##### V3服务商版本配置
+```yml
+wx:
+ pay:
+ configs:
+ # 服务商为公众号1提供服务
+ config1:
+ appId: wxe97b2x9c2b3d # 服务商appId
+ mchId: 16486610 # 服务商商户号
+ subAppId: wx118cexxe3c07679 # 子商户公众号appId
+ subMchId: 16496705 # 子商户号
+ apiV3Key: Dc1DBwSc094jAKDGR5aqqb7PTHr
+ privateKeyPath: classpath:cert/apiclient_key.pem
+ privateCertPath: classpath:cert/apiclient_cert.pem
+ # 服务商为公众号2提供服务
+ config2:
+ appId: wxe97b2x9c2b3d # 服务商appId(可以相同)
+ mchId: 16486610 # 服务商商户号(可以相同)
+ subAppId: wx228dexxf4d18890 # 子商户公众号appId(不同)
+ subMchId: 16496706 # 子商户号(不同)
+ apiV3Key: Dc1DBwSc094jAKDGR5aqqb7PTHr
+ privateKeyPath: classpath:cert/apiclient_key.pem
+ privateCertPath: classpath:cert/apiclient_cert.pem
+```
+
+#### 配置示例(application.properties)
+
+```properties
+# 公众号1配置
+wx.pay.configs.wx1234567890abcdef.app-id=wx1234567890abcdef
+wx.pay.configs.wx1234567890abcdef.mch-id=1234567890
+wx.pay.configs.wx1234567890abcdef.apiv3-key=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+wx.pay.configs.wx1234567890abcdef.cert-serial-no=62C6CEAA360BCxxxxxxxxxxxxxxx
+wx.pay.configs.wx1234567890abcdef.private-key-path=classpath:cert/app1/apiclient_key.pem
+wx.pay.configs.wx1234567890abcdef.private-cert-path=classpath:cert/app1/apiclient_cert.pem
+wx.pay.configs.wx1234567890abcdef.notify-url=https://example.com/pay/notify
+
+# 公众号2配置
+wx.pay.configs.wx9876543210fedcba.app-id=wx9876543210fedcba
+wx.pay.configs.wx9876543210fedcba.mch-id=9876543210
+wx.pay.configs.wx9876543210fedcba.apiv3-key=yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
+wx.pay.configs.wx9876543210fedcba.cert-serial-no=73D7DFBB471CDxxxxxxxxxxxxxxx
+wx.pay.configs.wx9876543210fedcba.private-key-path=classpath:cert/app2/apiclient_key.pem
+wx.pay.configs.wx9876543210fedcba.private-cert-path=classpath:cert/app2/apiclient_cert.pem
+wx.pay.configs.wx9876543210fedcba.notify-url=https://example.com/pay/notify
+```
+
+### 3. 使用示例
+
+自动注入的类型:`WxPayMultiServices`
+
+```java
+import com.binarywang.spring.starter.wxjava.pay.service.WxPayMultiServices;
+import com.github.binarywang.wxpay.bean.request.WxPayUnifiedOrderV3Request;
+import com.github.binarywang.wxpay.bean.result.WxPayOrderQueryV3Result;
+import com.github.binarywang.wxpay.bean.result.WxPayUnifiedOrderV3Result;
+import com.github.binarywang.wxpay.bean.result.enums.TradeTypeEnum;
+import com.github.binarywang.wxpay.service.WxPayService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+@Service
+public class PayService {
+ @Autowired
+ private WxPayMultiServices wxPayMultiServices;
+
+ /**
+ * 为不同的公众号创建支付订单
+ *
+ * @param configKey 配置标识(即 wx.pay.configs.<configKey> 中的 key,可以是 appId 或自定义标识)
+ */
+ public void createOrder(String configKey, String openId, Integer totalFee, String body) throws Exception {
+ // 根据配置标识获取对应的WxPayService
+ WxPayService wxPayService = wxPayMultiServices.getWxPayService(configKey);
+
+ if (wxPayService == null) {
+ throw new IllegalArgumentException("未找到配置标识对应的微信支付配置: " + configKey);
+ }
+
+ // 使用WxPayService进行支付操作
+ 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());
+
+ // V3统一下单
+ WxPayUnifiedOrderV3Result.JsapiResult result =
+ wxPayService.createOrderV3(TradeTypeEnum.JSAPI, request);
+
+ // 返回给前端用于调起支付
+ // ...
+ }
+
+ /**
+ * 服务商模式示例
+ */
+ public void serviceProviderExample(String configKey) throws Exception {
+ // 使用配置标识获取WxPayService
+ WxPayService wxPayService = wxPayMultiServices.getWxPayService(configKey);
+
+ if (wxPayService == null) {
+ throw new IllegalArgumentException("未找到配置: " + configKey);
+ }
+
+ // 获取子商户的配置信息
+ String subAppId = wxPayService.getConfig().getSubAppId();
+ String subMchId = wxPayService.getConfig().getSubMchId();
+
+ // 进行支付操作
+ // ...
+ }
+
+ /**
+ * 查询订单示例
+ *
+ * @param configKey 配置标识(即 wx.pay.configs.<configKey> 中的 key)
+ */
+ public void queryOrder(String configKey, String outTradeNo) throws Exception {
+ WxPayService wxPayService = wxPayMultiServices.getWxPayService(configKey);
+
+ if (wxPayService == null) {
+ throw new IllegalArgumentException("未找到配置标识对应的微信支付配置: " + configKey);
+ }
+
+ // 查询订单
+ WxPayOrderQueryV3Result result = wxPayService.queryOrderV3(null, outTradeNo);
+ // 处理查询结果
+ // ...
+ }
+
+ private String generateOutTradeNo() {
+ // 生成商户订单号
+ return "ORDER_" + System.currentTimeMillis();
+ }
+}
+```
+
+### 4. 配置说明
+
+#### 必填配置项
+
+| 配置项 | 说明 | 示例 |
+|--------|------|------|
+| appId | 公众号或小程序的appId | wx1234567890abcdef |
+| mchId | 商户号 | 1234567890 |
+
+#### V2版本配置项
+
+| 配置项 | 说明 | 是否必填 |
+|--------|------|----------|
+| mchKey | 商户密钥 | 是(V2) |
+| keyPath | p12证书文件路径 | 部分接口需要 |
+
+#### V3版本配置项
+
+| 配置项 | 说明 | 是否必填 |
+|--------|------|----------|
+| apiV3Key | API V3密钥 | 是(V3) |
+| certSerialNo | 证书序列号 | 是(V3) |
+| privateKeyPath | apiclient_key.pem路径 | 是(V3) |
+| privateCertPath | apiclient_cert.pem路径 | 是(V3) |
+
+#### 服务商模式配置项
+
+| 配置项 | 说明 | 是否必填 |
+|--------|------|----------|
+| subAppId | 子商户公众号appId | 服务商模式必填 |
+| subMchId | 子商户号 | 服务商模式必填 |
+
+#### 可选配置项
+
+| 配置项 | 说明 | 默认值 |
+|--------|------|--------|
+| notifyUrl | 支付结果通知URL | 无 |
+| refundNotifyUrl | 退款结果通知URL | 无 |
+| serviceId | 微信支付分serviceId | 无 |
+| payScoreNotifyUrl | 支付分回调地址 | 无 |
+| payScorePermissionNotifyUrl | 支付分授权回调地址 | 无 |
+| useSandboxEnv | 是否使用沙箱环境 | false |
+| apiHostUrl | 自定义API主机地址 | https://api.mch.weixin.qq.com |
+| strictlyNeedWechatPaySerial | 是否所有V3请求都添加序列号头 | false |
+| fullPublicKeyModel | 是否完全使用公钥模式 | false |
+| publicKeyId | 公钥ID | 无 |
+| publicKeyPath | 公钥文件路径 | 无 |
+
+## 常见问题
+
+### 1. 如何选择配置的key?
+
+配置的key(即 `wx.pay.configs.` 中的 `` 部分)可以自由选择:
+- 可以使用appId作为key(如 `wx.pay.configs.wx1234567890abcdef`),这样调用 `getWxPayService("wx1234567890abcdef")` 时就像直接用 appId 获取服务
+- 可以使用自定义标识(如 `wx.pay.configs.config1`),调用时使用 `getWxPayService("config1")`
+
+**注意**:`getWxPayService(configKey)` 方法的参数是配置文件中定义的 key,而不是 appId。只有当你使用 appId 作为配置 key 时,才能直接传入 appId。
+
+### 2. V2和V3配置可以混用吗?
+
+可以。不同的配置可以使用不同的版本,例如:
+```yml
+wx:
+ pay:
+ configs:
+ app1: # V2配置
+ appId: wx111
+ mchId: 111
+ mchKey: xxx
+ app2: # V3配置
+ appId: wx222
+ mchId: 222
+ apiV3Key: yyy
+ privateKeyPath: xxx
+```
+
+### 3. 证书文件如何放置?
+
+证书文件可以放在以下位置:
+- `src/main/resources` 目录下,使用 `classpath:` 前缀
+- 服务器绝对路径,直接填写完整路径
+- 建议为不同配置使用不同的目录组织证书
+
+### 4. 服务商模式如何配置?
+
+服务商模式需要同时配置服务商信息和子商户信息:
+- `appId` 和 `mchId` 填写服务商的信息
+- `subAppId` 和 `subMchId` 填写子商户的信息
+
+## 注意事项
+
+1. **配置安全**:生产环境中的密钥、证书等敏感信息,建议使用配置中心或环境变量管理
+2. **证书管理**:不同公众号的证书文件要分开存放,避免混淆
+3. **懒加载**:WxPayService 实例采用懒加载策略,只有在首次调用时才会创建
+4. **线程安全**:WxPayMultiServices 的实现是线程安全的
+5. **配置更新**:如需动态更新配置,可调用 `removeWxPayService(configKey)` 方法移除缓存的实例
+
+## 更多信息
+
+- [WxJava 项目首页](https://github.com/Wechat-Group/WxJava)
+- [微信支付官方文档](https://pay.weixin.qq.com/wiki/doc/api/)
+- [微信支付V3接口文档](https://pay.weixin.qq.com/wiki/doc/apiv3/index.shtml)
diff --git a/spring-boot-starters/wx-java-pay-multi-spring-boot-starter/pom.xml b/spring-boot-starters/wx-java-pay-multi-spring-boot-starter/pom.xml
new file mode 100644
index 0000000000..a5c0b842cb
--- /dev/null
+++ b/spring-boot-starters/wx-java-pay-multi-spring-boot-starter/pom.xml
@@ -0,0 +1,53 @@
+
+
+
+ wx-java-spring-boot-starters
+ com.github.binarywang
+ 4.8.0
+
+ 4.0.0
+
+ wx-java-pay-multi-spring-boot-starter
+ WxJava - Spring Boot Starter for Pay::支持多公众号关联配置
+ 微信支付开发的 Spring Boot Starter::支持多公众号关联配置
+
+
+
+ com.github.binarywang
+ weixin-java-pay
+ ${project.version}
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ ${spring.boot.version}
+ test
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+ ${spring.boot.version}
+
+
+ org.apache.maven.plugins
+ maven-source-plugin
+ 2.2.1
+
+
+ attach-sources
+
+ jar-no-fork
+
+
+
+
+
+
+
+
diff --git a/spring-boot-starters/wx-java-pay-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/pay/config/WxPayMultiAutoConfiguration.java b/spring-boot-starters/wx-java-pay-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/pay/config/WxPayMultiAutoConfiguration.java
new file mode 100644
index 0000000000..08ddafbf9c
--- /dev/null
+++ b/spring-boot-starters/wx-java-pay-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/pay/config/WxPayMultiAutoConfiguration.java
@@ -0,0 +1,38 @@
+package com.binarywang.spring.starter.wxjava.pay.config;
+
+import com.binarywang.spring.starter.wxjava.pay.properties.WxPayMultiProperties;
+import com.binarywang.spring.starter.wxjava.pay.service.WxPayMultiServices;
+import com.binarywang.spring.starter.wxjava.pay.service.WxPayMultiServicesImpl;
+import com.github.binarywang.wxpay.service.WxPayService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * 微信支付多公众号关联自动配置.
+ *
+ * @author Binary Wang
+ */
+@Slf4j
+@Configuration
+@EnableConfigurationProperties(WxPayMultiProperties.class)
+@ConditionalOnClass(WxPayService.class)
+@ConditionalOnProperty(prefix = WxPayMultiProperties.PREFIX, value = "enabled", matchIfMissing = true)
+public class WxPayMultiAutoConfiguration {
+
+ /**
+ * 构造微信支付多服务管理对象.
+ *
+ * @param wxPayMultiProperties 多配置属性
+ * @return 微信支付多服务管理对象
+ */
+ @Bean
+ @ConditionalOnMissingBean(WxPayMultiServices.class)
+ public WxPayMultiServices wxPayMultiServices(WxPayMultiProperties wxPayMultiProperties) {
+ return new WxPayMultiServicesImpl(wxPayMultiProperties);
+ }
+}
diff --git a/spring-boot-starters/wx-java-pay-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/pay/properties/WxPayMultiProperties.java b/spring-boot-starters/wx-java-pay-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/pay/properties/WxPayMultiProperties.java
new file mode 100644
index 0000000000..8d1180b0e4
--- /dev/null
+++ b/spring-boot-starters/wx-java-pay-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/pay/properties/WxPayMultiProperties.java
@@ -0,0 +1,27 @@
+package com.binarywang.spring.starter.wxjava.pay.properties;
+
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+import java.io.Serializable;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 微信支付多公众号关联配置属性类.
+ *
+ * @author Binary Wang
+ */
+@Data
+@NoArgsConstructor
+@ConfigurationProperties(WxPayMultiProperties.PREFIX)
+public class WxPayMultiProperties implements Serializable {
+ private static final long serialVersionUID = -8015955705346835955L;
+ public static final String PREFIX = "wx.pay";
+
+ /**
+ * 多个公众号的配置信息,key 可以是 appId 或自定义的标识.
+ */
+ private Map configs = new HashMap();
+}
diff --git a/spring-boot-starters/wx-java-pay-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/pay/properties/WxPaySingleProperties.java b/spring-boot-starters/wx-java-pay-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/pay/properties/WxPaySingleProperties.java
new file mode 100644
index 0000000000..a5cda55fb0
--- /dev/null
+++ b/spring-boot-starters/wx-java-pay-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/pay/properties/WxPaySingleProperties.java
@@ -0,0 +1,124 @@
+package com.binarywang.spring.starter.wxjava.pay.properties;
+
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+
+/**
+ * 微信支付单个公众号配置属性类.
+ *
+ * @author Binary Wang
+ */
+@Data
+@NoArgsConstructor
+public class WxPaySingleProperties implements Serializable {
+ private static final long serialVersionUID = 3978986361098922525L;
+
+ /**
+ * 设置微信公众号或者小程序等的appid.
+ */
+ private String appId;
+
+ /**
+ * 微信支付商户号.
+ */
+ private String mchId;
+
+ /**
+ * 微信支付商户密钥.
+ */
+ private String mchKey;
+
+ /**
+ * 服务商模式下的子商户公众账号ID,普通模式请不要配置.
+ */
+ private String subAppId;
+
+ /**
+ * 服务商模式下的子商户号,普通模式请不要配置.
+ */
+ private String subMchId;
+
+ /**
+ * apiclient_cert.p12文件的绝对路径,或者如果放在项目中,请以classpath:开头指定.
+ */
+ private String keyPath;
+
+ /**
+ * 微信支付分serviceId.
+ */
+ private String serviceId;
+
+ /**
+ * 证书序列号.
+ */
+ private String certSerialNo;
+
+ /**
+ * apiV3秘钥.
+ */
+ private String apiv3Key;
+
+ /**
+ * 微信支付异步回调地址,通知url必须为直接可访问的url,不能携带参数.
+ */
+ private String notifyUrl;
+
+ /**
+ * 退款结果异步回调地址,通知url必须为直接可访问的url,不能携带参数.
+ */
+ private String refundNotifyUrl;
+
+ /**
+ * 微信支付分回调地址.
+ */
+ private String payScoreNotifyUrl;
+
+ /**
+ * 微信支付分授权回调地址.
+ */
+ private String payScorePermissionNotifyUrl;
+
+ /**
+ * apiv3 商户apiclient_key.pem.
+ */
+ private String privateKeyPath;
+
+ /**
+ * apiv3 商户apiclient_cert.pem.
+ */
+ private String privateCertPath;
+
+ /**
+ * 公钥ID.
+ */
+ private String publicKeyId;
+
+ /**
+ * pub_key.pem证书文件的绝对路径或者以classpath:开头的类路径.
+ */
+ private String publicKeyPath;
+
+ /**
+ * 微信支付是否使用仿真测试环境.
+ * 默认不使用.
+ */
+ private boolean useSandboxEnv = false;
+
+ /**
+ * 自定义API主机地址,用于替换默认的 https://api.mch.weixin.qq.com.
+ * 例如:http://proxy.company.com:8080
+ */
+ private String apiHostUrl;
+
+ /**
+ * 是否将全部v3接口的请求都添加Wechatpay-Serial请求头,默认不添加.
+ */
+ private boolean strictlyNeedWechatPaySerial = false;
+
+ /**
+ * 是否完全使用公钥模式(用以微信从平台证书到公钥的灰度切换),默认不使用.
+ */
+ private boolean fullPublicKeyModel = false;
+}
diff --git a/spring-boot-starters/wx-java-pay-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/pay/service/WxPayMultiServices.java b/spring-boot-starters/wx-java-pay-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/pay/service/WxPayMultiServices.java
new file mode 100644
index 0000000000..3e0b7a999f
--- /dev/null
+++ b/spring-boot-starters/wx-java-pay-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/pay/service/WxPayMultiServices.java
@@ -0,0 +1,33 @@
+package com.binarywang.spring.starter.wxjava.pay.service;
+
+import com.github.binarywang.wxpay.service.WxPayService;
+
+/**
+ * 微信支付 {@link WxPayService} 所有实例存放类.
+ *
+ * @author Binary Wang
+ */
+public interface WxPayMultiServices {
+ /**
+ * 通过配置标识获取 WxPayService.
+ *
+ * 注意: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 services = new ConcurrentHashMap();
+ private final WxPayMultiProperties wxPayMultiProperties;
+
+ public WxPayMultiServicesImpl(WxPayMultiProperties wxPayMultiProperties) {
+ this.wxPayMultiProperties = wxPayMultiProperties;
+ }
+
+ @Override
+ public WxPayService getWxPayService(String configKey) {
+ if (StringUtils.isBlank(configKey)) {
+ log.warn("配置标识为空,无法获取WxPayService");
+ return null;
+ }
+
+ // 使用 computeIfAbsent 实现线程安全的懒加载,避免使用 synchronized(this) 带来的性能问题
+ return services.computeIfAbsent(configKey, key -> {
+ WxPaySingleProperties properties = wxPayMultiProperties.getConfigs().get(key);
+ if (properties == null) {
+ log.warn("未找到配置标识为[{}]的微信支付配置", key);
+ return null;
+ }
+ return this.buildWxPayService(properties);
+ });
+ }
+
+ @Override
+ public void removeWxPayService(String configKey) {
+ if (StringUtils.isBlank(configKey)) {
+ log.warn("配置标识为空,无法移除WxPayService");
+ return;
+ }
+ services.remove(configKey);
+ }
+
+ /**
+ * 根据配置构建 WxPayService.
+ *
+ * @param properties 单个配置属性
+ * @return WxPayService
+ */
+ private WxPayService buildWxPayService(WxPaySingleProperties properties) {
+ WxPayServiceImpl wxPayService = new WxPayServiceImpl();
+ WxPayConfig payConfig = new WxPayConfig();
+
+ payConfig.setAppId(StringUtils.trimToNull(properties.getAppId()));
+ payConfig.setMchId(StringUtils.trimToNull(properties.getMchId()));
+ payConfig.setMchKey(StringUtils.trimToNull(properties.getMchKey()));
+ payConfig.setSubAppId(StringUtils.trimToNull(properties.getSubAppId()));
+ payConfig.setSubMchId(StringUtils.trimToNull(properties.getSubMchId()));
+ payConfig.setKeyPath(StringUtils.trimToNull(properties.getKeyPath()));
+ payConfig.setUseSandboxEnv(properties.isUseSandboxEnv());
+ payConfig.setNotifyUrl(StringUtils.trimToNull(properties.getNotifyUrl()));
+ payConfig.setRefundNotifyUrl(StringUtils.trimToNull(properties.getRefundNotifyUrl()));
+
+ // 以下是apiv3以及支付分相关
+ payConfig.setServiceId(StringUtils.trimToNull(properties.getServiceId()));
+ payConfig.setPayScoreNotifyUrl(StringUtils.trimToNull(properties.getPayScoreNotifyUrl()));
+ payConfig.setPayScorePermissionNotifyUrl(StringUtils.trimToNull(properties.getPayScorePermissionNotifyUrl()));
+ payConfig.setPrivateKeyPath(StringUtils.trimToNull(properties.getPrivateKeyPath()));
+ payConfig.setPrivateCertPath(StringUtils.trimToNull(properties.getPrivateCertPath()));
+ payConfig.setCertSerialNo(StringUtils.trimToNull(properties.getCertSerialNo()));
+ payConfig.setApiV3Key(StringUtils.trimToNull(properties.getApiv3Key()));
+ payConfig.setPublicKeyId(StringUtils.trimToNull(properties.getPublicKeyId()));
+ payConfig.setPublicKeyPath(StringUtils.trimToNull(properties.getPublicKeyPath()));
+ payConfig.setApiHostUrl(StringUtils.trimToNull(properties.getApiHostUrl()));
+ payConfig.setStrictlyNeedWechatPaySerial(properties.isStrictlyNeedWechatPaySerial());
+ payConfig.setFullPublicKeyModel(properties.isFullPublicKeyModel());
+
+ wxPayService.setConfig(payConfig);
+ return wxPayService;
+ }
+}
diff --git a/spring-boot-starters/wx-java-pay-multi-spring-boot-starter/src/main/resources/META-INF/spring.factories b/spring-boot-starters/wx-java-pay-multi-spring-boot-starter/src/main/resources/META-INF/spring.factories
new file mode 100644
index 0000000000..d257d37276
--- /dev/null
+++ b/spring-boot-starters/wx-java-pay-multi-spring-boot-starter/src/main/resources/META-INF/spring.factories
@@ -0,0 +1,2 @@
+org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
+com.binarywang.spring.starter.wxjava.pay.config.WxPayMultiAutoConfiguration
diff --git a/spring-boot-starters/wx-java-pay-multi-spring-boot-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/spring-boot-starters/wx-java-pay-multi-spring-boot-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
new file mode 100644
index 0000000000..39e3342f4a
--- /dev/null
+++ b/spring-boot-starters/wx-java-pay-multi-spring-boot-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
@@ -0,0 +1,2 @@
+com.binarywang.spring.starter.wxjava.pay.config.WxPayMultiAutoConfiguration
+
diff --git a/spring-boot-starters/wx-java-pay-multi-spring-boot-starter/src/test/java/com/binarywang/spring/starter/wxjava/pay/WxPayMultiServicesTest.java b/spring-boot-starters/wx-java-pay-multi-spring-boot-starter/src/test/java/com/binarywang/spring/starter/wxjava/pay/WxPayMultiServicesTest.java
new file mode 100644
index 0000000000..25a091da02
--- /dev/null
+++ b/spring-boot-starters/wx-java-pay-multi-spring-boot-starter/src/test/java/com/binarywang/spring/starter/wxjava/pay/WxPayMultiServicesTest.java
@@ -0,0 +1,104 @@
+package com.binarywang.spring.starter.wxjava.pay;
+
+import com.binarywang.spring.starter.wxjava.pay.config.WxPayMultiAutoConfiguration;
+import com.binarywang.spring.starter.wxjava.pay.properties.WxPayMultiProperties;
+import com.binarywang.spring.starter.wxjava.pay.properties.WxPaySingleProperties;
+import com.binarywang.spring.starter.wxjava.pay.service.WxPayMultiServices;
+import com.github.binarywang.wxpay.service.WxPayService;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.TestPropertySource;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+/**
+ * 微信支付多公众号关联配置测试.
+ *
+ * @author Binary Wang
+ */
+@SpringBootTest(classes = {WxPayMultiAutoConfiguration.class, WxPayMultiServicesTest.TestApplication.class})
+@TestPropertySource(properties = {
+ "wx.pay.configs.app1.app-id=wx1111111111111111",
+ "wx.pay.configs.app1.mch-id=1111111111",
+ "wx.pay.configs.app1.mch-key=11111111111111111111111111111111",
+ "wx.pay.configs.app1.notify-url=https://example.com/pay/notify",
+ "wx.pay.configs.app2.app-id=wx2222222222222222",
+ "wx.pay.configs.app2.mch-id=2222222222",
+ "wx.pay.configs.app2.apiv3-key=22222222222222222222222222222222",
+ "wx.pay.configs.app2.cert-serial-no=2222222222222222",
+ "wx.pay.configs.app2.private-key-path=classpath:cert/apiclient_key.pem",
+ "wx.pay.configs.app2.private-cert-path=classpath:cert/apiclient_cert.pem"
+})
+public class WxPayMultiServicesTest {
+
+ @Autowired
+ private WxPayMultiServices wxPayMultiServices;
+
+ @Autowired
+ private WxPayMultiProperties wxPayMultiProperties;
+
+ @Test
+ public void testConfiguration() {
+ assertNotNull(wxPayMultiServices, "WxPayMultiServices should be autowired");
+ assertNotNull(wxPayMultiProperties, "WxPayMultiProperties should be autowired");
+
+ // 验证配置正确加载
+ assertEquals(2, wxPayMultiProperties.getConfigs().size(), "Should have 2 configurations");
+
+ WxPaySingleProperties app1Config = wxPayMultiProperties.getConfigs().get("app1");
+ assertNotNull(app1Config, "app1 configuration should exist");
+ assertEquals("wx1111111111111111", app1Config.getAppId());
+ assertEquals("1111111111", app1Config.getMchId());
+ assertEquals("11111111111111111111111111111111", app1Config.getMchKey());
+
+ WxPaySingleProperties app2Config = wxPayMultiProperties.getConfigs().get("app2");
+ assertNotNull(app2Config, "app2 configuration should exist");
+ assertEquals("wx2222222222222222", app2Config.getAppId());
+ assertEquals("2222222222", app2Config.getMchId());
+ assertEquals("22222222222222222222222222222222", app2Config.getApiv3Key());
+ }
+
+ @Test
+ public void testGetWxPayService() {
+ WxPayService app1Service = wxPayMultiServices.getWxPayService("app1");
+ assertNotNull(app1Service, "Should get WxPayService for app1");
+ assertEquals("wx1111111111111111", app1Service.getConfig().getAppId());
+ assertEquals("1111111111", app1Service.getConfig().getMchId());
+
+ WxPayService app2Service = wxPayMultiServices.getWxPayService("app2");
+ assertNotNull(app2Service, "Should get WxPayService for app2");
+ assertEquals("wx2222222222222222", app2Service.getConfig().getAppId());
+ assertEquals("2222222222", app2Service.getConfig().getMchId());
+
+ // 测试相同key返回相同实例
+ WxPayService app1ServiceAgain = wxPayMultiServices.getWxPayService("app1");
+ assertSame(app1Service, app1ServiceAgain, "Should return the same instance for the same key");
+ }
+
+ @Test
+ public void testGetWxPayServiceWithInvalidKey() {
+ WxPayService service = wxPayMultiServices.getWxPayService("nonexistent");
+ assertNull(service, "Should return null for non-existent key");
+ }
+
+ @Test
+ public void testRemoveWxPayService() {
+ // 首先获取一个服务实例
+ WxPayService app1Service = wxPayMultiServices.getWxPayService("app1");
+ assertNotNull(app1Service, "Should get WxPayService for app1");
+
+ // 移除服务
+ wxPayMultiServices.removeWxPayService("app1");
+
+ // 再次获取时应该创建新实例
+ WxPayService app1ServiceNew = wxPayMultiServices.getWxPayService("app1");
+ assertNotNull(app1ServiceNew, "Should get new WxPayService for app1");
+ assertNotSame(app1Service, app1ServiceNew, "Should return a new instance after removal");
+ }
+
+ @SpringBootApplication
+ static class TestApplication {
+ }
+}
diff --git a/spring-boot-starters/wx-java-pay-multi-spring-boot-starter/src/test/java/com/binarywang/spring/starter/wxjava/pay/example/WxPayMultiExample.java b/spring-boot-starters/wx-java-pay-multi-spring-boot-starter/src/test/java/com/binarywang/spring/starter/wxjava/pay/example/WxPayMultiExample.java
new file mode 100644
index 0000000000..48ae32d5b4
--- /dev/null
+++ b/spring-boot-starters/wx-java-pay-multi-spring-boot-starter/src/test/java/com/binarywang/spring/starter/wxjava/pay/example/WxPayMultiExample.java
@@ -0,0 +1,249 @@
+package com.binarywang.spring.starter.wxjava.pay.example;
+
+import com.binarywang.spring.starter.wxjava.pay.service.WxPayMultiServices;
+import com.github.binarywang.wxpay.bean.request.WxPayRefundV3Request;
+import com.github.binarywang.wxpay.bean.request.WxPayUnifiedOrderV3Request;
+import com.github.binarywang.wxpay.bean.result.WxPayOrderQueryV3Result;
+import com.github.binarywang.wxpay.bean.result.WxPayRefundV3Result;
+import com.github.binarywang.wxpay.bean.result.WxPayUnifiedOrderV3Result;
+import com.github.binarywang.wxpay.bean.result.enums.TradeTypeEnum;
+import com.github.binarywang.wxpay.service.WxPayService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+/**
+ * 微信支付多公众号关联使用示例.
+ *
+ * 本示例展示了如何使用 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/src/main/java/com/binarywang/spring/starter/wxjava/pay/config/WxPayAutoConfiguration.java b/spring-boot-starters/wx-java-pay-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/pay/config/WxPayAutoConfiguration.java
index 5a794de7e8..758fd929a1 100644
--- a/spring-boot-starters/wx-java-pay-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/pay/config/WxPayAutoConfiguration.java
+++ b/spring-boot-starters/wx-java-pay-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/pay/config/WxPayAutoConfiguration.java
@@ -51,6 +51,7 @@ public WxPayService wxPayService() {
payConfig.setKeyPath(StringUtils.trimToNull(this.properties.getKeyPath()));
payConfig.setUseSandboxEnv(this.properties.isUseSandboxEnv());
payConfig.setNotifyUrl(StringUtils.trimToNull(this.properties.getNotifyUrl()));
+ payConfig.setRefundNotifyUrl(StringUtils.trimToNull(this.properties.getRefundNotifyUrl()));
//以下是apiv3以及支付分相关
payConfig.setServiceId(StringUtils.trimToNull(this.properties.getServiceId()));
payConfig.setPayScoreNotifyUrl(StringUtils.trimToNull(this.properties.getPayScoreNotifyUrl()));
diff --git a/spring-boot-starters/wx-java-pay-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/pay/properties/WxPayProperties.java b/spring-boot-starters/wx-java-pay-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/pay/properties/WxPayProperties.java
index 8212e3b013..25f7d7c02e 100644
--- a/spring-boot-starters/wx-java-pay-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/pay/properties/WxPayProperties.java
+++ b/spring-boot-starters/wx-java-pay-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/pay/properties/WxPayProperties.java
@@ -64,6 +64,11 @@ public class WxPayProperties {
*/
private String notifyUrl;
+ /**
+ * 退款结果异步回调地址,通知url必须为直接可访问的url,不能携带参数
+ */
+ private String refundNotifyUrl;
+
/**
* 微信支付分回调地址
*/
diff --git a/spring-boot-starters/wx-java-qidian-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/qidian/enums/HttpClientType.java b/spring-boot-starters/wx-java-qidian-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/qidian/enums/HttpClientType.java
index 1a927211cc..04589a911b 100644
--- a/spring-boot-starters/wx-java-qidian-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/qidian/enums/HttpClientType.java
+++ b/spring-boot-starters/wx-java-qidian-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/qidian/enums/HttpClientType.java
@@ -19,4 +19,8 @@ public enum HttpClientType {
* JoddHttp.
*/
JoddHttp,
+ /**
+ * HttpComponents.
+ */
+ HttpComponents,
}
diff --git a/spring-boot-starters/wx-java-qidian-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/qidian/properties/WxQidianProperties.java b/spring-boot-starters/wx-java-qidian-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/qidian/properties/WxQidianProperties.java
index ddecefb7e2..bec5dfcce0 100644
--- a/spring-boot-starters/wx-java-qidian-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/qidian/properties/WxQidianProperties.java
+++ b/spring-boot-starters/wx-java-qidian-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/qidian/properties/WxQidianProperties.java
@@ -72,7 +72,7 @@ public static class ConfigStorage implements Serializable {
/**
* http客户端类型.
*/
- private HttpClientType httpClientType = HttpClientType.HttpClient;
+ private HttpClientType httpClientType = HttpClientType.HttpComponents;
/**
* http代理主机.
diff --git a/weixin-java-channel/pom.xml b/weixin-java-channel/pom.xml
index 13dd26fa98..28b3e2ed6c 100644
--- a/weixin-java-channel/pom.xml
+++ b/weixin-java-channel/pom.xml
@@ -29,6 +29,16 @@
jodd-http
provided
+
+ org.apache.httpcomponents
+ httpclient
+ provided
+
+
+ org.apache.httpcomponents
+ httpmime
+ provided
+
org.apache.httpcomponents.client5
httpclient5
@@ -111,12 +121,7 @@
jedis-lock
true
-
- org.mockito
- mockito-core
- 3.3.3
- test
-
+
diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxChannelCategoryService.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxChannelCategoryService.java
index 0b357a5d1c..ad86697614 100644
--- a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxChannelCategoryService.java
+++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxChannelCategoryService.java
@@ -6,11 +6,7 @@
import me.chanjar.weixin.channel.bean.audit.AuditResponse;
import me.chanjar.weixin.channel.bean.audit.CategoryAuditInfo;
import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse;
-import me.chanjar.weixin.channel.bean.category.CategoryDetailResult;
-import me.chanjar.weixin.channel.bean.category.CategoryQualificationResponse;
-import me.chanjar.weixin.channel.bean.category.PassCategoryResponse;
-import me.chanjar.weixin.channel.bean.category.ShopCategory;
-import me.chanjar.weixin.channel.bean.category.ShopCategoryResponse;
+import me.chanjar.weixin.channel.bean.category.*;
import me.chanjar.weixin.common.error.WxErrorException;
/**
@@ -121,4 +117,16 @@ AuditApplyResponse addCategory(String level1, String level2, String level3, List
* @throws WxErrorException 异常
*/
PassCategoryResponse listPassCategory() throws WxErrorException;
+
+ /**
+ * 获取店铺的类目权限列表
+ *
+ * @param isFilterStatus 是否按状态筛选
+ * @param status 类目状态(当 isFilterStatus 为 true 时有效)
+ * @return 类目权限列表
+ *
+ * @throws WxErrorException 异常
+ */
+ RelationCategoryResponse listRelationCategory(Boolean isFilterStatus, Integer status) throws WxErrorException;
+
}
diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelCategoryServiceImpl.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelCategoryServiceImpl.java
index 23cd839848..7070ab9298 100644
--- a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelCategoryServiceImpl.java
+++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelCategoryServiceImpl.java
@@ -1,13 +1,5 @@
package me.chanjar.weixin.channel.api.impl;
-import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Category.ADD_CATEGORY_URL;
-import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Category.AVAILABLE_CATEGORY_URL;
-import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Category.CANCEL_CATEGORY_AUDIT_URL;
-import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Category.GET_CATEGORY_AUDIT_URL;
-import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Category.GET_CATEGORY_DETAIL_URL;
-import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Category.LIST_ALL_CATEGORY_URL;
-import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Category.LIST_PASS_CATEGORY_URL;
-
import java.util.Collections;
import java.util.List;
import lombok.extern.slf4j.Slf4j;
@@ -17,17 +9,15 @@
import me.chanjar.weixin.channel.bean.audit.CategoryAuditInfo;
import me.chanjar.weixin.channel.bean.audit.CategoryAuditRequest;
import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse;
-import me.chanjar.weixin.channel.bean.category.CategoryDetailResult;
-import me.chanjar.weixin.channel.bean.category.CategoryQualificationResponse;
-import me.chanjar.weixin.channel.bean.category.PassCategoryResponse;
-import me.chanjar.weixin.channel.bean.category.ShopCategory;
-import me.chanjar.weixin.channel.bean.category.ShopCategoryResponse;
+import me.chanjar.weixin.channel.bean.category.*;
import me.chanjar.weixin.channel.util.JsonUtils;
import me.chanjar.weixin.channel.util.ResponseUtils;
import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.common.util.http.SimpleGetRequestExecutor;
import me.chanjar.weixin.common.util.http.SimplePostRequestExecutor;
+import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Category.*;
+
/**
* 视频号小店 商品类目相关接口
*
@@ -135,4 +125,15 @@ public PassCategoryResponse listPassCategory() throws WxErrorException {
return ResponseUtils.decode(resJson, PassCategoryResponse.class);
}
+ @Override
+ public RelationCategoryResponse listRelationCategory(Boolean isFilterStatus, Integer status) throws WxErrorException {
+ RelationCategoryRequest request = new RelationCategoryRequest(
+ isFilterStatus != null ? isFilterStatus : false,
+ status != null ? status : 0
+ );
+ String reqJson = JsonUtils.encode(request);
+ String resJson = shopService.post(LIST_RELATION_CATEGORY_URL, reqJson);
+ return ResponseUtils.decode(resJson, RelationCategoryResponse.class);
+ }
+
}
diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelServiceHttpComponentsImpl.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelServiceHttpComponentsImpl.java
index 6cf2d38503..f4cbb04755 100644
--- a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelServiceHttpComponentsImpl.java
+++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelServiceHttpComponentsImpl.java
@@ -5,7 +5,6 @@
import me.chanjar.weixin.channel.config.WxChannelConfig;
import me.chanjar.weixin.channel.util.JsonUtils;
import me.chanjar.weixin.common.util.http.HttpClientType;
-import me.chanjar.weixin.common.util.http.apache.ApacheBasicResponseHandler;
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;
@@ -41,7 +40,7 @@ public void initHttp() {
apacheHttpClientBuilder.httpProxyHost(config.getHttpProxyHost())
.httpProxyPort(config.getHttpProxyPort())
.httpProxyUsername(config.getHttpProxyUsername())
- .httpProxyPassword(config.getHttpProxyPassword().toCharArray());
+ .httpProxyPassword(config.getHttpProxyPassword() == null ? null : config.getHttpProxyPassword().toCharArray());
if (config.getHttpProxyHost() != null && config.getHttpProxyPort()> 0) {
this.httpProxy = new HttpHost(config.getHttpProxyHost(), config.getHttpProxyPort());
diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelServiceImpl.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelServiceImpl.java
index 6f2c349f3f..ccd4eafc79 100644
--- a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelServiceImpl.java
+++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelServiceImpl.java
@@ -8,7 +8,7 @@
* @author Zeyes
*/
@Slf4j
-public class WxChannelServiceImpl extends WxChannelServiceHttpClientImpl {
+public class WxChannelServiceImpl extends WxChannelServiceHttpComponentsImpl {
public WxChannelServiceImpl() {
}
diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/category/CategoryDetailResult.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/category/CategoryDetailResult.java
index 32313b7e34..3188bd3820 100644
--- a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/category/CategoryDetailResult.java
+++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/category/CategoryDetailResult.java
@@ -22,6 +22,9 @@ public class CategoryDetailResult extends WxChannelBaseResponse {
@JsonProperty("attr")
private Attr attr;
+ @JsonProperty("product_qua_list")
+ private List productQuaList;
+
@Data
@NoArgsConstructor
diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/category/RelationCategoryItem.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/category/RelationCategoryItem.java
new file mode 100644
index 0000000000..8e0bd1b0b5
--- /dev/null
+++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/category/RelationCategoryItem.java
@@ -0,0 +1,41 @@
+package me.chanjar.weixin.channel.bean.category;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+
+/**
+ * 店铺类目权限列表项
+ *
+ * @author chucheng
+ */
+@Data
+@NoArgsConstructor
+public class RelationCategoryItem implements Serializable {
+
+ /** 类目id */
+ @JsonProperty("id")
+ private Long id;
+
+ /** 类目状态, 1生效中,2已失效 */
+ @JsonProperty("status")
+ private Integer status;
+
+ /** 失效原因 */
+ @JsonProperty("uneffective_reason")
+ private String uneffectiveReason;
+
+ /** 生效时间 */
+ @JsonProperty("effective_time")
+ private Long effectiveTime;
+
+ /** 失效时间 */
+ @JsonProperty("uneffective_time")
+ private Long uneffectiveTime;
+
+ /** 类目资质id */
+ @JsonProperty("qua_id")
+ private Long quaId;
+}
diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/category/RelationCategoryRequest.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/category/RelationCategoryRequest.java
new file mode 100644
index 0000000000..c514e7d9ca
--- /dev/null
+++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/category/RelationCategoryRequest.java
@@ -0,0 +1,28 @@
+package me.chanjar.weixin.channel.bean.category;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import java.io.Serializable;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * 类目权限列表请求参数
+ *
+ * @author Zeyes
+ */
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class RelationCategoryRequest implements Serializable {
+
+ private static final long serialVersionUID = -8765432109876543210L;
+
+ /** 是否按状态筛选 */
+ @JsonProperty("is_filter_status")
+ private Boolean isFilterStatus;
+
+ /** 类目状态(当 isFilterStatus 为 true 时有效) */
+ @JsonProperty("status")
+ private Integer status;
+}
diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/category/RelationCategoryResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/category/RelationCategoryResponse.java
new file mode 100644
index 0000000000..4bd1ea96d4
--- /dev/null
+++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/category/RelationCategoryResponse.java
@@ -0,0 +1,25 @@
+package me.chanjar.weixin.channel.bean.category;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import java.util.List;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse;
+
+/**
+ * 店铺的类目权限列表响应
+ *
+ * @author chucheng
+ */
+@Data
+@NoArgsConstructor
+@EqualsAndHashCode(callSuper = true)
+public class RelationCategoryResponse extends WxChannelBaseResponse {
+
+ private static final long serialVersionUID = -8473920857463918245L;
+
+ @JsonProperty("list")
+ private List list;
+
+}
diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/SkuFastInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/SkuFastInfo.java
index a461e6d952..b37dfe472c 100644
--- a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/SkuFastInfo.java
+++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/SkuFastInfo.java
@@ -35,6 +35,14 @@ public class SkuFastInfo implements Serializable {
@JsonProperty("is_delete")
private Boolean delete;
+ /** 商品sku编码 */
+ @JsonProperty("sku_code")
+ private String skuCode;
+
+ /** 更新sku状态 0-默认值;5-上架;11-下架 */
+ @JsonProperty("status")
+ private Integer status;
+
@Data
@NoArgsConstructor
diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/SkuInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/SkuInfo.java
index 22e75d7afc..956b188c22 100644
--- a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/SkuInfo.java
+++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/SkuInfo.java
@@ -56,6 +56,10 @@ public class SkuInfo implements Serializable {
@JsonProperty("sku_id")
private String skuId;
+ /** sku条形码 */
+ @JsonProperty("bar_code")
+ private String barCode;
+
public SkuInfo() {
}
diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/SpuFastInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/SpuFastInfo.java
index 05e107779b..23b1135ba5 100644
--- a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/SpuFastInfo.java
+++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/SpuFastInfo.java
@@ -25,4 +25,28 @@ public class SpuFastInfo implements Serializable {
@JsonProperty("skus")
protected List skus;
+ /** 商品编码 */
+ @JsonProperty("spu_code")
+ protected String spuCode;
+
+ /** 限购信息 */
+ @JsonProperty("limit_info")
+ protected LimitInfo limitInfo;
+
+ /** 运费信息 */
+ @JsonProperty("express_info")
+ protected ExpressInfo expressInfo;
+
+ /** 额外服务 */
+ @JsonProperty("extra_service")
+ protected ExtraServiceInfo extraService;
+
+ /** 发货方式:0-快递发货;1-无需快递,手机号发货;3-无需快递,可选发货账号类型,默认为0,若为无需快递,则无需填写运费模版id */
+ @JsonProperty("deliver_method")
+ private Integer deliverMethod;
+
+ /** 商品待开售信息 */
+ @JsonProperty("timing_onsale_info")
+ private TimingOnSaleInfo timingOnSaleInfo;
+
}
diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/SpuInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/SpuInfo.java
index 155148c178..9b2224db94 100644
--- a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/SpuInfo.java
+++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/SpuInfo.java
@@ -151,4 +151,8 @@ public class SpuInfo extends SpuSimpleInfo {
/** 发布模式,0: 普通模式;1: 极简模式 */
@JsonProperty("release_mode")
private Integer releaseMode;
+
+ /** 商品待开售信息 */
+ @JsonProperty("timing_onsale_info")
+ private TimingOnSaleInfo timingOnSaleInfo;
}
diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/TimingOnSaleInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/TimingOnSaleInfo.java
new file mode 100644
index 0000000000..29270d426c
--- /dev/null
+++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/TimingOnSaleInfo.java
@@ -0,0 +1,36 @@
+package me.chanjar.weixin.channel.bean.product;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+
+/**
+ * 商品待开售信息
+ *
+ * @author chu
+ */
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class TimingOnSaleInfo implements Serializable {
+
+ /** 状态枚举 0-没有待开售;1-待开售 */
+ @JsonProperty("status")
+ private Integer status;
+
+ /** 开售时间,秒级时间戳,0为未配置时间 */
+ @JsonProperty("onsale_time")
+ private Long onSaleTime;
+
+ /** 是否隐藏价格 0-不隐藏;1-隐藏 */
+ @JsonProperty("is_hide_price")
+ private Integer isHidePrice;
+
+ /** 待开售任务ID,可用于请求立即开售 */
+ @JsonProperty("task_id")
+ private Integer taskId;
+
+}
diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/constant/WxChannelApiUrlConstants.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/constant/WxChannelApiUrlConstants.java
index 2d9aa84f84..4859b723fb 100644
--- a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/constant/WxChannelApiUrlConstants.java
+++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/constant/WxChannelApiUrlConstants.java
@@ -53,6 +53,8 @@ public interface Category {
String CANCEL_CATEGORY_AUDIT_URL = "https://api.weixin.qq.com/channels/ec/category/audit/cancel";
/** 获取账号申请通过的类目和资质信息 */
String LIST_PASS_CATEGORY_URL = "https://api.weixin.qq.com/channels/ec/category/list/get";
+ /** 获取店铺的类目权限列表 */
+ String LIST_RELATION_CATEGORY_URL = "https://api.weixin.qq.com/shop/ec/category/get_category_relation_list";
}
/** 主页管理相关接口 */
diff --git a/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/api/impl/WxChannelCategoryServiceImplTest.java b/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/api/impl/WxChannelCategoryServiceImplTest.java
index 125e061cd8..06afde2993 100644
--- a/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/api/impl/WxChannelCategoryServiceImplTest.java
+++ b/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/api/impl/WxChannelCategoryServiceImplTest.java
@@ -158,4 +158,14 @@ public void testListPassCategory() throws WxErrorException {
assertTrue(response.isSuccess());
System.out.println(response);
}
+
+ @Test
+ public void testListRelationCategory() throws WxErrorException {
+ WxChannelCategoryService categoryService = channelService.getCategoryService();
+ me.chanjar.weixin.channel.bean.category.RelationCategoryResponse response =
+ categoryService.listRelationCategory(true, 1);
+ assertNotNull(response);
+ assertTrue(response.isSuccess());
+ System.out.println(response);
+ }
}
diff --git a/weixin-java-common/pom.xml b/weixin-java-common/pom.xml
index e5427942c4..2053177b12 100644
--- a/weixin-java-common/pom.xml
+++ b/weixin-java-common/pom.xml
@@ -24,23 +24,17 @@
okhttp
provided
+
org.apache.httpcomponents.client5
httpclient5
- provided
-
- org.slf4j
- slf4j-api
-
-
- com.thoughtworks.xstream
- xstream
-
+
org.apache.httpcomponents
httpclient
+ provided
commons-logging
@@ -51,6 +45,16 @@
org.apache.httpcomponents
httpmime
+ provided
+
+
+
+ org.slf4j
+ slf4j-api
+
+
+ com.thoughtworks.xstream
+ xstream
org.slf4j
@@ -82,6 +86,11 @@
org.projectlombok
lombok
+
+ org.mockito
+ mockito-core
+ test
+
ch.qos.logback
@@ -93,11 +102,7 @@
testng
test
-
- org.mockito
- mockito-all
- test
-
+
com.google.inject
guice
diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/api/WxConsts.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/api/WxConsts.java
index 70c4e47933..d7e8936e62 100644
--- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/api/WxConsts.java
+++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/api/WxConsts.java
@@ -12,7 +12,7 @@
/**
* 微信开发所使用到的常量类.
*
- * @author Daniel Qian & binarywang & Wang_Wong
+ * @author Daniel Qian, binarywang, Wang_Wong
*/
@UtilityClass
public class WxConsts {
diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/oauth2/WxOAuth2AccessToken.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/oauth2/WxOAuth2AccessToken.java
index c08a49063d..b339844ad6 100644
--- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/oauth2/WxOAuth2AccessToken.java
+++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/oauth2/WxOAuth2AccessToken.java
@@ -7,7 +7,10 @@
import java.io.Serializable;
/**
- * https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140842
+ * OAuth2 AccessToken
+ *
+ * 参考:{@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/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/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/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/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 0b0590b1e6..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
@@ -197,6 +197,7 @@ public EncryptContext encryptContext(String plainText) {
/**
* 对明文进行加密.
*
+ * @param randomStr 随机字符串
* @param plainText 需要加密的明文
* @return 加密后base64编码的字符串
*/
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/apache/ApacheHttpClientBuilder.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/ApacheHttpClientBuilder.java
index de34ca5bd1..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,42 +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/json/WxGsonBuilder.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/json/WxGsonBuilder.java
index 6ea269f7e4..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
@@ -43,6 +43,11 @@ public boolean shouldSkipClass(Class> aClass) {
});
}
+ /**
+ * 创建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/xml/XStreamInitializer.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/xml/XStreamInitializer.java
index 3fa91fa70e..51cd1e980c 100644
--- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/xml/XStreamInitializer.java
+++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/xml/XStreamInitializer.java
@@ -22,6 +22,11 @@ public class XStreamInitializer {
public static ClassLoader classLoader;
+ /**
+ * 设置类加载器
+ *
+ * @param classLoaderInfo 类加载器
+ */
public static void setClassLoader(ClassLoader classLoaderInfo) {
classLoader = classLoaderInfo;
}
diff --git a/weixin-java-common/src/test/java/me/chanjar/weixin/common/redis/CommonWxRedisOpsTest.java b/weixin-java-common/src/test/java/me/chanjar/weixin/common/redis/CommonWxRedisOpsTest.java
index 96ba20ba2b..fb53c8c4b6 100644
--- a/weixin-java-common/src/test/java/me/chanjar/weixin/common/redis/CommonWxRedisOpsTest.java
+++ b/weixin-java-common/src/test/java/me/chanjar/weixin/common/redis/CommonWxRedisOpsTest.java
@@ -35,6 +35,17 @@ public void testGetExpire() {
Assert.assertTrue(expireSeconds <= 4 && expireSeconds>= 0);
}
+ @Test
+ public void testGetExpireForNonExistentKey() {
+ String nonExistentKey = "non_existent_key_" + System.currentTimeMillis();
+ Long expire = wxRedisOps.getExpire(nonExistentKey);
+ // 对于不存在的 key,底层使用 getExpire(key, TimeUnit.SECONDS) 时应返回负值
+ // Spring Data Redis 2.x 和 3.x 约定:-2 表示 key 不存在,-1 表示 key 没有过期时间
+ // 因此这里不应返回 null,而应返回一个小于 0 的值
+ Assert.assertNotNull(expire, "Non-existent key should not have null expiration");
+ Assert.assertTrue(expire < 0, "Non-existent key should have negative expiration"); + } + @Test public void testExpire() { String key = "access_token", value = String.valueOf(System.currentTimeMillis()); diff --git a/weixin-java-cp/pom.xml b/weixin-java-cp/pom.xml index 6c47176afc..9294b62d20 100644 --- a/weixin-java-cp/pom.xml +++ b/weixin-java-cp/pom.xml @@ -30,6 +30,11 @@ okhttp
provided
+
+ org.apache.httpcomponents
+ httpclient
+ provided
+
org.apache.httpcomponents.client5
httpclient5
@@ -52,6 +57,7 @@
org.springframework.data
spring-data-redis
+
org.testng
testng
@@ -59,9 +65,10 @@
org.mockito
- mockito-all
+ mockito-core
test
+
com.google.inject
guice
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
/**
* 企业可通过此接口,转接在职成员的客户给其他成员。
- *
+ *
* external_userid必须是handover_userid的客户(即配置了客户联系功能的成员所添加的联系人)。
* 在职成员的每位客户最多被分配2次。客户被转接成功后,将有90个自然日的服务关系保护期,保护期内的客户无法再次被分配。
- *
+ *
* 权限说明:
- * * 企业需要使用"客户联系"secret或配置到"可调用应用"列表中的自建应用secret所获取的accesstoken来调用(accesstoken如何获取?)。
- * 第三方应用需拥有"企业客户权限->客户联系->在职继承"权限
+ * 企业需要使用"客户联系"secret或配置到"可调用应用"列表中的自建应用secret所获取的accesstoken来调用(accesstoken如何获取?)。
+ * {@code 第三方应用需拥有"企业客户权限->客户联系->在职继承"权限}
* 接替成员必须在此第三方应用或自建应用的可见范围内。
* 接替成员需要配置了客户联系功能。
* 接替成员需要在企业微信激活且已经过实名认证。
- *
+ *
*
* @param req 转接在职成员的客户给其他成员请求实体
* @return wx cp base resp
@@ -516,13 +518,13 @@ WxCpExternalContactBatchInfo getContactDetailBatch(String[] userIdList, String c
/**
* 企业和第三方可通过此接口查询在职成员的客户转接情况。
- *
+ *
* 权限说明:
- *
+ *
* 企业需要使用"客户联系"secret或配置到"可调用应用"列表中的自建应用secret所获取的accesstoken来调用(accesstoken如何获取?)。
- * 第三方应用需拥有"企业客户权限->客户联系->在职继承"权限
+ * {@code 第三方应用需拥有"企业客户权限->客户联系->在职继承"权限}
* 接替成员必须在此第三方应用或自建应用的可见范围内。
- *
+ *
*
* @param handOverUserid 原添加成员的userid
* @param takeOverUserid 接替成员的userid
@@ -535,19 +537,21 @@ WxCpUserTransferResultResp transferResult(String handOverUserid, String takeOver
/**
* 企业可通过此接口,分配离职成员的客户给其他成员。
- *
+ *
* handover_userid必须是已离职用户。
* external_userid必须是handover_userid的客户(即配置了客户联系功能的成员所添加的联系人)。
* 在职成员的每位客户最多被分配2次。客户被转接成功后,将有90个自然日的服务关系保护期,保护期内的客户无法再次被分配。
- *
+
+ *
* 权限说明:
- *
+
+ *
* 企业需要使用"客户联系"secret或配置到"可调用应用"列表中的自建应用secret所获取的accesstoken来调用(accesstoken如何获取?)。
- * 第三方应用需拥有"企业客户权限->客户联系->离职分配"权限
+ * {@code 第三方应用需拥有"企业客户权限->客户联系->离职分配"权限}
* 接替成员必须在此第三方应用或自建应用的可见范围内。
* 接替成员需要配置了客户联系功能。
* 接替成员需要在企业微信激活且已经过实名认证。
- *
+ *
*
* @param req 转接在职成员的客户给其他成员请求实体
* @return wx cp base resp
@@ -557,13 +561,14 @@ WxCpUserTransferResultResp transferResult(String handOverUserid, String takeOver
/**
* 企业和第三方可通过此接口查询离职成员的客户分配情况。
- *
+ *
* 权限说明:
- *
+
+ *
* 企业需要使用"客户联系"secret或配置到"可调用应用"列表中的自建应用secret所获取的accesstoken来调用(accesstoken如何获取?)。
- * 第三方应用需拥有"企业客户权限->客户联系->在职继承"权限
+ * {@code 第三方应用需拥有"企业客户权限->客户联系->在职继承"权限}
* 接替成员必须在此第三方应用或自建应用的可见范围内。
- *
+ *
*
* @param handOverUserid 原添加成员的userid
* @param takeOverUserid 接替成员的userid
@@ -630,21 +635,24 @@ WxCpUserExternalGroupChatList listGroupChat(Integer pageIndex, Integer pageSize,
/**
* 企业可通过此接口,将已离职成员为群主的群,分配给另一个客服成员。
*
- *
+ *
* 注意::
- *
+
+ *
* 群主离职了的客户群,才可继承
* 继承给的新群主,必须是配置了客户联系功能的成员
* 继承给的新群主,必须有设置实名
* 继承给的新群主,必须有激活企业微信
* 同一个人的群,限制每天最多分配300个给新群主
- *
+
+ *
* 权限说明:
- *
+
+ *
* 企业需要使用"客户联系"secret或配置到"可调用应用"列表中的自建应用secret所获取的accesstoken来调用(accesstoken如何获取?)。
- * 第三方应用需拥有"企业客户权限->客户联系->分配离职成员的客户群"权限
+ * {@code 第三方应用需拥有"企业客户权限->客户联系->分配离职成员的客户群"权限}
* 对于第三方/自建应用,群主必须在应用的可见范围。
- *
+ *
*
* @param chatIds 需要转群主的客户群ID列表。取值范围: 1 ~ 100
* @param newOwner 新群主ID
@@ -656,7 +664,7 @@ WxCpUserExternalGroupChatList listGroupChat(Integer pageIndex, Integer pageSize,
/**
* 企业可通过此接口,将在职成员为群主的群,分配给另一个客服成员。
- *
+ *
* 注意:
* 继承给的新群主,必须是配置了客户联系功能的成员
* 继承给的新群主,必须有设置实名
@@ -716,11 +724,14 @@ WxCpUserExternalGroupChatStatistic getGroupChatStatistic(Date startTime, Integer
* 企业可通过此接口添加企业群发消息的任务并通知客服人员发送给相关客户或客户群。(注:企业微信终端需升级到2.7.5版本及以上)
* 注意:调用该接口并不会直接发送消息给客户/客户群,需要相关的客服人员操作以后才会实际发送(客服人员的企业微信需要升级到2.7.5及以上版本)
* 同一个企业每个自然月内仅可针对一个客户/客户群发送4条消息,超过限制的用户将会被忽略。
- *
+
+ *
* 请求方式: 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
/**
*
* 企业和第三方应用可通过此接口获取企业与成员的群发记录。
- * 获取企业群发成员执行结果
+ * 文档地址: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 c1a8d56255..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
@@ -126,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/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 externalUserIdList)
* https://qyapi.weixin.qq.com/cgi-bin/kf/get_corp_statistic?access_token=ACCESS_TOKEN
* 文档地址:
* https://developer.work.weixin.qq.com/document/path/95489
- *
+ *
* @param request 查询参数
* @return 客户数据统计 -企业汇总数据
* @throws WxErrorException the wx error exception
@@ -238,7 +238,7 @@ WxCpKfCustomerBatchGetResp customerBatchGet(List externalUserIdList)
* https://qyapi.weixin.qq.com/cgi-bin/kf/get_servicer_statistic?access_token=ACCESS_TOKEN
* 文档地址:
* https://developer.work.weixin.qq.com/document/path/95490
- *
+ *
* @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
*
*
* @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 getChatRecords(long seq, @NonNull long limit, String proxy, String passwd, @NonNull long timeout) throws Exception;
+
/**
* 获取解密的聊天数据Model
*
@@ -39,10 +56,24 @@ public interface WxCpMsgAuditService {
* @param pkcs1 使用什么方式进行解密,1代表使用PKCS1进行解密,2代表PKCS8进行解密 ...
* @return 解密后的聊天数据 decrypt data
* @throws Exception the exception
+ * @deprecated 请使用 {@link #getDecryptChatData(WxCpChatDatas.WxCpChatData, Integer)} 代替,
+ * 该方法需要传入SDK,容易导致SDK生命周期管理混乱,引发JVM崩溃
*/
+ @Deprecated
WxCpChatModel getDecryptData(@NonNull long sdk, @NonNull WxCpChatDatas.WxCpChatData chatData,
@NonNull Integer pkcs1) throws Exception;
+ /**
+ * 获取解密的聊天数据Model(推荐使用)
+ * 该方法不需要传入SDK,SDK由框架自动管理,更加安全
+ *
+ * @param chatData 聊天数据
+ * @param pkcs1 使用什么方式进行解密,1代表使用PKCS1进行解密,2代表PKCS8进行解密 ...
+ * @return 解密后的聊天数据
+ * @throws Exception the exception
+ */
+ WxCpChatModel getDecryptChatData(@NonNull WxCpChatDatas.WxCpChatData chatData, @NonNull Integer pkcs1) throws Exception;
+
/**
* 获取解密的聊天数据明文
*
@@ -51,9 +82,23 @@ WxCpChatModel getDecryptData(@NonNull long sdk, @NonNull WxCpChatDatas.WxCpChatD
* @param pkcs1 使用什么方式进行解密,1代表使用PKCS1进行解密,2代表PKCS8进行解密 ...
* @return 解密后的明文 chat plain text
* @throws Exception the exception
+ * @deprecated 请使用 {@link #getChatRecordPlainText(WxCpChatDatas.WxCpChatData, Integer)} 代替,
+ * 该方法需要传入SDK,容易导致SDK生命周期管理混乱,引发JVM崩溃
*/
+ @Deprecated
String getChatPlainText(@NonNull long sdk, @NonNull WxCpChatDatas.WxCpChatData chatData, @NonNull Integer pkcs1) throws Exception;
+ /**
+ * 获取解密的聊天数据明文(推荐使用)
+ * 该方法不需要传入SDK,SDK由框架自动管理,更加安全
+ *
+ * @param chatData 聊天数据
+ * @param pkcs1 使用什么方式进行解密,1代表使用PKCS1进行解密,2代表PKCS8进行解密 ...
+ * @return 解密后的明文
+ * @throws Exception the exception
+ */
+ String getChatRecordPlainText(@NonNull WxCpChatDatas.WxCpChatData chatData, @NonNull Integer pkcs1) throws Exception;
+
/**
* 获取媒体文件
* 针对图片、文件等媒体数据,提供sdk接口拉取数据内容。
@@ -69,10 +114,32 @@ WxCpChatModel getDecryptData(@NonNull long sdk, @NonNull WxCpChatDatas.WxCpChatD
* @param timeout 超时时间,分片数据需累加到文件存储。单次最大返回512K字节,如果文件比较大,自行设置长一点,比如timeout=10000
* @param targetFilePath 目标文件绝对路径+实际文件名,比如:/usr/local/file/20220114/474f866b39d10718810d55262af82662.gif
* @throws WxErrorException the wx error exception
+ * @deprecated 请使用 {@link #downloadMediaFile(String, String, String, long, String)} 代替,
+ * 该方法需要传入SDK,容易导致SDK生命周期管理混乱,引发JVM崩溃
*/
+ @Deprecated
void getMediaFile(@NonNull long sdk, @NonNull String sdkfileid, String proxy, String passwd, @NonNull long timeout,
@NonNull String targetFilePath) throws WxErrorException;
+ /**
+ * 获取媒体文件(推荐使用)
+ * 该方法不需要传入SDK,SDK由框架自动管理,更加安全
+ * 针对图片、文件等媒体数据,提供sdk接口拉取数据内容。
+ *
+ * 注意:
+ * 根据上面返回的文件类型,拼接好存放文件的绝对路径即可。此时绝对路径写入文件流,来达到获取媒体文件的目的。
+ * 详情可以看官方文档,亦可阅读此接口源码。
+ *
+ * @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 action) throws WxErrorException;
+ /**
+ * 获取媒体文件 传入一个lambda,each所有的数据分片byte[],更加灵活(推荐使用)
+ * 该方法不需要传入SDK,SDK由框架自动管理,更加安全
+ * 针对图片、文件等媒体数据,提供sdk接口拉取数据内容。
+ * 详情可以看官方文档,亦可阅读此接口源码。
+ *
+ * @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 action 传入一个lambda,each所有的数据分片
+ * @throws WxErrorException the wx error exception
+ */
+ void downloadMediaFile(@NonNull String sdkfileid, String proxy, String passwd, @NonNull long timeout,
+ @NonNull Consumer action) throws WxErrorException;
+
/**
* 获取会话内容存档开启成员列表
* 企业可通过此接口,获取企业开启会话内容存档的成员列表
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpOAuth2Service.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpOAuth2Service.java
index b7a44047aa..1824196720 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpOAuth2Service.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpOAuth2Service.java
@@ -90,9 +90,10 @@ public interface WxCpOAuth2Service {
/**
* 获取家校访问用户身份
* 该接口用于根据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
@@ -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 {
/**
* 获取用户二次验证信息
- *
+ *
* 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 {
*
*
* @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查询预定详情.
*
* 企业可通过此接口根据预定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 getDialRecord(Date startTime, Date endTime, Integer offset,
* https://qyapi.weixin.qq.com/cgi-bin/checkin/addcheckinuserface?access_token=ACCESS_TOKEN
* 文档地址:
* https://developer.work.weixin.qq.com/document/path/93378
- *
+ *
* @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;
+
+ /**
+ * 编辑表格内容
+ * 该接口可以对一个在线表格批量执行多个更新操作
+ *
+ * 注意:
+ * 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/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/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/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 0b601ca502..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,7 @@ public interface WxCpService extends WxService {
/**
* 企业互联的服务类对象
*
- * @return
+ * @return 企业互联服务对象
*/
WxCpCorpGroupService getCorpGroupService();
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 {
*
* 获取部门成员详情
* 请求方式: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/WxCpCorpGroupServiceImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpCorpGroupServiceImpl.java
index 48bd952a83..e3dc1cbe1c 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpCorpGroupServiceImpl.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpCorpGroupServiceImpl.java
@@ -18,7 +18,7 @@
* 企业互联相关接口实现类
*
* @author libo <422423229@qq.com>
- * Created on 27/2/2023 9:57 PM
+ * @since 2023年02月27日 9:57 PM
*/
@RequiredArgsConstructor
public class WxCpCorpGroupServiceImpl implements WxCpCorpGroupService {
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpExternalContactServiceImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpExternalContactServiceImpl.java
index 8e3a8d7b95..d43589595f 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpExternalContactServiceImpl.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpExternalContactServiceImpl.java
@@ -38,7 +38,7 @@
/**
* The type Wx cp external contact service.
*
- * @author 曹祖鹏 & yuanqixun & Mr.Pan & Wang_Wong
+ * @author 曹祖鹏, yuanqixun, Mr.Pan, Wang_Wong
*/
@RequiredArgsConstructor
public class WxCpExternalContactServiceImpl implements WxCpExternalContactService {
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpMsgAuditServiceImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpMsgAuditServiceImpl.java
index cdf559ad7a..63dc7ac007 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpMsgAuditServiceImpl.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpMsgAuditServiceImpl.java
@@ -20,6 +20,7 @@
import java.io.FileOutputStream;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.function.Consumer;
@@ -137,6 +138,49 @@ private synchronized long initSdk() throws WxErrorException {
return sdk;
}
+ /**
+ * 获取SDK并增加引用计数(原子操作)
+ * 如果SDK未初始化或已过期,会自动初始化
+ *
+ * @return sdk id
+ * @throws WxErrorException 初始化失败时抛出异常
+ */
+ private long acquireSdk() throws WxErrorException {
+ WxCpConfigStorage configStorage = cpService.getWxCpConfigStorage();
+
+ // 尝试获取现有的有效SDK并增加引用计数(原子操作)
+ long sdk = configStorage.acquireMsgAuditSdk();
+
+ if (sdk> 0) {
+ // 成功获取到有效的SDK
+ return sdk;
+ }
+
+ // SDK未初始化或已过期,需要初始化
+ // initSdk()方法已经是synchronized的,确保只有一个线程初始化
+ sdk = this.initSdk();
+
+ // 初始化后增加引用计数
+ int refCount = configStorage.incrementMsgAuditSdkRefCount(sdk);
+ if (refCount < 0) { + // SDK已经被替换,需要重新获取 + return acquireSdk(); + } + + return sdk; + } + + /** + * 释放SDK引用计数 + * + * @param sdk sdk id + */ + private void releaseSdk(long sdk) { + if (sdk> 0) {
+ cpService.getWxCpConfigStorage().releaseMsgAuditSdk(sdk);
+ }
+ }
+
@Override
public WxCpChatModel getDecryptData(@NonNull long sdk, @NonNull WxCpChatDatas.WxCpChatData chatData,
@NonNull Integer pkcs1) throws Exception {
@@ -280,4 +324,127 @@ public WxCpAgreeInfo checkSingleAgree(@NonNull WxCpCheckAgreeRequest checkAgreeR
return WxCpAgreeInfo.fromJson(responseContent);
}
+ @Override
+ public List getChatRecords(long seq, @NonNull long limit, String proxy, String passwd,
+ @NonNull long timeout) throws Exception {
+ // 获取SDK并自动增加引用计数(原子操作)
+ long sdk = this.acquireSdk();
+
+ try {
+ long slice = Finance.NewSlice();
+ long ret = Finance.GetChatData(sdk, seq, limit, proxy, passwd, timeout, slice);
+ if (ret != 0) {
+ Finance.FreeSlice(slice);
+ throw new WxErrorException("getchatdata err ret " + ret);
+ }
+
+ // 拉取会话存档
+ String content = Finance.GetContentFromSlice(slice);
+ Finance.FreeSlice(slice);
+ WxCpChatDatas chatDatas = WxCpChatDatas.fromJson(content);
+ if (chatDatas.getErrCode().intValue() != 0) {
+ throw new WxErrorException(chatDatas.toJson());
+ }
+
+ List chatDataList = chatDatas.getChatData();
+ return chatDataList != null ? chatDataList : Collections.emptyList();
+ } finally {
+ // 释放SDK引用计数(原子操作)
+ this.releaseSdk(sdk);
+ }
+ }
+
+ @Override
+ public WxCpChatModel getDecryptChatData(@NonNull WxCpChatDatas.WxCpChatData chatData,
+ @NonNull Integer pkcs1) throws Exception {
+ // 获取SDK并自动增加引用计数(原子操作)
+ long sdk = this.acquireSdk();
+
+ try {
+ String plainText = this.decryptChatData(sdk, chatData, pkcs1);
+ return WxCpChatModel.fromJson(plainText);
+ } finally {
+ // 释放SDK引用计数(原子操作)
+ this.releaseSdk(sdk);
+ }
+ }
+
+ @Override
+ public String getChatRecordPlainText(@NonNull WxCpChatDatas.WxCpChatData chatData,
+ @NonNull Integer pkcs1) throws Exception {
+ // 获取SDK并自动增加引用计数(原子操作)
+ long sdk = this.acquireSdk();
+
+ try {
+ return this.decryptChatData(sdk, chatData, pkcs1);
+ } finally {
+ // 释放SDK引用计数(原子操作)
+ this.releaseSdk(sdk);
+ }
+ }
+
+ @Override
+ public void downloadMediaFile(@NonNull String sdkfileid, String proxy, String passwd, @NonNull long timeout,
+ @NonNull String targetFilePath) throws WxErrorException {
+ // 获取SDK并自动增加引用计数(原子操作)
+ long sdk;
+ try {
+ sdk = this.acquireSdk();
+ } catch (Exception e) {
+ throw new WxErrorException(e);
+ }
+
+ // 使用AtomicReference捕获Lambda中的异常,以便在执行完后抛出
+ final java.util.concurrent.atomic.AtomicReference exceptionHolder = new java.util.concurrent.atomic.AtomicReference();
+
+ try {
+ File targetFile = new File(targetFilePath);
+ if (!targetFile.getParentFile().exists()) {
+ targetFile.getParentFile().mkdirs();
+ }
+ this.getMediaFile(sdk, sdkfileid, proxy, passwd, timeout, i -> {
+ // 如果之前已经发生异常,不再继续处理
+ if (exceptionHolder.get() != null) {
+ return;
+ }
+ try {
+ // 大于512k的文件会分片拉取,此处需要使用追加写,避免后面的分片覆盖之前的数据。
+ FileOutputStream outputStream = new FileOutputStream(targetFile, true);
+ outputStream.write(i);
+ outputStream.close();
+ } catch (Exception e) {
+ exceptionHolder.set(e);
+ }
+ });
+
+ // 检查是否发生异常,如果有则抛出
+ Exception caughtException = exceptionHolder.get();
+ if (caughtException != null) {
+ throw new WxErrorException(caughtException);
+ }
+ } finally {
+ // 释放SDK引用计数(原子操作)
+ this.releaseSdk(sdk);
+ }
+ }
+
+ @Override
+ public void downloadMediaFile(@NonNull String sdkfileid, String proxy, String passwd, @NonNull long timeout,
+ @NonNull Consumer action) throws WxErrorException {
+ // 获取SDK并自动增加引用计数(原子操作)
+ long sdk;
+ try {
+ sdk = this.acquireSdk();
+ } catch (Exception e) {
+ throw new WxErrorException(e);
+ }
+
+ try {
+ this.getMediaFile(sdk, sdkfileid, proxy, passwd, timeout, action);
+ } finally {
+ // 释放SDK引用计数(原子操作)
+ this.releaseSdk(sdk);
+ }
+ }
+
}
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpOaWeDocServiceImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpOaWeDocServiceImpl.java
index 81de32453d..fc5379dc73 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpOaWeDocServiceImpl.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpOaWeDocServiceImpl.java
@@ -63,4 +63,27 @@ public WxCpDocShare docShare(@NonNull String docId) throws WxErrorException {
String responseContent = this.cpService.post(apiUrl, jsonObject.toString());
return WxCpDocShare.fromJson(responseContent);
}
+
+ @Override
+ public WxCpDocSheetBatchUpdateResponse docBatchUpdate(@NonNull WxCpDocSheetBatchUpdateRequest request) throws WxErrorException {
+ String apiUrl = this.cpService.getWxCpConfigStorage().getApiUrl(WEDOC_SPREADSHEET_BATCH_UPDATE);
+ String responseContent = this.cpService.post(apiUrl, request.toJson());
+ return WxCpDocSheetBatchUpdateResponse.fromJson(responseContent);
+ }
+
+ @Override
+ public WxCpDocSheetProperties getSheetProperties(@NonNull String docId) throws WxErrorException {
+ String apiUrl = this.cpService.getWxCpConfigStorage().getApiUrl(WEDOC_SPREADSHEET_GET_SHEET_PROPERTIES);
+ JsonObject jsonObject = new JsonObject();
+ jsonObject.addProperty("docid", docId);
+ String responseContent = this.cpService.post(apiUrl, jsonObject.toString());
+ return WxCpDocSheetProperties.fromJson(responseContent);
+ }
+
+ @Override
+ public WxCpDocSheetData getSheetRangeData(@NonNull WxCpDocSheetGetDataRequest request) throws WxErrorException {
+ String apiUrl = this.cpService.getWxCpConfigStorage().getApiUrl(WEDOC_SPREADSHEET_GET_SHEET_RANGE_DATA);
+ String responseContent = this.cpService.post(apiUrl, request.toJson());
+ return WxCpDocSheetData.fromJson(responseContent);
+ }
}
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpServiceHttpComponentsImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpServiceHttpComponentsImpl.java
index 92fd2dbd9b..4b6a1e36ff 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpServiceHttpComponentsImpl.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpServiceHttpComponentsImpl.java
@@ -82,7 +82,8 @@ public void initHttp() {
apacheHttpClientBuilder.httpProxyHost(this.configStorage.getHttpProxyHost())
.httpProxyPort(this.configStorage.getHttpProxyPort())
.httpProxyUsername(this.configStorage.getHttpProxyUsername())
- .httpProxyPassword(this.configStorage.getHttpProxyPassword().toCharArray());
+ .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());
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpBaseResp.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpBaseResp.java
index 6bf9a30aeb..a895c38a8f 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpBaseResp.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpBaseResp.java
@@ -10,7 +10,8 @@
/**
* 返回结果
*
- * @author yqx & WangWong created on 2020年3月16日
+ * @author yqx, WangWong
+ * @since 2020年3月16日
*/
@Getter
@Setter
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpAuthInfo.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpAuthInfo.java
index fa50216153..9919fd72b8 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpAuthInfo.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpAuthInfo.java
@@ -216,7 +216,7 @@ public static class Agent implements Serializable {
/**
* 付费状态
- *
+ *
*
* - 0-没有付费;
* - 1-限时试用;
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpCustomizedAppDetail.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpCustomizedAppDetail.java
new file mode 100644
index 0000000000..f9ca645b82
--- /dev/null
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpCustomizedAppDetail.java
@@ -0,0 +1,221 @@
+package me.chanjar.weixin.cp.bean;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * 代开发应用详情.
+ *
+ * @author Binary Wang
+ * created on 2026年01月14日
+ */
+@Data
+public class WxCpTpCustomizedAppDetail extends WxCpBaseResp {
+
+ /**
+ * 授权方企业id
+ */
+ @SerializedName("auth_corpid")
+ private String authCorpId;
+
+ /**
+ * 授权方企业名称
+ */
+ @SerializedName("auth_corp_name")
+ private String authCorpName;
+
+ /**
+ * 授权方企业方形头像
+ */
+ @SerializedName("auth_corp_square_logo_url")
+ private String authCorpSquareLogoUrl;
+
+ /**
+ * 授权方企业圆形头像
+ */
+ @SerializedName("auth_corp_round_logo_url")
+ private String authCorpRoundLogoUrl;
+
+ /**
+ * 授权方企业类型,1. 企业; 2. 政府以及事业单位; 3. 其他组织, 4.团队小型企业(原企业微信认证版用户)
+ */
+ @SerializedName("auth_corp_type")
+ private Integer authCorpType;
+
+ /**
+ * 授权方企业在微工作台(原企业号)的二维码,可用于关注微工作台
+ */
+ @SerializedName("auth_corp_qrcode_url")
+ private String authCorpQrcodeUrl;
+
+ /**
+ * 授权方企业用户规模
+ */
+ @SerializedName("auth_corp_user_limit")
+ private Integer authCorpUserLimit;
+
+ /**
+ * 授权方企业的主体名称(仅认证或验证过的企业有),即企业全称
+ */
+ @SerializedName("auth_corp_full_name")
+ private String authCorpFullName;
+
+ /**
+ * 企业类型,1. 已验证企业;2. 已认证企业
+ */
+ @SerializedName("auth_corp_verified_type")
+ private Integer authCorpVerifiedType;
+
+ /**
+ * 授权方企业所属行业
+ */
+ @SerializedName("auth_corp_industry")
+ private String authCorpIndustry;
+
+ /**
+ * 授权方企业所属子行业
+ */
+ @SerializedName("auth_corp_sub_industry")
+ private String authCorpSubIndustry;
+
+ /**
+ * 授权方企业所在地址
+ */
+ @SerializedName("auth_corp_location")
+ private String authCorpLocation;
+
+ /**
+ * 代开发自建应用详情
+ */
+ @SerializedName("customized_app_list")
+ private List customizedAppList;
+
+ /**
+ * From json wx cp tp customized app detail.
+ *
+ * @param json the json
+ * @return the wx cp tp customized app detail
+ */
+ public static WxCpTpCustomizedAppDetail fromJson(String json) {
+ return WxCpGsonBuilder.create().fromJson(json, WxCpTpCustomizedAppDetail.class);
+ }
+
+ @Override
+ public String toJson() {
+ return WxCpGsonBuilder.create().toJson(this);
+ }
+
+ /**
+ * 代开发自建应用信息
+ */
+ @Data
+ public static class CustomizedApp implements Serializable {
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 代开发自建应用的agentid
+ */
+ @SerializedName("agentid")
+ private Integer agentId;
+
+ /**
+ * 代开发自建应用对应的模板id
+ */
+ @SerializedName("template_id")
+ private String templateId;
+
+ /**
+ * 代开发自建应用的name
+ */
+ @SerializedName("name")
+ private String name;
+
+ /**
+ * 代开发自建应用的description
+ */
+ @SerializedName("description")
+ private String description;
+
+ /**
+ * 代开发自建应用的logo url
+ */
+ @SerializedName("logo_url")
+ private String logoUrl;
+
+ /**
+ * 代开发自建应用的可见范围
+ */
+ @SerializedName("allow_userinfos")
+ private AllowUserInfos allowUserInfos;
+
+ /**
+ * 代开发自建应用是否被禁用
+ */
+ @SerializedName("close")
+ private Integer close;
+
+ /**
+ * 代开发自建应用主页url
+ */
+ @SerializedName("home_url")
+ private String homeUrl;
+
+ /**
+ * 代开发自建应用的模式,0 = 代开发自建应用;1 = 第三方应用代开发
+ */
+ @SerializedName("app_type")
+ private Integer appType;
+ }
+
+ /**
+ * 应用可见范围
+ */
+ @Data
+ public static class AllowUserInfos implements Serializable {
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 应用可见范围(成员)
+ */
+ @SerializedName("user")
+ private List users;
+
+ /**
+ * 应用可见范围(部门)
+ */
+ @SerializedName("department")
+ private List departments;
+ }
+
+ /**
+ * 成员信息
+ */
+ @Data
+ public static class User implements Serializable {
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 成员userid
+ */
+ @SerializedName("userid")
+ private String userId;
+ }
+
+ /**
+ * 部门信息
+ */
+ @Data
+ public static class Department implements Serializable {
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 部门id
+ */
+ @SerializedName("id")
+ private Integer id;
+ }
+}
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpPermanentCodeInfo.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpPermanentCodeInfo.java
index 522e606a20..5330194abe 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpPermanentCodeInfo.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpPermanentCodeInfo.java
@@ -225,7 +225,7 @@ public static class Agent implements Serializable {
/**
* 付费状态
- *
+ *
*
* - 0-没有付费;
* - 1-限时试用;
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpTag.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpTag.java
index 74e1fec3f8..a73ec171b4 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpTag.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpTag.java
@@ -10,7 +10,7 @@
* The type Wx cp tp tag.
*
* @author zhangq
- * @since 2021 -02-14 16:15 16:15
+ * @since 2021年02月14日 16:15
*/
@Data
public class WxCpTpTag implements Serializable {
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpTagAddOrRemoveUsersResult.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpTagAddOrRemoveUsersResult.java
index dfbf250480..565cbb408c 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpTagAddOrRemoveUsersResult.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpTagAddOrRemoveUsersResult.java
@@ -6,7 +6,7 @@
* 企业微信第三方开发-增加标签成员成员api响应体
*
* @author zhangq
- * @since 2021 /2/14 16:44
+ * @since 2021年2月14日 16:44
*/
public class WxCpTpTagAddOrRemoveUsersResult extends WxCpTagAddOrRemoveUsersResult {
private static final long serialVersionUID = 3490401800490702052L;
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpTagGetResult.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpTagGetResult.java
index 162030c956..134656e438 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpTagGetResult.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpTagGetResult.java
@@ -6,7 +6,7 @@
* 获取标签成员接口响应体
*
* @author zhangq
- * @since 2021 /2/14 16:28
+ * @since 2021年2月14日 16:28
*/
public class WxCpTpTagGetResult extends WxCpTagGetResult {
private static final long serialVersionUID = 9051748686315562400L;
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpTemplateList.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpTemplateList.java
new file mode 100644
index 0000000000..83abbb6e7d
--- /dev/null
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpTemplateList.java
@@ -0,0 +1,83 @@
+package me.chanjar.weixin.cp.bean;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * 应用模板列表.
+ *
+ * @author Binary Wang
+ * created on 2026年01月14日
+ */
+@Data
+public class WxCpTpTemplateList extends WxCpBaseResp {
+
+ /**
+ * 应用模板列表
+ */
+ @SerializedName("template_list")
+ private List templateList;
+
+ /**
+ * From json wx cp tp template list.
+ *
+ * @param json the json
+ * @return the wx cp tp template list
+ */
+ public static WxCpTpTemplateList fromJson(String json) {
+ return WxCpGsonBuilder.create().fromJson(json, WxCpTpTemplateList.class);
+ }
+
+ @Override
+ public String toJson() {
+ return WxCpGsonBuilder.create().toJson(this);
+ }
+
+ /**
+ * 应用模板信息
+ */
+ @Data
+ public static class Template implements Serializable {
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 模板id
+ */
+ @SerializedName("template_id")
+ private String templateId;
+
+ /**
+ * 模板类型
+ */
+ @SerializedName("template_type")
+ private Integer templateType;
+
+ /**
+ * 应用名称
+ */
+ @SerializedName("app_name")
+ private String appName;
+
+ /**
+ * 应用logo url
+ */
+ @SerializedName("logo_url")
+ private String logoUrl;
+
+ /**
+ * 应用简介
+ */
+ @SerializedName("app_desc")
+ private String appDesc;
+
+ /**
+ * 应用状态
+ */
+ @SerializedName("status")
+ private Integer status;
+ }
+}
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/external/WxCpMsgTemplate.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/external/WxCpMsgTemplate.java
index 6c546daa83..42e51afbb8 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/external/WxCpMsgTemplate.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/external/WxCpMsgTemplate.java
@@ -14,10 +14,9 @@
/**
* 企业群发消息任务
- *
- * 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/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/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 {
/**
*
* 请使用.
- * {@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/WxCpXmlMessage.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/message/WxCpXmlMessage.java
index 4001c7d0e4..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")
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 {
/**
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/WxCpCheckinGroupBase.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/WxCpCheckinGroupBase.java
index 3bc542ccd0..81e5dcc329 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/WxCpCheckinGroupBase.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/WxCpCheckinGroupBase.java
@@ -10,8 +10,8 @@
* 打卡规则基础信息
*
* @author zhongjun96
- * @date 2023年7月7日
- **/
+ * @since 2023年7月7日
+ */
@Data
public class WxCpCheckinGroupBase implements Serializable {
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/WxCpOaSchedule.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/WxCpOaSchedule.java
index 20b1f45e20..9836a3291a 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/WxCpOaSchedule.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/WxCpOaSchedule.java
@@ -157,7 +157,7 @@ public static class Reminder implements Serializable {
/**
* 提醒时间与日程开始时间(start_time)的差值,当is_remind为1时有效。例如:-300表示日程开始前5分钟提醒。
* 特殊情况:企业微信终端设置的"全天"类型的日程,由于start_time是0点时间戳,提醒如果设置了当天9点,则会出现正数32400。
- *
+ *
* 取值范围:-604800 ~ 86399
*/
@SerializedName("remind_time_diffs")
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/doc/WxCpDocSheetBatchUpdateRequest.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/doc/WxCpDocSheetBatchUpdateRequest.java
new file mode 100644
index 0000000000..37c42717e7
--- /dev/null
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/doc/WxCpDocSheetBatchUpdateRequest.java
@@ -0,0 +1,199 @@
+package me.chanjar.weixin.cp.bean.oa.doc;
+
+import java.io.Serializable;
+import java.util.List;
+
+import com.google.gson.annotations.SerializedName;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import lombok.experimental.Accessors;
+import me.chanjar.weixin.cp.bean.oa.doc.WxCpDocSheetData.GridData;
+import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder;
+
+/**
+ * 编辑表格内容请求
+ *
+ * @author zhongying
+ * @since 2026年01月07日
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+@Accessors(chain = true)
+public class WxCpDocSheetBatchUpdateRequest implements Serializable {
+ private static final long serialVersionUID = 584565591133421347L;
+
+ /**
+ * 文档的docid.必填
+ */
+ @SerializedName("docid")
+ private String docId;
+
+ /**
+ * 更新操作列表.必填
+ */
+ @SerializedName("requests")
+ private List requests;
+
+ /**
+ * From json wx cp doc sheet batch update request.
+ *
+ * @param json the json
+ * @return the wx cp doc sheet batch update request
+ */
+ public static WxCpDocSheetBatchUpdateRequest fromJson(String json) {
+ return WxCpGsonBuilder.create().fromJson(json, WxCpDocSheetBatchUpdateRequest.class);
+ }
+
+ /**
+ * To json string.
+ *
+ * @return the string
+ */
+ public String toJson() {
+ return WxCpGsonBuilder.create().toJson(this);
+ }
+
+ /**
+ * 更新操作
+ */
+ @Getter
+ @Setter
+ public static class Request implements Serializable {
+ private static final long serialVersionUID = 253933038745894628L;
+
+ /**
+ * 新增工作表
+ */
+ @SerializedName("add_sheet_request")
+ private AddSheetRequest addSheetRequest;
+
+ /**
+ * 删除工作表请求
+ */
+ @SerializedName("delete_sheet_request")
+ private DeleteSheetRequest deleteSheetRequest;
+
+ /**
+ * 更新范围内单元格内容
+ */
+ @SerializedName("update_range_request")
+ private UpdateRangeRequest updateRangeRequest;
+
+ /**
+ * 删除表格连续的行或列
+ */
+ @SerializedName("delete_dimension_request")
+ private DeleteDimensionRequest deleteDimensionRequest;
+
+ /**
+ * 新增工作表,新增需满足以下限制
+ * 范围列数 <=200 + * 范围内的总单元格数量 <=10000 + */ + @Getter + @Setter + public static class AddSheetRequest implements Serializable { + private static final long serialVersionUID = 523704967699486288L; + + /** + * 工作表名称 + */ + @SerializedName("title") + private String title; + + /** + * 新增工作表的初始行数 + */ + @SerializedName("row_count") + private int rowCount; + + /** + * 新增工作表的初始列数 + */ + @SerializedName("column_count") + private int columnCount; + } + + /** + * 删除工作表请求 + */ + @Getter + @Setter + public static class DeleteSheetRequest implements Serializable { + private static final long serialVersionUID = 767974765396168274L; + + /** + * 工作表唯一标识 + */ + @SerializedName("sheet_id") + private String sheetId; + } + + /** + * 更新范围内单元格内容 + */ + @Getter + @Setter + public static class UpdateRangeRequest implements Serializable { + private static final long serialVersionUID = 433859595039061888L; + + /** + * 工作表唯一标识 + */ + @SerializedName("sheet_id") + private String sheetId; + + /** + * 表格数据 + */ + @SerializedName("grid_data") + private GridData gridData; + } + + /** + * 删除表格连续的行或列 + * 注意: + * 1.该操作会导致表格缩表 + * 2.删除的范围遵循 左闭右开 ———— [start_index,end_index) ,如果 end_index <= start_index + * 则该请求报错。 + */ + @Getter + @Setter + public static class DeleteDimensionRequest implements Serializable { + private static final long serialVersionUID = 107245521502978033L; + + /** + * 工作表唯一标识 + */ + @SerializedName("sheet_id") + private String sheetId; + + /** + * 删除的维度类型. + * ROW:行 + * COLUMN:列 + */ + @SerializedName("dimension") + private String dimension; + + /** + * 删除行列的起始序号(从1开始) + */ + @SerializedName("start_index") + private int startIndex; + + /** + * 删除行列的终止序号(从1开始) + */ + @SerializedName("end_index") + private int endIndex; + } + } +} diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/doc/WxCpDocSheetBatchUpdateResponse.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/doc/WxCpDocSheetBatchUpdateResponse.java new file mode 100644 index 0000000000..8e31e9022f --- /dev/null +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/doc/WxCpDocSheetBatchUpdateResponse.java @@ -0,0 +1,126 @@ +package me.chanjar.weixin.cp.bean.oa.doc; + +import java.io.Serializable; + +import com.google.gson.annotations.SerializedName; + +import lombok.Data; +import lombok.Getter; +import lombok.Setter; +import me.chanjar.weixin.cp.bean.oa.doc.WxCpDocSheetProperties.Properties; +import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder; + +/** + * 更新操作(UpdateRequest])对应的响应结构体类型 + * + * @author zhongying + * @since 2026-01-08 + */ +@Data +public class WxCpDocSheetBatchUpdateResponse implements Serializable { + private static final long serialVersionUID = 781694717017131015L; + + /** + * 新增工作表响应 + */ + @SerializedName("add_sheet_response") + private AddSheetResponse addSheetResponse; + + /** + * 删除工作表响应 + */ + @SerializedName("delete_sheet_response") + private DeleteSheetResponse deleteSheetResponse; + + /** + * 更新范围内单元格内容响应 + */ + @SerializedName("update_range_response") + private UpdateRangeResponse updateRangeResponse; + + /** + * 删除表格连续的行或列响应 + */ + @SerializedName("delete_dimension_response") + private DeleteDimensionResponse deleteDimensionResponse; + + /** + * 从 JSON 字符串反序列化为 WxCpDocSheetBatchUpdateResponse 对象。 + * + * @param json the json + * @return 反序列化得到的 WxCpDocSheetBatchUpdateResponse 对象 + */ + public static WxCpDocSheetBatchUpdateResponse fromJson(String json) { + return WxCpGsonBuilder.create().fromJson(json, WxCpDocSheetBatchUpdateResponse.class); + } + + /** + * To json string. + * + * @return the string + */ + public String toJson() { + return WxCpGsonBuilder.create().toJson(this); + } + + /** + * 新增工作表响应 + */ + @Getter + @Setter + public static class AddSheetResponse implements Serializable { + private static final long serialVersionUID = 618814209485621336L; + + /** + * 新增子表的属性 + */ + @SerializedName("properties") + private Properties properties; + } + + /** + * 删除工作表响应 + */ + @Getter + @Setter + public static class DeleteSheetResponse implements Serializable { + private static final long serialVersionUID = 625927337093938411L; + + /** + * 被删除的工作表的唯一标识 + */ + @SerializedName("sheet_id") + private String sheetId; + } + + /** + * 更新范围内单元格内容响应 + */ + @Getter + @Setter + public static class UpdateRangeResponse implements Serializable { + private static final long serialVersionUID = 180842641209787414L; + + /** + * 数据更新的成功的单元格数量 + */ + @SerializedName("updated_cells") + private int updatedCells; + } + + /** + * 删除表格连续的行(或列),请求响应体结构 + */ + @Getter + @Setter + public static class DeleteDimensionResponse implements Serializable { + private static final long serialVersionUID = 107245521502978033L; + + /** + * 被删除的行数(或列数) + */ + @SerializedName("deleted") + private int deleted; + } + +} diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/doc/WxCpDocSheetData.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/doc/WxCpDocSheetData.java new file mode 100644 index 0000000000..15e76430b7 --- /dev/null +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/doc/WxCpDocSheetData.java @@ -0,0 +1,260 @@ +package me.chanjar.weixin.cp.bean.oa.doc; + +import java.io.Serializable; +import java.util.List; + +import com.google.gson.annotations.SerializedName; + +import lombok.Data; +import lombok.Getter; +import lombok.Setter; +import me.chanjar.weixin.cp.bean.WxCpBaseResp; +import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder; + +/** + * 获取表格数据 + * + * @author zhongying + * @since 2026-01-07 + */ +@Data +public class WxCpDocSheetData extends WxCpBaseResp implements Serializable { + private static final long serialVersionUID = 498054945993671723L; + + /** + * 返回data + */ + @SerializedName("grid_data") + private GridData gridData; + + /** + * From json sheet data. + * + * @param json the json + * @return the sheet data + */ + public static WxCpDocSheetData fromJson(String json) { + return WxCpGsonBuilder.create().fromJson(json, WxCpDocSheetData.class); + } + + /** + * To json string. + * + * @return the string + */ + @Override + public String toJson() { + return WxCpGsonBuilder.create().toJson(this); + } + + /** + * 表格数据 + */ + @Getter + @Setter + public static class GridData implements Serializable { + private static final long serialVersionUID = 389887488098521821L; + + /** + * 起始行编号 (从0开始计算) + */ + @SerializedName("start_row") + private int startRow; + + /** + * 起始列编号 (从0开始计算) + */ + @SerializedName("start_column") + private int startColumn; + + /** + * 各行的数据 + */ + @SerializedName("rows") + private List rows;
+
+ /**
+ * 行数据
+ */
+ @Getter
+ @Setter
+ public static class RowData implements Serializable {
+ private static final long serialVersionUID = 225648868492722337L;
+
+ /**
+ * 各个单元格的数据内容
+ */
+ @SerializedName("values")
+ private List values;
+
+ /**
+ * 单元格的信息
+ */
+ @Getter
+ @Setter
+ public static class CellData implements Serializable {
+ private static final long serialVersionUID = 656471192719707304L;
+
+ /**
+ * 单元格的数据内容
+ */
+ @SerializedName("cell_value")
+ private CellValue cellValue;
+
+ /**
+ * 单元格的样式信息
+ */
+ @SerializedName("cell_format")
+ private CellFormat cellFormat;
+
+ /**
+ * 单元格的数据内容,暂时只支持文本、链接,一个CellValue中只能选填一个字段
+ */
+ @Getter
+ @Setter
+ public static class CellValue implements Serializable {
+ private static final long serialVersionUID = 656471192719707304L;
+
+ /**
+ * 文本
+ */
+ @SerializedName("text")
+ private String text;
+
+ /**
+ * 链接
+ */
+ @SerializedName("link")
+ private Link link;
+
+ /**
+ * 链接
+ */
+ @Getter
+ @Setter
+ public static class Link implements Serializable {
+ private static final long serialVersionUID = 912896452879347178L;
+
+ /**
+ * 链接url
+ */
+ @SerializedName("url")
+ private String url;
+
+ /**
+ * 链接标题
+ */
+ @SerializedName("text")
+ private String text;
+ }
+ }
+
+ /**
+ * 单元格的样式信息
+ */
+ @Getter
+ @Setter
+ public static class CellFormat implements Serializable {
+ private static final long serialVersionUID = 656471192719707304L;
+
+ /**
+ * 文字样式
+ */
+
+ @SerializedName("text_format")
+ private TextFormat textFormat;
+
+ /**
+ * 文字样式
+ */
+ @Getter
+ @Setter
+ public static class TextFormat implements Serializable {
+ private static final long serialVersionUID = 184358104921122206L;
+
+ /**
+ * 字体
+ */
+ @SerializedName("font")
+ private String font;
+
+ /**
+ * 字体大小,最大72
+ */
+ @SerializedName("font_size")
+ private int fontSize;
+
+ /**
+ * 字体加粗
+ */
+ @SerializedName("bold")
+ private boolean bold;
+
+ /**
+ * 斜体
+ */
+ @SerializedName("italic")
+ private boolean italic;
+
+ /**
+ * 字体删除线
+ */
+ @SerializedName("strikethrough")
+ private boolean strikethrough;
+
+ /**
+ * 字体下划线
+ */
+ @SerializedName("underline")
+ private boolean underline;
+
+ /**
+ * 字体颜色
+ */
+ @SerializedName("color")
+ private Color color;
+
+ /**
+ * 字体颜色
+ */
+ @Getter
+ @Setter
+ public static class Color implements Serializable {
+ private static final long serialVersionUID = 140418085882147315L;
+
+ /**
+ * 红色,取值范围:[0,255]
+ */
+ @SerializedName("red")
+ private int red;
+
+ /**
+ * 绿色,取值范围:[0,255]
+ */
+ @SerializedName("green")
+ private int green;
+
+ /**
+ * 蓝色,取值范围:[0,255]
+ */
+ @SerializedName("blue")
+ private int blue;
+
+ /**
+ * alpha通道,取值范围:[0,255],默认值为255完全不透明
+ */
+ @SerializedName("alpha")
+ private int alpha;
+
+ }
+
+ }
+ }
+
+ }
+
+ }
+
+ }
+
+}
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/doc/WxCpDocSheetGetDataRequest.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/doc/WxCpDocSheetGetDataRequest.java
new file mode 100644
index 0000000000..df2907aa31
--- /dev/null
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/doc/WxCpDocSheetGetDataRequest.java
@@ -0,0 +1,64 @@
+package me.chanjar.weixin.cp.bean.oa.doc;
+
+import java.io.Serializable;
+
+import com.google.gson.annotations.SerializedName;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import lombok.experimental.Accessors;
+import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder;
+
+/**
+ * 获取表格数据请求
+ *
+ * @author zhongying
+ * @since 2026年01月07日
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+@Accessors(chain = true)
+public class WxCpDocSheetGetDataRequest implements Serializable {
+ private static final long serialVersionUID = 827718590347822812L;
+
+ /**
+ * 在线表格唯一标识.必填
+ */
+ @SerializedName("docid")
+ private String docId;
+
+ /**
+ * 工作表ID,工作表的唯一标识.必填
+ */
+ @SerializedName("sheet_id")
+ private String sheetId;
+
+ /**
+ * 查询的范围,格式为"A1:B2",表示获取A1到B2单元格的数据.必填
+ */
+ @SerializedName("range")
+ private String range;
+
+ /**
+ * From json to {@link WxCpDocSheetGetDataRequest}.
+ *
+ * @param json the json string representing {@link WxCpDocSheetGetDataRequest}
+ * @return the {@link WxCpDocSheetGetDataRequest} object
+ */
+ public static WxCpDocSheetGetDataRequest fromJson(String json) {
+ return WxCpGsonBuilder.create().fromJson(json, WxCpDocSheetGetDataRequest.class);
+ }
+
+ /**
+ * To json string.
+ *
+ * @return the string
+ */
+ public String toJson() {
+ return WxCpGsonBuilder.create().toJson(this);
+ }
+}
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/doc/WxCpDocSheetProperties.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/doc/WxCpDocSheetProperties.java
new file mode 100644
index 0000000000..6e67965fe0
--- /dev/null
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/doc/WxCpDocSheetProperties.java
@@ -0,0 +1,79 @@
+package me.chanjar.weixin.cp.bean.oa.doc;
+
+import java.io.Serializable;
+import java.util.List;
+
+import com.google.gson.annotations.SerializedName;
+
+import lombok.Data;
+import lombok.Getter;
+import lombok.Setter;
+import me.chanjar.weixin.cp.bean.WxCpBaseResp;
+import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder;
+
+/**
+ * 获取表格行列信息
+ *
+ * @author zhongying
+ * @since 2026年01月07日
+ */
+@Data
+public class WxCpDocSheetProperties extends WxCpBaseResp implements Serializable {
+ private static final long serialVersionUID = 666707252457243065L;
+
+ @SerializedName("properties")
+ private List properties;
+
+ /**
+ * From json sheet properties.
+ *
+ * @param json the json
+ * @return the sheet properties
+ */
+ public static WxCpDocSheetProperties fromJson(String json) {
+ return WxCpGsonBuilder.create().fromJson(json, WxCpDocSheetProperties.class);
+ }
+
+ /**
+ * To json string.
+ *
+ * @return the string
+ */
+ public String toJson() {
+ return WxCpGsonBuilder.create().toJson(this);
+ }
+
+ /**
+ * 工作表属性
+ */
+ @Getter
+ @Setter
+ public static class Properties implements Serializable {
+ private static final long serialVersionUID = 640301090538839892L;
+
+ /**
+ * 工作表ID,工作表的唯一标识
+ */
+ @SerializedName("sheet_id")
+ private String sheetId;
+
+ /**
+ * 工作表名称
+ */
+ @SerializedName("title")
+ private String title;
+
+ /**
+ * 表格的总行数
+ */
+ @SerializedName("row_count")
+ private int rowCount;
+
+ /**
+ * 表格的总列数
+ */
+ @SerializedName("column_count")
+ private int columnCount;
+
+ }
+}
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/meetingroom/WxCpOaMeetingRoomBookResult.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/meetingroom/WxCpOaMeetingRoomBookResult.java
index 16cf32fa5c..4f74bab79c 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/meetingroom/WxCpOaMeetingRoomBookResult.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/meetingroom/WxCpOaMeetingRoomBookResult.java
@@ -39,7 +39,7 @@ public static WxCpOaMeetingRoomBookResult fromJson(String json) {
private String schedule_id;
/**
* 通过会议预定会议室 和 通过日程预定会议室 接口返回
- *
+ *
* 会议室冲突日期列表,为当天0点的时间戳;使用重复会议预定会议室,部分日期与会议室预定情况冲突时返回
*/
@SerializedName("conflict_date")
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/templatedata/TemplateTips.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/templatedata/TemplateTips.java
index 58daeb007c..eada2ad100 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/templatedata/TemplateTips.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/templatedata/TemplateTips.java
@@ -8,7 +8,7 @@
/**
* @author mrsiu@msn.com
* @version 1.0
- * @date 2025年1月16日 09:40
+ * @since 2025年1月16日 09:40
*/
@Data
public class TemplateTips {
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/templatedata/TemplateTipsContent.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/templatedata/TemplateTipsContent.java
index 939e6819a0..0af8be8be5 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/templatedata/TemplateTipsContent.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/templatedata/TemplateTipsContent.java
@@ -5,7 +5,7 @@
/**
* @author mrsiu@msn.com
* @version 1.0
- * @date 2025年1月16日 09:42
+ * @since 2025年1月16日 09:42
*/
@Data
public class TemplateTipsContent {
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/templatedata/TemplateTipsSubText.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/templatedata/TemplateTipsSubText.java
index ac4681038c..78e88562e8 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/templatedata/TemplateTipsSubText.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/templatedata/TemplateTipsSubText.java
@@ -5,7 +5,7 @@
/**
* @author mrsiu@msn.com
* @version 1.0
- * @date 2025年1月16日 09:45
+ * @since 2025年1月16日 09:45
*/
@Data
public class TemplateTipsSubText {
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/templatedata/TemplateTipsSubTextContent.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/templatedata/TemplateTipsSubTextContent.java
index 9c99b2688e..f33c31d054 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/templatedata/TemplateTipsSubTextContent.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/templatedata/TemplateTipsSubTextContent.java
@@ -6,7 +6,7 @@
/**
* @author mrsiu@msn.com
* @version 1.0
- * @date 2025年1月16日 09:46
+ * @since 2025年1月16日 09:46
*/
@Data
public class TemplateTipsSubTextContent {
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/templatedata/TemplateTipsSubTextContentLink.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/templatedata/TemplateTipsSubTextContentLink.java
index 4cd198409a..fd4db2c18f 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/templatedata/TemplateTipsSubTextContentLink.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/templatedata/TemplateTipsSubTextContentLink.java
@@ -5,7 +5,7 @@
/**
* @author mrsiu@msn.com
* @version 1.0
- * @date 2025年1月16日 09:49
+ * @since 2025年1月16日 09:49
*/
@Data
public class TemplateTipsSubTextContentLink {
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/templatedata/TemplateTipsSubTextContentPlainText.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/templatedata/TemplateTipsSubTextContentPlainText.java
index 12969cdcdb..0a2e792edb 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/templatedata/TemplateTipsSubTextContentPlainText.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/templatedata/TemplateTipsSubTextContentPlainText.java
@@ -3,9 +3,9 @@
import lombok.Data;
/**
- * @author mrsiu@msn.com
- * @date 2025年1月16日 09:47
- * @version 1.0
+ * @author mrsiu@msn.com
+ * @version 1.0
+ * @since 2025年1月16日 09:47
*/
@Data
public class TemplateTipsSubTextContentPlainText {
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/templatedata/TemplateTipsText.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/templatedata/TemplateTipsText.java
index 100c5bb137..c9dbe68abf 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/templatedata/TemplateTipsText.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/templatedata/TemplateTipsText.java
@@ -6,9 +6,9 @@
import java.util.List;
/**
- * @author mrsiu@msn.com
- * @date 2025年1月16日 09:43
- * @version 1.0
+ * @author mrsiu@msn.com
+ * @version 1.0
+ * @since 2025年1月16日 09:43
*/
@Data
public class TemplateTipsText {
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/WxCpConfigStorage.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/WxCpConfigStorage.java
index 8b968e540c..fd96d76c30 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/WxCpConfigStorage.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/WxCpConfigStorage.java
@@ -292,4 +292,47 @@ public interface WxCpConfigStorage {
* 使会话存档SDK过期
*/
void expireMsgAuditSdk();
+
+ /**
+ * 增加会话存档SDK的引用计数
+ * 用于支持多线程安全的SDK生命周期管理
+ *
+ * @param sdk sdk id
+ * @return 增加后的引用计数,如果SDK不匹配返回-1
+ */
+ int incrementMsgAuditSdkRefCount(long sdk);
+
+ /**
+ * 减少会话存档SDK的引用计数
+ * 当引用计数降为0时,自动销毁SDK以释放资源
+ *
+ * @param sdk sdk id
+ * @return 减少后的引用计数,如果返回0表示SDK已被销毁,如果SDK不匹配返回-1
+ */
+ int decrementMsgAuditSdkRefCount(long sdk);
+
+ /**
+ * 获取会话存档SDK的引用计数
+ *
+ * @param sdk sdk id
+ * @return 当前引用计数,如果SDK不匹配返回-1
+ */
+ int getMsgAuditSdkRefCount(long sdk);
+
+ /**
+ * 获取当前SDK并增加引用计数(原子操作)
+ * 如果SDK未初始化或已过期,返回0而不增加引用计数
+ * 此方法用于在获取SDK后立即增加引用计数,避免并发问题
+ *
+ * @return 当前有效的SDK id并已增加引用计数,如果SDK无效返回0
+ */
+ long acquireMsgAuditSdk();
+
+ /**
+ * 减少SDK引用计数并在必要时释放(原子操作)
+ * 此方法确保引用计数递减和SDK检查在同一个同步块内完成
+ *
+ * @param sdk sdk id
+ */
+ void releaseMsgAuditSdk(long sdk);
}
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/WxCpCorpGroupConfigStorage.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/WxCpCorpGroupConfigStorage.java
index 07acb189a8..df758ac3a2 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/WxCpCorpGroupConfigStorage.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/WxCpCorpGroupConfigStorage.java
@@ -31,8 +31,8 @@ public interface WxCpCorpGroupConfigStorage {
/**
* Update corp access token.
*
- * @param corpId
- * @param agentId
+ * @param corpId 企业ID
+ * @param agentId 应用ID
* @param corpAccessToken the corp access token
* @param expiresInSeconds the expires in seconds
*/
@@ -41,8 +41,8 @@ public interface WxCpCorpGroupConfigStorage {
/**
* 授权企业的access token相关
*
- * @param corpId the corp id
- * @param agentId
+ * @param corpId 企业ID
+ * @param agentId 应用ID
* @return the access token
*/
String getCorpAccessToken(String corpId, Integer agentId);
@@ -50,8 +50,8 @@ public interface WxCpCorpGroupConfigStorage {
/**
* Gets access token entity.
*
- * @param corpId the corp id
- * @param agentId
+ * @param corpId 企业ID
+ * @param agentId 应用ID
* @return the access token entity
*/
WxAccessToken getCorpAccessTokenEntity(String corpId, Integer agentId);
@@ -59,8 +59,8 @@ public interface WxCpCorpGroupConfigStorage {
/**
* Is access token expired boolean.
*
- * @param corpId the corp id
- * @param agentId
+ * @param corpId 企业ID
+ * @param agentId 应用ID
* @return the boolean
*/
boolean isCorpAccessTokenExpired(String corpId, Integer agentId);
@@ -68,8 +68,8 @@ public interface WxCpCorpGroupConfigStorage {
/**
* Expire access token.
*
- * @param corpId the corp id
- * @param agentId
+ * @param corpId 企业ID
+ * @param agentId 应用ID
*/
void expireCorpAccessToken(String corpId, Integer agentId);
@@ -118,7 +118,8 @@ public interface WxCpCorpGroupConfigStorage {
/**
* Gets access token lock.
*
- * @param corpId the corp id
+ * @param corpId 企业ID
+ * @param agentId 应用ID
* @return the access token lock
*/
Lock getCorpAccessTokenLock(String corpId, Integer agentId);
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/WxCpTpConfigStorage.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/WxCpTpConfigStorage.java
index 2cd37821b5..0a2ed0e76a 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/WxCpTpConfigStorage.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/WxCpTpConfigStorage.java
@@ -133,7 +133,7 @@ public interface WxCpTpConfigStorage {
String getEncodingAESKey();
/**
- * 企微服务商企业ID & 企业secret
+ * {@code 企微服务商企业ID & 企业secret}
*
* @return the corp id
*/
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/impl/AbstractWxCpTpInRedisConfigImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/impl/AbstractWxCpTpInRedisConfigImpl.java
index 08eed33c16..6d1260fe68 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/impl/AbstractWxCpTpInRedisConfigImpl.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/impl/AbstractWxCpTpInRedisConfigImpl.java
@@ -187,7 +187,9 @@ public String getEncodingAESKey() {
/**
- * 企微服务商企业ID & 企业secret, 来自于企微配置
+ * {@code 企微服务商企业ID & 企业secret, 来自于企微配置}
+ *
+ * @return 企业ID
*/
@Override
public String getCorpId() {
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/impl/WxCpDefaultConfigImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/impl/WxCpDefaultConfigImpl.java
index 4bf13f24ea..f8047e846f 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/impl/WxCpDefaultConfigImpl.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/impl/WxCpDefaultConfigImpl.java
@@ -1,5 +1,6 @@
package me.chanjar.weixin.cp.config.impl;
+import com.tencent.wework.Finance;
import me.chanjar.weixin.common.bean.WxAccessToken;
import me.chanjar.weixin.common.util.http.apache.ApacheHttpClientBuilder;
import me.chanjar.weixin.cp.config.WxCpConfigStorage;
@@ -54,6 +55,10 @@ public class WxCpDefaultConfigImpl implements WxCpConfigStorage, Serializable {
*/
private volatile long msgAuditSdk;
private volatile long msgAuditSdkExpiresTime;
+ /**
+ * 会话存档SDK引用计数,用于多线程安全的生命周期管理
+ */
+ private volatile int msgAuditSdkRefCount;
private volatile String oauth2redirectUri;
private volatile String httpProxyHost;
private volatile int httpProxyPort;
@@ -470,13 +475,77 @@ public boolean isMsgAuditSdkExpired() {
@Override
public synchronized void updateMsgAuditSdk(long sdk, int expiresInSeconds) {
+ // 如果有旧的SDK且不同于新的SDK,需要销毁旧的SDK
+ if (this.msgAuditSdk> 0 && this.msgAuditSdk != sdk) {
+ // 无论旧SDK是否仍有引用,都需要销毁它以避免资源泄漏
+ // 如果有飞行中的请求使用旧SDK,这些请求可能会失败,但这比资源泄漏更安全
+ Finance.DestroySdk(this.msgAuditSdk);
+ }
this.msgAuditSdk = sdk;
// 预留200秒的时间
this.msgAuditSdkExpiresTime = System.currentTimeMillis() + (expiresInSeconds - 200) * 1000L;
+ // 重置引用计数,因为这是一个全新的SDK
+ this.msgAuditSdkRefCount = 0;
}
@Override
public void expireMsgAuditSdk() {
this.msgAuditSdkExpiresTime = 0;
}
+
+ @Override
+ public synchronized int incrementMsgAuditSdkRefCount(long sdk) {
+ if (this.msgAuditSdk == sdk && sdk> 0) {
+ return ++this.msgAuditSdkRefCount;
+ }
+ return -1; // SDK不匹配,返回-1表示错误
+ }
+
+ @Override
+ public synchronized int decrementMsgAuditSdkRefCount(long sdk) {
+ if (this.msgAuditSdk == sdk && this.msgAuditSdkRefCount> 0) {
+ int newCount = --this.msgAuditSdkRefCount;
+ // 当引用计数降为0时,自动销毁SDK以释放资源
+ // 再次检查SDK是否仍然是当前缓存的SDK(防止并发重新初始化)
+ if (newCount == 0 && this.msgAuditSdk == sdk) {
+ Finance.DestroySdk(sdk);
+ this.msgAuditSdk = 0;
+ this.msgAuditSdkExpiresTime = 0;
+ }
+ return newCount;
+ }
+ return -1; // SDK不匹配或引用计数已为0,返回-1表示错误
+ }
+
+ @Override
+ public synchronized int getMsgAuditSdkRefCount(long sdk) {
+ if (this.msgAuditSdk == sdk && sdk> 0) {
+ return this.msgAuditSdkRefCount;
+ }
+ return -1; // SDK不匹配,返回-1表示错误
+ }
+
+ @Override
+ public synchronized long acquireMsgAuditSdk() {
+ // 检查SDK是否有效(已初始化且未过期)
+ if (this.msgAuditSdk> 0 && !isMsgAuditSdkExpired()) {
+ this.msgAuditSdkRefCount++;
+ return this.msgAuditSdk;
+ }
+ return 0; // SDK未初始化或已过期
+ }
+
+ @Override
+ public synchronized void releaseMsgAuditSdk(long sdk) {
+ if (this.msgAuditSdk == sdk && this.msgAuditSdkRefCount> 0) {
+ int newCount = --this.msgAuditSdkRefCount;
+ // 当引用计数降为0时,自动销毁SDK以释放资源
+ // 再次检查SDK是否仍然是当前缓存的SDK(防止并发重新初始化)
+ if (newCount == 0 && this.msgAuditSdk == sdk) {
+ Finance.DestroySdk(sdk);
+ this.msgAuditSdk = 0;
+ this.msgAuditSdkExpiresTime = 0;
+ }
+ }
+ }
}
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/impl/WxCpRedisConfigImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/impl/WxCpRedisConfigImpl.java
index 49cd7c4559..48e2445506 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/impl/WxCpRedisConfigImpl.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/impl/WxCpRedisConfigImpl.java
@@ -1,5 +1,6 @@
package me.chanjar.weixin.cp.config.impl;
+import com.tencent.wework.Finance;
import me.chanjar.weixin.common.bean.WxAccessToken;
import me.chanjar.weixin.common.util.http.apache.ApacheHttpClientBuilder;
import me.chanjar.weixin.cp.config.WxCpConfigStorage;
@@ -55,6 +56,10 @@ public class WxCpRedisConfigImpl implements WxCpConfigStorage {
*/
private volatile long msgAuditSdk;
private volatile long msgAuditSdkExpiresTime;
+ /**
+ * 会话存档SDK引用计数,用于多线程安全的生命周期管理
+ */
+ private volatile int msgAuditSdkRefCount;
/**
* Instantiates a new Wx cp redis config.
@@ -488,13 +493,77 @@ public boolean isMsgAuditSdkExpired() {
@Override
public synchronized void updateMsgAuditSdk(long sdk, int expiresInSeconds) {
+ // 如果有旧的SDK且不同于新的SDK,需要销毁旧的SDK
+ if (this.msgAuditSdk> 0 && this.msgAuditSdk != sdk) {
+ // 无论旧SDK是否仍有引用,都需要销毁它以避免资源泄漏
+ // 如果有飞行中的请求使用旧SDK,这些请求可能会失败,但这比资源泄漏更安全
+ Finance.DestroySdk(this.msgAuditSdk);
+ }
this.msgAuditSdk = sdk;
// 预留200秒的时间
this.msgAuditSdkExpiresTime = System.currentTimeMillis() + (expiresInSeconds - 200) * 1000L;
+ // 重置引用计数,因为这是一个全新的SDK
+ this.msgAuditSdkRefCount = 0;
}
@Override
public void expireMsgAuditSdk() {
this.msgAuditSdkExpiresTime = 0;
}
+
+ @Override
+ public synchronized int incrementMsgAuditSdkRefCount(long sdk) {
+ if (this.msgAuditSdk == sdk && sdk> 0) {
+ return ++this.msgAuditSdkRefCount;
+ }
+ return -1; // SDK不匹配,返回-1表示错误
+ }
+
+ @Override
+ public synchronized int decrementMsgAuditSdkRefCount(long sdk) {
+ if (this.msgAuditSdk == sdk && this.msgAuditSdkRefCount> 0) {
+ int newCount = --this.msgAuditSdkRefCount;
+ // 当引用计数降为0时,自动销毁SDK以释放资源
+ // 再次检查SDK是否仍然是当前缓存的SDK(防止并发重新初始化)
+ if (newCount == 0 && this.msgAuditSdk == sdk) {
+ Finance.DestroySdk(sdk);
+ this.msgAuditSdk = 0;
+ this.msgAuditSdkExpiresTime = 0;
+ }
+ return newCount;
+ }
+ return -1; // SDK不匹配或引用计数已为0,返回-1表示错误
+ }
+
+ @Override
+ public synchronized int getMsgAuditSdkRefCount(long sdk) {
+ if (this.msgAuditSdk == sdk && sdk> 0) {
+ return this.msgAuditSdkRefCount;
+ }
+ return -1; // SDK不匹配,返回-1表示错误
+ }
+
+ @Override
+ public synchronized long acquireMsgAuditSdk() {
+ // 检查SDK是否有效(已初始化且未过期)
+ if (this.msgAuditSdk> 0 && !isMsgAuditSdkExpired()) {
+ this.msgAuditSdkRefCount++;
+ return this.msgAuditSdk;
+ }
+ return 0; // SDK未初始化或已过期
+ }
+
+ @Override
+ public synchronized void releaseMsgAuditSdk(long sdk) {
+ if (this.msgAuditSdk == sdk && this.msgAuditSdkRefCount> 0) {
+ int newCount = --this.msgAuditSdkRefCount;
+ // 当引用计数降为0时,自动销毁SDK以释放资源
+ // 再次检查SDK是否仍然是当前缓存的SDK(防止并发重新初始化)
+ if (newCount == 0 && this.msgAuditSdk == sdk) {
+ Finance.DestroySdk(sdk);
+ this.msgAuditSdk = 0;
+ this.msgAuditSdkExpiresTime = 0;
+ }
+ }
+ }
}
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/impl/WxCpTpDefaultConfigImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/impl/WxCpTpDefaultConfigImpl.java
index fc124251f5..7fbbc9ccce 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/impl/WxCpTpDefaultConfigImpl.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/impl/WxCpTpDefaultConfigImpl.java
@@ -29,7 +29,7 @@ public class WxCpTpDefaultConfigImpl implements WxCpTpConfigStorage, Serializabl
private final transient Map authCorpJsapiTicketLocker = new ConcurrentHashMap();
private final transient Map authSuiteJsapiTicketLocker = new ConcurrentHashMap();
/**
- * 企微服务商企业ID & 企业secret,来自于企微配置
+ * {@code 企微服务商企业ID & 企业secret,来自于企微配置}
*/
protected volatile String corpId;
/**
@@ -198,7 +198,7 @@ public String getSuiteId() {
/**
* Sets suite id.
*
- * @param suiteId
+ * @param suiteId 套件ID
*/
public void setSuiteId(String suiteId) {
this.suiteId = suiteId;
@@ -211,6 +211,8 @@ public String getSuiteSecret() {
/**
* Sets suite secret.
+ *
+ * @param corpSecret 套件密钥
*/
public void setSuiteSecret(String corpSecret) {
this.suiteSecret = corpSecret;
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/constant/WxCpApiPathConsts.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/constant/WxCpApiPathConsts.java
index ade813ef5c..6e2ee866ab 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/constant/WxCpApiPathConsts.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/constant/WxCpApiPathConsts.java
@@ -575,6 +575,21 @@ interface Oa {
*/
String WEDOC_DOC_SHARE = "/cgi-bin/wedoc/doc_share";
+ /**
+ * The constant WEDOC_SPREADSHEET_BATCH_UPDATE.
+ */
+ String WEDOC_SPREADSHEET_BATCH_UPDATE = "/cgi-bin/wedoc/spreadsheet/batch_update";
+
+ /**
+ * The constant WEDOC_SPREADSHEET_GET_SHEET_PROPERTIES.
+ */
+ String WEDOC_SPREADSHEET_GET_SHEET_PROPERTIES = "/cgi-bin/wedoc/spreadsheet/get_sheet_properties";
+
+ /**
+ * The constant WEDOC_SPREADSHEET_GET_SHEET_RANGE_DATA.
+ */
+ String WEDOC_SPREADSHEET_GET_SHEET_RANGE_DATA = "/cgi-bin/wedoc/spreadsheet/get_sheet_range_data";
+
/**
* 邮件
* https://developer.work.weixin.qq.com/document/path/95486
@@ -905,6 +920,15 @@ interface Tp {
*/
String GET_CUSTOMIZED_AUTH_URL = "/cgi-bin/service/get_customized_auth_url";
+ /**
+ * The constant GET_TEMPLATE_LIST.
+ */
+ String GET_TEMPLATE_LIST = "/cgi-bin/service/get_template_list";
+
+ /**
+ * The constant GET_CUSTOMIZED_APP_DETAIL.
+ */
+ String GET_CUSTOMIZED_APP_DETAIL = "/cgi-bin/service/get_customized_app_detail";
/**
* The constant CONTACT_SEARCH.
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/constant/WxCpConsts.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/constant/WxCpConsts.java
index ff3f8e0e6c..df85249013 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/constant/WxCpConsts.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/constant/WxCpConsts.java
@@ -124,7 +124,7 @@ public static class EventType {
public static final String REOPEN_INACTIVE_AGENT = "reopen_inactive_agent";
/**
- * 企业成员添加外部联系人事件推送 & 会话存档客户同意进行聊天内容存档事件回调事件
+ * {@code 企业成员添加外部联系人事件推送 & 会话存档客户同意进行聊天内容存档事件回调事件}
*/
public static final String CHANGE_EXTERNAL_CONTACT = "change_external_contact";
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/corpgroup/service/WxCpCgService.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/corpgroup/service/WxCpCgService.java
index b9b2c5d774..f94c14a4f1 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/corpgroup/service/WxCpCgService.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/corpgroup/service/WxCpCgService.java
@@ -33,28 +33,31 @@ public interface WxCpCgService {
/**
* 授权企业的access token相关
*
- * @param corpId the corp id
- * @param agentId
- * @param businessType
+ * @param corpId 企业ID
+ * @param agentId 应用ID
+ * @param businessType 业务类型
* @return the access token
+ * @throws WxErrorException 微信错误异常
*/
WxAccessToken getCorpAccessTokenEntity(String corpId, Integer agentId, Integer businessType) throws WxErrorException;
/**
* Gets access token entity.
*
- * @param corpId the corp id
- * @param agentId
- * @param businessType
+ * @param corpId 企业ID
+ * @param agentId 应用ID
+ * @param businessType 业务类型
+ * @param forceRefresh 是否强制刷新
* @return the access token entity
+ * @throws WxErrorException 微信错误异常
*/
WxAccessToken getCorpAccessTokenEntity(String corpId, Integer agentId, Integer businessType, boolean forceRefresh) throws WxErrorException;
/**
* Is access token expired boolean.
*
- * @param corpId the corp id
- * @param agentId
+ * @param corpId 企业ID
+ * @param agentId 应用ID
* @return the boolean
*/
boolean isCorpAccessTokenExpired(String corpId, Integer agentId);
@@ -62,8 +65,8 @@ public interface WxCpCgService {
/**
* Expire access token.
*
- * @param corpId the corp id
- * @param agentId
+ * @param corpId 企业ID
+ * @param agentId 应用ID
*/
void expireCorpAccessToken(String corpId, Integer agentId);
@@ -72,6 +75,7 @@ public interface WxCpCgService {
*
* @param url 接口地址
* @param queryParam 请求参数
+ * @param req 获取token请求参数
* @return the string
* @throws WxErrorException the wx error exception
*/
@@ -83,6 +87,7 @@ public interface WxCpCgService {
* @param url 接口地址
* @param queryParam 请求参数
* @param withoutCorpAccessToken 请求是否忽略CorpAccessToken 默认不忽略-false
+ * @param req 获取token请求参数
* @return the string
* @throws WxErrorException the wx error exception
*/
@@ -93,6 +98,7 @@ public interface WxCpCgService {
*
* @param url 接口地址
* @param postData 请求body字符串
+ * @param req 获取token请求参数
* @return the string
* @throws WxErrorException the wx error exception
*/
@@ -110,6 +116,7 @@ public interface WxCpCgService {
* @param executor 执行器
* @param uri 请求地址
* @param data 参数
+ * @param req 获取token请求参数
* @return the t
* @throws WxErrorException the wx error exception
*/
@@ -156,7 +163,7 @@ public interface WxCpCgService {
/**
* 互联企业的服务类对象
*
- * @return
+ * @return 互联企业服务对象
*/
WxCpLinkedCorpService getLinkedCorpService();
@@ -164,10 +171,11 @@ public interface WxCpCgService {
* 获取下级/下游企业小程序session
* https://developer.work.weixin.qq.com/document/path/93355
*
- * @param userId
- * @param sessionKey
- * @return
- * @throws WxErrorException
+ * @param userId 用户ID
+ * @param sessionKey 会话密钥
+ * @param req 请求参数
+ * @return 小程序session结果
+ * @throws WxErrorException 微信错误异常
*/
WxCpMaTransferSession getCorpTransferSession(String userId, String sessionKey, WxCpCorpGroupCorpGetTokenReq req) throws WxErrorException;
}
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/corpgroup/service/impl/BaseWxCpCgServiceImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/corpgroup/service/impl/BaseWxCpCgServiceImpl.java
index 9991073739..e4fe2a686a 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/corpgroup/service/impl/BaseWxCpCgServiceImpl.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/corpgroup/service/impl/BaseWxCpCgServiceImpl.java
@@ -112,6 +112,7 @@ public String post(String url, String postData, WxCpCorpGroupCorpGetTokenReq req
* @param url the url
* @param postData the post data
* @param withoutCorpAccessToken the without Corp access token
+ * @param req 获取token请求参数
* @return the string
* @throws WxErrorException the wx error exception
*/
@@ -136,6 +137,7 @@ public T execute(RequestExecutor executor, String uri, E data, WxCp
* @param uri the uri
* @param data the data
* @param withoutCorpAccessToken the without Corp access token
+ * @param req 获取token请求参数
* @return the t
* @throws WxErrorException the wx error exception
*/
@@ -181,6 +183,7 @@ public T execute(RequestExecutor executor, String uri, E data, bool
* @param executor the executor
* @param uri the uri
* @param data the data
+ * @param req 获取token请求参数
* @return the t
* @throws WxErrorException the wx error exception
*/
@@ -197,6 +200,7 @@ protected T executeInternal(RequestExecutor executor, String uri, E
* @param uri the uri
* @param data the data
* @param withoutCorpAccessToken the without Corp access token
+ * @param req 获取token请求参数
* @return the t
* @throws WxErrorException the wx error exception
*/
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/corpgroup/service/impl/WxCpCgServiceHttpComponentsImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/corpgroup/service/impl/WxCpCgServiceHttpComponentsImpl.java
index d5c60ad037..4c8fa5d131 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/corpgroup/service/impl/WxCpCgServiceHttpComponentsImpl.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/corpgroup/service/impl/WxCpCgServiceHttpComponentsImpl.java
@@ -35,7 +35,8 @@ public void initHttp() {
apacheHttpClientBuilder.httpProxyHost(this.configStorage.getHttpProxyHost())
.httpProxyPort(this.configStorage.getHttpProxyPort())
.httpProxyUsername(this.configStorage.getHttpProxyUsername())
- .httpProxyPassword(this.configStorage.getHttpProxyPassword().toCharArray());
+ .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());
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/WxCpTpCustomizedService.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/WxCpTpCustomizedService.java
new file mode 100644
index 0000000000..99e2848852
--- /dev/null
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/WxCpTpCustomizedService.java
@@ -0,0 +1,41 @@
+package me.chanjar.weixin.cp.tp.service;
+
+import me.chanjar.weixin.common.error.WxErrorException;
+import me.chanjar.weixin.cp.bean.WxCpTpCustomizedAppDetail;
+import me.chanjar.weixin.cp.bean.WxCpTpTemplateList;
+
+/**
+ * 企业微信第三方应用 - 代开发相关接口.
+ *
+ * @author Binary Wang
+ * created on 2026年01月14日
+ */
+public interface WxCpTpCustomizedService {
+
+ /**
+ * 获取应用模板列表
+ *
+ * 服务商可通过本接口获取服务商所拥有的应用模板列表
+ * 文档地址: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 {
* 延长试用期
*
* 文档地址
- *
+ *
* 注意:
*
* - 一个应用可以多次延长试用,但是试用总天数不能超过60天
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/WxCpTpIdConvertService.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/WxCpTpIdConvertService.java
index 10268bcb31..83dd6fe36b 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/WxCpTpIdConvertService.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/WxCpTpIdConvertService.java
@@ -20,9 +20,11 @@ public interface WxCpTpIdConvertService {
* unionid与external_userid的关联
* 查看文档
*
+ * @param cropId 企业ID
* @param unionid 微信客户的unionid
* @param openid 微信客户的openid
* @param subjectType 程序或公众号的主体类型: 0表示主体名称是企业的,1表示主体名称是服务商的
+ * @return 转换结果
* @throws WxErrorException 。
*/
WxCpTpUnionidToExternalUseridResult unionidToExternalUserid(String cropId, String unionid, String openid,
@@ -31,29 +33,33 @@ WxCpTpUnionidToExternalUseridResult unionidToExternalUserid(String cropId, Strin
/**
* 将企业主体下的客户标签ID转换成服务商主体下的客户标签ID
- * @param corpId 企业微信 ID
- * @param externalTagIdList 企业主体下的客户标签ID列表,最多不超过1000个
- * @return 客户标签转换结果
+ *
+ * @param corpId 企业微信 ID
+ * @param externalTagIdList 企业主体下的客户标签ID列表,最多不超过1000个
+ * @return 客户标签转换结果
* @throws WxErrorException .
*/
WxCpTpTagIdListConvertResult externalTagId(String corpId, String... externalTagIdList) throws WxErrorException;
/**
* 将企业主体下的微信客服ID转换成服务商主体下的微信客服ID
- * @param corpId 企业微信 ID
- * @param openKfIdList 微信客服ID列表,最多不超过1000个
- * @return 微信客服ID转换结果
- * @throws WxErrorException .
+ *
+ * @param corpId 企业微信 ID
+ * @param openKfIdList 微信客服ID列表,最多不超过1000个
+ * @return 微信客服ID转换结果
+ * @throws WxErrorException .
*/
- WxCpTpOpenKfIdConvertResult ConvertOpenKfId (String corpId, String... openKfIdList ) throws WxErrorException;
+ WxCpTpOpenKfIdConvertResult ConvertOpenKfId(String corpId, String... openKfIdList) throws WxErrorException;
/**
* 将应用获取的外部用户临时idtmp_external_userid,转换为external_userid
- * @param corpId 企业微信Id
- * @param businessType 业务类型。1-会议 2-收集表
- * @param userType 转换的目标用户类型。1-客户 2-企业互联 3-上下游 4-互联企业(圈子)
- * @param tmpExternalUserIdList 外部用户临时id,最多不超过100个
- * @return 转换成功的结果列表
+ *
+ * @param corpId 企业微信Id
+ * @param businessType 业务类型。1-会议 2-收集表
+ * @param userType 转换的目标用户类型。1-客户 2-企业互联 3-上下游 4-互联企业(圈子)
+ * @param tmpExternalUserIdList 外部用户临时id,最多不超过100个
+ * @return 转换成功的结果列表
+ * @throws WxErrorException .
*/
WxCpTpConvertTmpExternalUserIdResult convertTmpExternalUserId(String corpId, int businessType, int userType, String... tmpExternalUserIdList) throws WxErrorException;
}
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/WxCpTpOAService.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/WxCpTpOAService.java
index 85321213ab..4aed96b7b5 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/WxCpTpOAService.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/WxCpTpOAService.java
@@ -51,8 +51,7 @@ public interface WxCpTpOAService {
String copyTemplate(@NonNull String openTemplateId, String corpId) throws WxErrorException;
/**
- *
- * 获取审批申请详情
+ * 获取审批申请详情
*
* @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 orderId 订单号
* @return the order
@@ -31,7 +31,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 b24be535da..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
@@ -199,7 +199,7 @@ 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;
@@ -227,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;
@@ -558,12 +558,12 @@ WxCpTpXmlMessage fromEncryptedXml(String encryptedXml,
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;
@@ -655,9 +655,25 @@ WxCpTpXmlMessage fromEncryptedXml(String encryptedXml,
/**
* 构造第三方应用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 f8f554b81a..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
@@ -60,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的锁.
*/
@@ -809,6 +810,16 @@ 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/WxCpTpServiceHttpComponentsImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/impl/WxCpTpServiceHttpComponentsImpl.java
index bba597a3ee..44b5fd8693 100644
--- 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
@@ -10,7 +10,6 @@
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.config.WxCpTpConfigStorage;
import me.chanjar.weixin.cp.constant.WxCpApiPathConsts;
import org.apache.hc.client5.http.classic.methods.HttpPost;
import org.apache.hc.client5.http.config.RequestConfig;
@@ -87,9 +86,10 @@ public void initHttp() {
HttpComponentsClientBuilder apacheHttpClientBuilder = DefaultHttpComponentsClientBuilder.get();
apacheHttpClientBuilder.httpProxyHost(this.configStorage.getHttpProxyHost())
- .httpProxyPort(this.configStorage.getHttpProxyPort())
- .httpProxyUsername(this.configStorage.getHttpProxyUsername())
- .httpProxyPassword(this.configStorage.getHttpProxyPassword().toCharArray());
+ .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());
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
- * @since 2021 -02-14 16:02
+ * @since 2021年02月14日 16:02
*/
@RequiredArgsConstructor
public class WxCpTpTagServiceImpl implements WxCpTpTagService {
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/util/xml/XStreamTransformer.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/util/xml/XStreamTransformer.java
index 098a781c64..09c6ce8287 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/util/xml/XStreamTransformer.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/util/xml/XStreamTransformer.java
@@ -20,7 +20,7 @@ public class XStreamTransformer {
protected static final Map, XStream> CLASS_2_XSTREAM_INSTANCE = configXStreamInstance();
/**
- * xml -> pojo
+ * {@code xml -> pojo}
*
* @param the type parameter
* @param clazz the clazz
@@ -58,7 +58,7 @@ public static void register(Class> clz, XStream xStream) {
}
/**
- * pojo -> xml.
+ * {@code pojo -> xml.}
*
* @param the type parameter
* @param clazz the clazz
diff --git a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/WxCpMsgAuditTest.java b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/WxCpMsgAuditTest.java
index ec7362ed5d..a1ea40f3fb 100644
--- a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/WxCpMsgAuditTest.java
+++ b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/WxCpMsgAuditTest.java
@@ -754,6 +754,84 @@ public void testGetMediaFile() throws Exception {
Finance.DestroySdk(chatDatas.getSdk());
}
+ /**
+ * 测试新的安全API方法(推荐使用)
+ * 这些方法不需要手动管理SDK生命周期,更加安全
+ */
+ @Test
+ public void testNewSafeApi() throws Exception {
+ WxCpMsgAuditService msgAuditService = cpService.getMsgAuditService();
+
+ // 测试新的getChatRecords方法 - 不暴露SDK
+ List chatRecords = msgAuditService.getChatRecords(0L, 10L, null, null, 1000L);
+ log.info("获取到 {} 条聊天记录", chatRecords.size());
+
+ for (WxCpChatDatas.WxCpChatData chatData : chatRecords) {
+ // 测试新的getDecryptChatData方法 - 不需要传入SDK
+ WxCpChatModel decryptData = msgAuditService.getDecryptChatData(chatData, 2);
+ log.info("解密数据:{}", decryptData.toJson());
+
+ // 测试新的getChatRecordPlainText方法 - 不需要传入SDK
+ String plainText = msgAuditService.getChatRecordPlainText(chatData, 2);
+ log.info("明文数据:{}", plainText);
+
+ // 如果是媒体消息,测试新的downloadMediaFile方法
+ String msgType = decryptData.getMsgType();
+ if ("image".equals(msgType) || "voice".equals(msgType) || "video".equals(msgType) || "file".equals(msgType)) {
+ String suffix = "";
+ String md5Sum = "";
+ String sdkFileId = "";
+
+ switch (msgType) {
+ case "image":
+ suffix = ".jpg";
+ md5Sum = decryptData.getImage().getMd5Sum();
+ sdkFileId = decryptData.getImage().getSdkFileId();
+ break;
+ case "voice":
+ suffix = ".amr";
+ md5Sum = decryptData.getVoice().getMd5Sum();
+ sdkFileId = decryptData.getVoice().getSdkFileId();
+ break;
+ case "video":
+ suffix = ".mp4";
+ md5Sum = decryptData.getVideo().getMd5Sum();
+ sdkFileId = decryptData.getVideo().getSdkFileId();
+ break;
+ case "file":
+ md5Sum = decryptData.getFile().getMd5Sum();
+ suffix = "." + decryptData.getFile().getFileExt();
+ sdkFileId = decryptData.getFile().getSdkFileId();
+ break;
+ default:
+ // 未知消息类型,跳过处理
+ continue;
+ }
+
+ // 测试新的downloadMediaFile方法 - 不需要传入SDK
+ String path = Thread.currentThread().getContextClassLoader().getResource("").getPath();
+ String targetPath = path + "testfile-new/" + md5Sum + suffix;
+ File file = new File(targetPath);
+
+ // 确保父目录存在
+ if (!file.getParentFile().exists()) {
+ file.getParentFile().mkdirs();
+ }
+
+ // 删除已存在的文件
+ if (file.exists()) {
+ file.delete();
+ }
+
+ // 使用新的API下载媒体文件
+ msgAuditService.downloadMediaFile(sdkFileId, null, null, 1000L, targetPath);
+ log.info("媒体文件下载成功:{}", targetPath);
+ }
+ }
+
+ // 注意:使用新API无需手动调用 Finance.DestroySdk(),SDK由框架自动管理
+ }
+
// 测试Uint64类型
public static void main(String[] args){
/*
diff --git a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/tp/service/impl/WxCpTpCustomizedServiceImplTest.java b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/tp/service/impl/WxCpTpCustomizedServiceImplTest.java
new file mode 100644
index 0000000000..23babff412
--- /dev/null
+++ b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/tp/service/impl/WxCpTpCustomizedServiceImplTest.java
@@ -0,0 +1,178 @@
+package me.chanjar.weixin.cp.tp.service.impl;
+
+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.config.impl.WxCpTpDefaultConfigImpl;
+import me.chanjar.weixin.cp.tp.service.WxCpTpCustomizedService;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+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;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.when;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+
+/**
+ * 代开发相关接口测试
+ *
+ * @author Binary Wang
+ * created on 2026年01月14日
+ */
+public class WxCpTpCustomizedServiceImplTest {
+
+ @Mock
+ private WxCpTpServiceApacheHttpClientImpl wxCpTpService;
+
+ private WxCpTpConfigStorage configStorage;
+
+ private WxCpTpCustomizedService wxCpTpCustomizedService;
+
+ /**
+ * Sets up.
+ */
+ @BeforeClass
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ configStorage = new WxCpTpDefaultConfigImpl();
+ when(wxCpTpService.getWxCpTpConfigStorage()).thenReturn(configStorage);
+ wxCpTpCustomizedService = new WxCpTpCustomizedServiceImpl(wxCpTpService);
+ }
+
+ /**
+ * 测试获取应用模板列表
+ *
+ * @throws WxErrorException the wx error exception
+ */
+ @Test
+ public void testGetTemplateList() throws WxErrorException {
+ String result = "{\n" +
+ " \"errcode\": 0,\n" +
+ " \"errmsg\": \"ok\",\n" +
+ " \"template_list\": [\n" +
+ " {\n" +
+ " \"template_id\": \"tpl001\",\n" +
+ " \"template_type\": 1,\n" +
+ " \"app_name\": \"测试应用\",\n" +
+ " \"logo_url\": \"https://example.com/logo.png\",\n" +
+ " \"app_desc\": \"这是一个测试应用\",\n" +
+ " \"status\": 1\n" +
+ " }\n" +
+ " ]\n" +
+ "}";
+
+ String url = configStorage.getApiUrl(GET_TEMPLATE_LIST);
+ when(wxCpTpService.getWxCpProviderToken()).thenReturn("mock_provider_token");
+ when(wxCpTpService.get(eq(url + "?provider_access_token=mock_provider_token"), eq(null), eq(true)))
+ .thenReturn(result);
+
+ final WxCpTpTemplateList templateList = wxCpTpCustomizedService.getTemplateList();
+
+ assertNotNull(templateList);
+ assertEquals(templateList.getErrcode(), Long.valueOf(0));
+ assertNotNull(templateList.getTemplateList());
+ assertEquals(templateList.getTemplateList().size(), 1);
+ assertEquals(templateList.getTemplateList().get(0).getTemplateId(), "tpl001");
+ assertEquals(templateList.getTemplateList().get(0).getAppName(), "测试应用");
+ }
+
+ /**
+ * 测试获取代开发应用详情
+ *
+ * @throws WxErrorException the wx error exception
+ */
+ @Test
+ public void testGetCustomizedAppDetail() throws WxErrorException {
+ String authCorpId = "ww1234567890abcdef";
+ Integer agentId = 1000001;
+
+ String result = "{\n" +
+ " \"errcode\": 0,\n" +
+ " \"errmsg\": \"ok\",\n" +
+ " \"auth_corpid\": \"ww1234567890abcdef\",\n" +
+ " \"auth_corp_name\": \"测试企业\",\n" +
+ " \"auth_corp_square_logo_url\": \"https://example.com/square_logo.png\",\n" +
+ " \"auth_corp_round_logo_url\": \"https://example.com/round_logo.png\",\n" +
+ " \"auth_corp_type\": 1,\n" +
+ " \"auth_corp_qrcode_url\": \"https://example.com/qrcode.png\",\n" +
+ " \"auth_corp_user_limit\": 200,\n" +
+ " \"auth_corp_full_name\": \"测试企业有限公司\",\n" +
+ " \"auth_corp_verified_type\": 2,\n" +
+ " \"auth_corp_industry\": \"互联网\",\n" +
+ " \"auth_corp_sub_industry\": \"软件服务\",\n" +
+ " \"auth_corp_location\": \"广东省深圳市\",\n" +
+ " \"customized_app_list\": [\n" +
+ " {\n" +
+ " \"agentid\": 1000001,\n" +
+ " \"template_id\": \"tpl001\",\n" +
+ " \"name\": \"测试应用\",\n" +
+ " \"description\": \"这是一个测试应用\",\n" +
+ " \"logo_url\": \"https://example.com/logo.png\",\n" +
+ " \"allow_userinfos\": {\n" +
+ " \"user\": [\n" +
+ " {\"userid\": \"zhangsan\"}\n" +
+ " ],\n" +
+ " \"department\": [\n" +
+ " {\"id\": 1}\n" +
+ " ]\n" +
+ " },\n" +
+ " \"close\": 0,\n" +
+ " \"home_url\": \"https://example.com/home\",\n" +
+ " \"app_type\": 0\n" +
+ " }\n" +
+ " ]\n" +
+ "}";
+
+ String url = configStorage.getApiUrl(GET_CUSTOMIZED_APP_DETAIL);
+ when(wxCpTpService.getWxCpProviderToken()).thenReturn("mock_provider_token");
+ when(wxCpTpService.post(eq(url + "?provider_access_token=mock_provider_token"), any(String.class), eq(true)))
+ .thenReturn(result);
+
+ final WxCpTpCustomizedAppDetail appDetail = wxCpTpCustomizedService.getCustomizedAppDetail(authCorpId, agentId);
+
+ assertNotNull(appDetail);
+ assertEquals(appDetail.getErrcode(), Long.valueOf(0));
+ assertEquals(appDetail.getAuthCorpId(), authCorpId);
+ assertEquals(appDetail.getAuthCorpName(), "测试企业");
+ assertNotNull(appDetail.getCustomizedAppList());
+ assertEquals(appDetail.getCustomizedAppList().size(), 1);
+ assertEquals(appDetail.getCustomizedAppList().get(0).getAgentId(), agentId);
+ assertEquals(appDetail.getCustomizedAppList().get(0).getTemplateId(), "tpl001");
+ assertEquals(appDetail.getCustomizedAppList().get(0).getName(), "测试应用");
+ }
+
+ /**
+ * 测试获取代开发应用详情(不指定agentId)
+ *
+ * @throws WxErrorException the wx error exception
+ */
+ @Test
+ public void testGetCustomizedAppDetailWithoutAgentId() throws WxErrorException {
+ String authCorpId = "ww1234567890abcdef";
+
+ String result = "{\n" +
+ " \"errcode\": 0,\n" +
+ " \"errmsg\": \"ok\",\n" +
+ " \"auth_corpid\": \"ww1234567890abcdef\",\n" +
+ " \"auth_corp_name\": \"测试企业\",\n" +
+ " \"customized_app_list\": []\n" +
+ "}";
+
+ String url = configStorage.getApiUrl(GET_CUSTOMIZED_APP_DETAIL);
+ when(wxCpTpService.getWxCpProviderToken()).thenReturn("mock_provider_token");
+ when(wxCpTpService.post(eq(url + "?provider_access_token=mock_provider_token"), any(String.class), eq(true)))
+ .thenReturn(result);
+
+ final WxCpTpCustomizedAppDetail appDetail = wxCpTpCustomizedService.getCustomizedAppDetail(authCorpId, null);
+
+ assertNotNull(appDetail);
+ assertEquals(appDetail.getErrcode(), Long.valueOf(0));
+ assertEquals(appDetail.getAuthCorpId(), authCorpId);
+ }
+}
diff --git a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/tp/service/impl/WxCpTpEditionServiceImplTest.java b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/tp/service/impl/WxCpTpEditionServiceImplTest.java
index 7eebf1f306..5746c1e91a 100644
--- a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/tp/service/impl/WxCpTpEditionServiceImplTest.java
+++ b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/tp/service/impl/WxCpTpEditionServiceImplTest.java
@@ -11,8 +11,8 @@
import org.testng.annotations.Test;
import static me.chanjar.weixin.cp.constant.WxCpApiPathConsts.Tp.PROLONG_TRY;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.eq;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.when;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertNotNull;
@@ -34,7 +34,7 @@ public class WxCpTpEditionServiceImplTest {
*/
@BeforeClass
public void setUp() {
- MockitoAnnotations.initMocks(this);
+ MockitoAnnotations.openMocks(this);
configStorage = new WxCpTpDefaultConfigImpl();
when(wxCpTpService.getWxCpTpConfigStorage()).thenReturn(configStorage);
wxCpTpEditionService = new WxCpTpEditionServiceImpl(wxCpTpService);
diff --git a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/tp/service/impl/WxCpTpLicenseServiceImplTest.java b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/tp/service/impl/WxCpTpLicenseServiceImplTest.java
index 7ad22c6543..3398d0806f 100644
--- a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/tp/service/impl/WxCpTpLicenseServiceImplTest.java
+++ b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/tp/service/impl/WxCpTpLicenseServiceImplTest.java
@@ -16,8 +16,8 @@
import java.util.*;
import static me.chanjar.weixin.cp.constant.WxCpApiPathConsts.License.*;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.eq;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.when;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertNotNull;
@@ -41,7 +41,7 @@ public class WxCpTpLicenseServiceImplTest {
*/
@BeforeClass
public void setUp() {
- MockitoAnnotations.initMocks(this);
+ MockitoAnnotations.openMocks(this);
configStorage = new WxCpTpDefaultConfigImpl();
when(wxCpTpService.getWxCpTpConfigStorage()).thenReturn(configStorage);
wxCpTpLicenseService = new WxCpTpLicenseServiceImpl(wxCpTpService);
diff --git a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/tp/service/impl/WxCpTpOrderServiceImplTest.java b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/tp/service/impl/WxCpTpOrderServiceImplTest.java
index 91df78051d..db504fe24e 100644
--- a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/tp/service/impl/WxCpTpOrderServiceImplTest.java
+++ b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/tp/service/impl/WxCpTpOrderServiceImplTest.java
@@ -18,8 +18,8 @@
import static me.chanjar.weixin.cp.constant.WxCpApiPathConsts.Tp.GET_ORDER;
import static me.chanjar.weixin.cp.constant.WxCpApiPathConsts.Tp.GET_ORDER_LIST;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.eq;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.when;
import static org.testng.Assert.*;
@@ -40,7 +40,7 @@ public class WxCpTpOrderServiceImplTest {
*/
@BeforeClass
public void setUp() {
- MockitoAnnotations.initMocks(this);
+ MockitoAnnotations.openMocks(this);
configStorage = new WxCpTpDefaultConfigImpl();
when(wxCpTpService.getWxCpTpConfigStorage()).thenReturn(configStorage);
wxCpTpOrderService = new WxCpTpOrderServiceImpl(wxCpTpService);
diff --git a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/tp/service/impl/WxCpTpTagServiceImplTest.java b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/tp/service/impl/WxCpTpTagServiceImplTest.java
index 7ff33045fd..2939b8a8b1 100644
--- a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/tp/service/impl/WxCpTpTagServiceImplTest.java
+++ b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/tp/service/impl/WxCpTpTagServiceImplTest.java
@@ -17,7 +17,7 @@
import java.util.List;
import static me.chanjar.weixin.cp.constant.WxCpApiPathConsts.Tag.*;
-import static org.mockito.Matchers.*;
+import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.when;
import static org.testng.Assert.*;
@@ -41,7 +41,7 @@ public class WxCpTpTagServiceImplTest {
*/
@BeforeClass
public void setUp() {
- MockitoAnnotations.initMocks(this);
+ MockitoAnnotations.openMocks(this);
configStorage = new WxCpTpDefaultConfigImpl();
when(wxCpTpService.getWxCpTpConfigStorage()).thenReturn(configStorage);
wxCpTpTagService = new WxCpTpTagServiceImpl(wxCpTpService);
diff --git a/weixin-java-miniapp/pom.xml b/weixin-java-miniapp/pom.xml
index 340a15e58e..a3b1687b2e 100644
--- a/weixin-java-miniapp/pom.xml
+++ b/weixin-java-miniapp/pom.xml
@@ -31,6 +31,16 @@
okhttp
provided
+
+ org.apache.httpcomponents
+ httpclient
+ provided
+
+
+ org.apache.httpcomponents
+ httpmime
+ provided
+
org.apache.httpcomponents.client5
httpclient5
@@ -96,9 +106,9 @@
org.mockito
mockito-core
- 3.3.3
test
+
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaServiceHttpComponentsImpl.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaServiceHttpComponentsImpl.java
new file mode 100644
index 0000000000..526e1ec4e7
--- /dev/null
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaServiceHttpComponentsImpl.java
@@ -0,0 +1,101 @@
+package cn.binarywang.wx.miniapp.api.impl;
+
+import cn.binarywang.wx.miniapp.bean.WxMaStableAccessTokenRequest;
+import cn.binarywang.wx.miniapp.config.WxMaConfig;
+import lombok.extern.slf4j.Slf4j;
+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 org.apache.commons.lang3.StringUtils;
+import org.apache.hc.client5.http.classic.methods.HttpGet;
+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;
+
+/**
+ * Apache Http client5 方式实现
+ *
+ * @author zhangyl
+ */
+@Slf4j
+public class WxMaServiceHttpComponentsImpl extends BaseWxMaServiceImpl {
+ private CloseableHttpClient httpClient;
+ private HttpHost httpProxy;
+
+ @Override
+ public void initHttp() {
+ WxMaConfig configStorage = this.getWxMaConfig();
+ HttpComponentsClientBuilder apacheHttpClientBuilder = DefaultHttpComponentsClientBuilder.get();
+
+ apacheHttpClientBuilder.httpProxyHost(configStorage.getHttpProxyHost())
+ .httpProxyPort(configStorage.getHttpProxyPort())
+ .httpProxyUsername(configStorage.getHttpProxyUsername())
+ .httpProxyPassword(configStorage.getHttpProxyPassword() == null ? null :
+ configStorage.getHttpProxyPassword().toCharArray());
+
+ if (configStorage.getHttpProxyHost() != null && configStorage.getHttpProxyPort()> 0) {
+ this.httpProxy = new HttpHost(configStorage.getHttpProxyHost(), configStorage.getHttpProxyPort());
+ }
+
+ this.httpClient = apacheHttpClientBuilder.build();
+ }
+
+ @Override
+ public CloseableHttpClient getRequestHttpClient() {
+ return httpClient;
+ }
+
+ @Override
+ public HttpHost getRequestHttpProxy() {
+ return httpProxy;
+ }
+
+ @Override
+ public HttpClientType getRequestType() {
+ return HttpClientType.HTTP_COMPONENTS;
+ }
+
+ @Override
+ protected String doGetAccessTokenRequest() throws IOException {
+ String url = StringUtils.isNotEmpty(this.getWxMaConfig().getAccessTokenUrl()) ?
+ this.getWxMaConfig().getAccessTokenUrl() :
+ GET_ACCESS_TOKEN_URL.replace(WxMaConfig.DEFAULT_API_HOST_URL, this.getWxMaConfig().getEffectiveApiHostUrl());
+
+ url = String.format(url, this.getWxMaConfig().getAppid(), this.getWxMaConfig().getSecret());
+
+ HttpGet httpGet = new HttpGet(url);
+ if (this.getRequestHttpProxy() != null) {
+ RequestConfig config = RequestConfig.custom().setProxy(this.getRequestHttpProxy()).build();
+ httpGet.setConfig(config);
+ }
+ return getRequestHttpClient().execute(httpGet, BasicResponseHandler.INSTANCE);
+ }
+
+ @Override
+ protected String doGetStableAccessTokenRequest(boolean forceRefresh) throws IOException {
+ String url = StringUtils.isNotEmpty(this.getWxMaConfig().getAccessTokenUrl()) ?
+ this.getWxMaConfig().getAccessTokenUrl() :
+ GET_STABLE_ACCESS_TOKEN.replace(
+ WxMaConfig.DEFAULT_API_HOST_URL, this.getWxMaConfig().getEffectiveApiHostUrl());
+
+ HttpPost httpPost = new HttpPost(url);
+ if (this.getRequestHttpProxy() != null) {
+ RequestConfig config = RequestConfig.custom().setProxy(this.getRequestHttpProxy()).build();
+ httpPost.setConfig(config);
+ }
+ WxMaStableAccessTokenRequest wxMaAccessTokenRequest = new WxMaStableAccessTokenRequest();
+ wxMaAccessTokenRequest.setAppid(this.getWxMaConfig().getAppid());
+ wxMaAccessTokenRequest.setSecret(this.getWxMaConfig().getSecret());
+ wxMaAccessTokenRequest.setGrantType("client_credential");
+ wxMaAccessTokenRequest.setForceRefresh(forceRefresh);
+
+ httpPost.setEntity(new StringEntity(wxMaAccessTokenRequest.toJson(), ContentType.APPLICATION_JSON));
+ return getRequestHttpClient().execute(httpPost, BasicResponseHandler.INSTANCE);
+ }
+}
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaServiceImpl.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaServiceImpl.java
index 16478f8411..d0820a0c2e 100644
--- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaServiceImpl.java
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaServiceImpl.java
@@ -6,6 +6,6 @@
* @author Binary Wang
*/
@Slf4j
-public class WxMaServiceImpl extends WxMaServiceHttpClientImpl {
+public class WxMaServiceImpl extends WxMaServiceHttpComponentsImpl {
}
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/config/WxMaConfig.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/config/WxMaConfig.java
index 8164d48346..e39836c061 100644
--- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/config/WxMaConfig.java
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/config/WxMaConfig.java
@@ -13,6 +13,11 @@
*/
public interface WxMaConfig {
+ /**
+ * 设置更新access_token之前的回调
+ *
+ * @param updateAccessTokenBefore 回调函数
+ */
default void setUpdateAccessTokenBefore(Consumer updateAccessTokenBefore) {}
/**
@@ -23,8 +28,18 @@ default void setUpdateAccessTokenBefore(Consumer updateAcce
String getAccessToken();
// region 稳定版access token
+ /**
+ * 是否使用稳定版access_token
+ *
+ * @return 是否使用稳定版access_token
+ */
boolean isStableAccessToken();
+ /**
+ * 设置是否使用稳定版access_token
+ *
+ * @param useStableAccessToken 是否使用稳定版access_token
+ */
void useStableAccessToken(boolean useStableAccessToken);
// endregion
@@ -65,6 +80,12 @@ default void updateAccessToken(WxAccessToken accessToken) {
*/
void updateAccessToken(String accessToken, int expiresInSeconds);
+ /**
+ * 更新access_token处理器
+ *
+ * @param accessToken 新的 access_token 值
+ * @param expiresInSeconds 过期时间,单位:秒
+ */
default void updateAccessTokenProcessor(String accessToken, int expiresInSeconds) {
WxAccessTokenEntity wxAccessTokenEntity = new WxAccessTokenEntity();
wxAccessTokenEntity.setAppid(getAppid());
@@ -74,6 +95,11 @@ default void updateAccessTokenProcessor(String accessToken, int expiresInSeconds
updateAccessToken(accessToken, expiresInSeconds);
}
+ /**
+ * 更新access_token之前的回调
+ *
+ * @param wxAccessTokenEntity access_token实体
+ */
default void updateAccessTokenBefore(WxAccessTokenEntity wxAccessTokenEntity) {}
/**
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/util/crypt/WxMaCryptUtils.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/util/crypt/WxMaCryptUtils.java
index 08346dbbb8..2343634bfc 100644
--- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/util/crypt/WxMaCryptUtils.java
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/util/crypt/WxMaCryptUtils.java
@@ -36,6 +36,7 @@ public WxMaCryptUtils(WxMaConfig config) {
* @param sessionKey session_key
* @param encryptedData 消息密文
* @param ivStr iv字符串
+ * @return 解密后的字符串
*/
public static String decrypt(String sessionKey, String encryptedData, String ivStr) {
try {
@@ -58,6 +59,7 @@ public static String decrypt(String sessionKey, String encryptedData, String ivS
* @param sessionKey session_key
* @param encryptedData 消息密文
* @param ivStr iv字符串
+ * @return 解密后的字符串
*/
public static String decryptAnotherWay(String sessionKey, String encryptedData, String ivStr) {
byte[] keyBytes = Base64.decodeBase64(sessionKey.getBytes(UTF_8));
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/util/xml/XStreamTransformer.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/util/xml/XStreamTransformer.java
index f36d8c8fbd..b9e80d7341 100644
--- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/util/xml/XStreamTransformer.java
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/util/xml/XStreamTransformer.java
@@ -24,7 +24,12 @@ public class XStreamTransformer {
}
/**
- * xml -> pojo.
+ * {@code xml -> pojo.}
+ *
+ * @param 返回类型
+ * @param clazz 类型
+ * @param xml xml字符串
+ * @return 转换后的对象
*/
@SuppressWarnings("unchecked")
public static T fromXml(Class clazz, String xml) {
@@ -32,6 +37,14 @@ public static T fromXml(Class clazz, String xml) {
return object;
}
+ /**
+ * {@code xml -> pojo.}
+ *
+ * @param 返回类型
+ * @param clazz 类型
+ * @param is 输入流
+ * @return 转换后的对象
+ */
@SuppressWarnings("unchecked")
public static T fromXml(Class clazz, InputStream is) {
T object = (T) CLASS_2_XSTREAM_INSTANCE.get(clazz).fromXML(is);
@@ -39,7 +52,12 @@ public static T fromXml(Class clazz, InputStream is) {
}
/**
- * pojo -> xml.
+ * {@code pojo -> xml.}
+ *
+ * @param 类型参数
+ * @param clazz 类型
+ * @param object 对象
+ * @return xml字符串
*/
public static String toXml(Class clazz, T object) {
return CLASS_2_XSTREAM_INSTANCE.get(clazz).toXML(object);
diff --git a/weixin-java-mp/pom.xml b/weixin-java-mp/pom.xml
index c33aaeab86..823e7fd1f8 100644
--- a/weixin-java-mp/pom.xml
+++ b/weixin-java-mp/pom.xml
@@ -31,6 +31,16 @@
okhttp
provided
+
+ org.apache.httpcomponents
+ httpclient
+ provided
+
+
+ org.apache.httpcomponents
+ httpmime
+ provided
+
org.apache.httpcomponents.client5
httpclient5
@@ -44,9 +54,11 @@
org.mockito
- mockito-all
+ mockito-core
test
+
+
com.google.inject
guice
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpCardService.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpCardService.java
index 188e4be78b..ddce68e765 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpCardService.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpCardService.java
@@ -255,11 +255,9 @@ public interface WxMpCardService {
String addTestWhiteList(String openid) throws WxErrorException;
/**
- *
* 创建卡券
- *
*
- * @param cardCreateRequest 卡券创建请求对象
+ * @param cardCreateMessage 卡券创建请求对象
* @return 卡券创建结果对象
* @throws WxErrorException 微信API调用异常,可能包括:
*
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpCommentService.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpCommentService.java
index 25463061fe..738c1ea656 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpCommentService.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpCommentService.java
@@ -37,8 +37,8 @@ public interface WxMpCommentService {
* @param msgDataId 群发返回的msg_data_id
* @param index 多图文时,用来指定第几篇图文,从0开始,不带默认操作该msg_data_id的第一篇图文
* @param begin 起始位置
- * @param count 获取数目(>=50会被拒绝)
- * @param type type=0 普通评论&精选评论 type=1 普通评论 type=2 精选评论
+ * @param count 获取数目(大于等于50会被拒绝)
+ * @param type type=0 普通评论和精选评论 type=1 普通评论 type=2 精选评论
* @return 评论列表数据 wx mp comment list vo
* @throws WxErrorException 异常
*/
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpDataCubeService.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpDataCubeService.java
index d107444e21..83dd2342ec 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpDataCubeService.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpDataCubeService.java
@@ -60,10 +60,13 @@ public interface WxMpDataCubeService {
List getArticleSummary(Date beginDate, Date endDate) throws WxErrorException;
/**
- *
* 获取图文群发总数据(getarticletotal)
- * 详情请见文档:图文分析数据接口
+ *
+ * {@code 详情请见文档:图文分析数据接口}
+ *
+ *
* 接口url格式:https://api.weixin.qq.com/datacube/getarticletotal?access_token=ACCESS_TOKEN
+ *
*
* @param beginDate 开始时间
* @param endDate 最大时间跨度1天,endDate不能早于begingDate
@@ -73,10 +76,13 @@ public interface WxMpDataCubeService {
List getArticleTotal(Date beginDate, Date endDate) throws WxErrorException;
/**
- *
* 获取图文统计数据(getuserread)
- * 详情请见文档:图文分析数据接口
+ *
+ * {@code 详情请见文档:图文分析数据接口}
+ *
+ *
* 接口url格式:https://api.weixin.qq.com/datacube/getuserread?access_token=ACCESS_TOKEN
+ *
*
* @param beginDate 开始时间
* @param endDate 最大时间跨度3天,endDate不能早于begingDate
@@ -86,10 +92,13 @@ public interface WxMpDataCubeService {
List getUserRead(Date beginDate, Date endDate) throws WxErrorException;
/**
- *
* 获取图文统计分时数据(getuserreadhour)
- * 详情请见文档:图文分析数据接口
+ *
+ * {@code 详情请见文档:图文分析数据接口}
+ *
+ *
* 接口url格式:https://api.weixin.qq.com/datacube/getuserreadhour?access_token=ACCESS_TOKEN
+ *
*
* @param beginDate 开始时间
* @param endDate 最大时间跨度1天,endDate不能早于begingDate
@@ -99,10 +108,13 @@ public interface WxMpDataCubeService {
List getUserReadHour(Date beginDate, Date endDate) throws WxErrorException;
/**
- *
* 获取图文分享转发数据(getusershare)
- * 详情请见文档:图文分析数据接口
+ *
+ * {@code 详情请见文档:图文分析数据接口}
+ *
+ *
* 接口url格式:https://api.weixin.qq.com/datacube/getusershare?access_token=ACCESS_TOKEN
+ *
*
* @param beginDate 开始时间
* @param endDate 最大时间跨度7天,endDate不能早于begingDate
@@ -112,10 +124,13 @@ public interface WxMpDataCubeService {
List getUserShare(Date beginDate, Date endDate) throws WxErrorException;
/**
- *
* 获取图文分享转发分时数据(getusersharehour)
- * 详情请见文档:图文分析数据接口
+ *
+ * {@code 详情请见文档:图文分析数据接口}
+ *
+ *
* 接口url格式:https://api.weixin.qq.com/datacube/getusersharehour?access_token=ACCESS_TOKEN
+ *
*
* @param beginDate 开始时间
* @param endDate 最大时间跨度1天,endDate不能早于begingDate
@@ -127,10 +142,13 @@ public interface WxMpDataCubeService {
//*******************消息分析数据接口***********************//
/**
- *
* 获取消息发送概况数据(getupstreammsg)
- * 详情请见文档:消息分析数据接口
+ *
+ * {@code 详情请见文档:消息分析数据接口}
+ *
+ *
* 接口url格式:https://api.weixin.qq.com/datacube/getupstreammsg?access_token=ACCESS_TOKEN
+ *
*
* @param beginDate 开始时间
* @param endDate 最大时间跨度7天,endDate不能早于begingDate
@@ -140,10 +158,13 @@ public interface WxMpDataCubeService {
List getUpstreamMsg(Date beginDate, Date endDate) throws WxErrorException;
/**
- *
* 获取消息分送分时数据(getupstreammsghour)
- * 详情请见文档:消息分析数据接口
+ *
+ * {@code 详情请见文档:消息分析数据接口}
+ *
+ *
* 接口url格式:https://api.weixin.qq.com/datacube/getupstreammsghour?access_token=ACCESS_TOKEN
+ *
*
* @param beginDate 开始时间
* @param endDate 最大时间跨度1天,endDate不能早于begingDate
@@ -153,10 +174,13 @@ public interface WxMpDataCubeService {
List getUpstreamMsgHour(Date beginDate, Date endDate) throws WxErrorException;
/**
- *
* 获取消息发送周数据(getupstreammsgweek)
- * 详情请见文档:消息分析数据接口
+ *
+ * {@code 详情请见文档:消息分析数据接口}
+ *
+ *
* 接口url格式:https://api.weixin.qq.com/datacube/getupstreammsgweek?access_token=ACCESS_TOKEN
+ *
*
* @param beginDate 开始时间
* @param endDate 最大时间跨度30天,endDate不能早于begingDate
@@ -166,10 +190,13 @@ public interface WxMpDataCubeService {
List getUpstreamMsgWeek(Date beginDate, Date endDate) throws WxErrorException;
/**
- *
* 获取消息发送月数据(getupstreammsgmonth)
- * 详情请见文档:消息分析数据接口
+ *
+ * {@code 详情请见文档:消息分析数据接口}
+ *
+ *
* 接口url格式:https://api.weixin.qq.com/datacube/getupstreammsgmonth?access_token=ACCESS_TOKEN
+ *
*
* @param beginDate 开始时间
* @param endDate 最大时间跨度30天,endDate不能早于begingDate
@@ -179,10 +206,13 @@ public interface WxMpDataCubeService {
List getUpstreamMsgMonth(Date beginDate, Date endDate) throws WxErrorException;
/**
- *
* 获取消息发送分布数据(getupstreammsgdist)
- * 详情请见文档:消息分析数据接口
+ *
+ * {@code 详情请见文档:消息分析数据接口}
+ *
+ *
* 接口url格式:https://api.weixin.qq.com/datacube/getupstreammsgdist?access_token=ACCESS_TOKEN
+ *
*
* @param beginDate 开始时间
* @param endDate 最大时间跨度15天,endDate不能早于begingDate
@@ -192,10 +222,13 @@ public interface WxMpDataCubeService {
List getUpstreamMsgDist(Date beginDate, Date endDate) throws WxErrorException;
/**
- *
* 获取消息发送分布周数据(getupstreammsgdistweek)
- * 详情请见文档:消息分析数据接口
+ *
+ * {@code 详情请见文档:消息分析数据接口}
+ *
+ *
* 接口url格式:https://api.weixin.qq.com/datacube/getupstreammsgdistweek?access_token=ACCESS_TOKEN
+ *
*
* @param beginDate 开始时间
* @param endDate 最大时间跨度30天,endDate不能早于begingDate
@@ -205,10 +238,13 @@ public interface WxMpDataCubeService {
List getUpstreamMsgDistWeek(Date beginDate, Date endDate) throws WxErrorException;
/**
- *
* 获取消息发送分布月数据(getupstreammsgdistmonth)
- * 详情请见文档:消息分析数据接口
+ *
+ * {@code 详情请见文档:消息分析数据接口}
+ *
+ *
* 接口url格式:https://api.weixin.qq.com/datacube/getupstreammsgdistmonth?access_token=ACCESS_TOKEN
+ *
*
* @param beginDate 开始时间
* @param endDate 最大时间跨度30天,endDate不能早于begingDate
@@ -220,10 +256,13 @@ public interface WxMpDataCubeService {
//*******************接口分析数据接口***********************//
/**
- *
* 获取接口分析数据(getinterfacesummary)
- * 详情请见文档:接口分析数据接口
+ *
+ * {@code 详情请见文档:接口分析数据接口}
+ *
+ *
* 接口url格式:https://api.weixin.qq.com/datacube/getinterfacesummary?access_token=ACCESS_TOKEN
+ *
*
* @param beginDate 开始时间
* @param endDate 最大时间跨度30天,endDate不能早于begingDate
@@ -233,10 +272,13 @@ public interface WxMpDataCubeService {
List getInterfaceSummary(Date beginDate, Date endDate) throws WxErrorException;
/**
- *
* 获取接口分析分时数据(getinterfacesummaryhour)
- * 详情请见文档:接口分析数据接口
+ *
+ * {@code 详情请见文档:接口分析数据接口}
+ *
+ *
* 接口url格式:https://api.weixin.qq.com/datacube/getinterfacesummaryhour?access_token=ACCESS_TOKEN
+ *
*
* @param beginDate 开始时间
* @param endDate 最大时间跨度1天,endDate不能早于begingDate
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpService.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpService.java
index 468dced138..2d965bf8de 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpService.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpService.java
@@ -54,10 +54,10 @@ public interface WxMpService extends WxService {
WxMpShortKeyResult fetchShorten(String shortKey) throws WxErrorException;
/**
- *
* 验证消息的确来自微信服务器.
- * 详情请见: http://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421135319&token=&lang=zh_CN
- *
+ *
+ * {@code 详情请见: 接入指南}
+ *
*
* @param timestamp 时间戳,字符串格式
* @param nonce 随机串,字符串格式
@@ -76,16 +76,19 @@ public interface WxMpService extends WxService {
String getAccessToken() throws WxErrorException;
/**
- *
* 获取access_token,本方法线程安全.
+ *
* 且在多线程同时刷新时只刷新一次,避免超出2000次/日的调用次数上限
- *
+ *
+ *
* 另:本service的所有方法都会在access_token过期时调用此方法
- *
+ *
+ *
* 程序员在非必要情况下尽量不要主动调用此方法
- *
- * 详情请见: http://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140183&token=&lang=zh_CN
- *
+ *
+ *
+ * {@code 详情请见: 获取access_token}
+ *
*
* @param forceRefresh 是否强制刷新,true表示强制刷新,false表示使用缓存
* @return token access token,字符串格式
@@ -126,12 +129,13 @@ public interface WxMpService extends WxService {
String getJsapiTicket() throws WxErrorException;
/**
- *
* 获得jsapi_ticket.
+ *
* 获得时会检查jsapiToken是否过期,如果过期了,那么就刷新一下,否则就什么都不干
- *
- * 详情请见:http://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421141115&token=&lang=zh_CN
- *
+ *
+ *
+ * {@code 详情请见:JS-SDK使用权限签名算法}
+ *
*
* @param forceRefresh 强制刷新,true表示强制刷新,false表示使用缓存
* @return jsapi ticket,字符串格式
@@ -140,11 +144,10 @@ public interface WxMpService extends WxService {
String getJsapiTicket(boolean forceRefresh) throws WxErrorException;
/**
- *
* 创建调用jsapi时所需要的签名.
- *
- * 详情请见:http://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421141115&token=&lang=zh_CN
- *
+ *
+ * {@code 详情请见:JS-SDK使用权限签名算法}
+ *
*
* @param url 当前网页的URL,不包括#及其后面部分
* @return 生成的签名对象,包含签名、时间戳、随机串等信息
@@ -153,10 +156,10 @@ public interface WxMpService extends WxService {
WxJsapiSignature createJsapiSignature(String url) throws WxErrorException;
/**
- *
* 长链接转短链接接口.
- * 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=长链接转短链接接口
- *
+ *
+ * 详情请见: 长链接转短链接接口
+ *
*
* @param longUrl 长url,需要转换的原始URL
* @return 生成的短地址,字符串格式
@@ -167,10 +170,10 @@ public interface WxMpService extends WxService {
String shortUrl(String longUrl) throws WxErrorException;
/**
- *
* 语义查询接口.
- * 详情请见:http://mp.weixin.qq.com/wiki/index.php?title=语义理解
- *
+ *
+ * 详情请见:语义理解
+ *
*
* @param semanticQuery 查询条件,包含查询内容、类型等信息
* @return 查询结果,包含语义理解的结果和建议回复
@@ -179,11 +182,13 @@ public interface WxMpService extends WxService {
WxMpSemanticQueryResult semanticQuery(WxMpSemanticQuery semanticQuery) throws WxErrorException;
/**
- *
* 构造第三方使用网站应用授权登录的url.
- * 详情请见: 网站应用微信登录开发指南
- * URL格式为:https://open.weixin.qq.com/connect/qrconnect?appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=SCOPE&state=STATE#wechat_redirect
- *
+ *
+ * {@code 详情请见: 网站应用微信登录开发指南}
+ *
+ *
+ * {@code URL格式为:https://open.weixin.qq.com/connect/qrconnect?appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=SCOPE&state=STATE#wechat_redirect}
+ *
*
* @param redirectUri 用户授权完成后的重定向链接,无需urlencode, 方法内会进行encode
* @param scope 应用授权作用域,拥有多个作用域用逗号(,)分隔,网页应用目前仅填写snsapi_login即可
@@ -193,10 +198,7 @@ public interface WxMpService extends WxService {
String buildQrConnectUrl(String redirectUri, String scope, String state);
/**
- *
* 获取微信服务器IP地址
- * http://mp.weixin.qq.com/wiki/0/2ad4b6bfd29f30f71d39616c2a0fcedc.html
- *
*
* @return 微信服务器ip地址数组,包含所有微信服务器IP地址
* @throws WxErrorException 微信API调用异常
@@ -204,11 +206,10 @@ public interface WxMpService extends WxService {
String[] getCallbackIP() throws WxErrorException;
/**
- *
- * 网络检测
- * https://mp.weixin.qq.com/wiki?t=resource/res_main&id=21541575776DtsuT
- * 为了帮助开发者排查回调连接失败的问题,提供这个网络检测的API。它可以对开发者URL做域名解析,然后对所有IP进行一次ping操作,得到丢包率和耗时。
- *
+ * 网络检测
+ *
+ * 为了帮助开发者排查回调连接失败的问题,提供这个网络检测的API。它可以对开发者URL做域名解析,然后对所有IP进行一次ping操作,得到丢包率和耗时。
+ *
*
* @param action 执行的检测动作,可选值:all(全部检测)、dns(仅域名解析)、ping(仅网络连通性检测)
* @param operator 指定平台从某个运营商进行检测,可选值:CHINANET(中国电信)、UNICOM(中国联通)、CAP(中国联通)、CUCC(中国联通)
@@ -239,12 +240,13 @@ public interface WxMpService extends WxService {
WxMpCurrentAutoReplyInfo getCurrentAutoReplyInfo() throws WxErrorException;
/**
- *
- * 公众号调用或第三方平台帮公众号调用对公众号的所有api调用(包括第三方帮其调用)次数进行清零:
- * HTTP调用:https://api.weixin.qq.com/cgi-bin/clear_quota?access_token=ACCESS_TOKEN
- * 接口文档地址:https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1433744592
- *
- *
+ * 公众号调用或第三方平台帮公众号调用对公众号的所有api调用(包括第三方帮其调用)次数进行清零.
+ *
+ * HTTP调用:https://api.weixin.qq.com/cgi-bin/clear_quota?access_token=ACCESS_TOKEN
+ *
+ *
+ * {@code 接口文档地址:https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1433744592}
+ *
*
* @param appid 公众号的APPID,需要清零调用的公众号的appid
* @throws WxErrorException 微信API调用异常
@@ -252,11 +254,9 @@ public interface WxMpService extends WxService {
void clearQuota(String appid) throws WxErrorException;
/**
- *
* Service没有实现某个API的时候,可以用这个,
* 比{@link #get}和{@link #post}方法更灵活,可以自己构造RequestExecutor用来处理不同的参数和不同的返回类型。
* 可以参考,{@link MediaUploadRequestExecutor}的实现方法
- *
*
* @param 返回值类型
* @param 参数类型
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/BaseWxMpServiceImpl.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/BaseWxMpServiceImpl.java
index 63ca608eba..76ab466157 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/BaseWxMpServiceImpl.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/BaseWxMpServiceImpl.java
@@ -304,8 +304,9 @@ public String getAccessToken(boolean forceRefresh) throws WxErrorException {
/**
* 通过网络请求获取稳定版接口调用凭据
*
- * @return .
- * @throws IOException .
+ * @param forceRefresh 是否强制刷新
+ * @return access_token字符串
+ * @throws IOException IO异常
*/
protected abstract String doGetStableAccessTokenRequest(boolean forceRefresh) throws IOException;
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpServiceHttpComponentsImpl.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpServiceHttpComponentsImpl.java
index bbf065acfc..c54202ad2f 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpServiceHttpComponentsImpl.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpServiceHttpComponentsImpl.java
@@ -51,7 +51,8 @@ public void initHttp() {
apacheHttpClientBuilder.httpProxyHost(configStorage.getHttpProxyHost())
.httpProxyPort(configStorage.getHttpProxyPort())
.httpProxyUsername(configStorage.getHttpProxyUsername())
- .httpProxyPassword(configStorage.getHttpProxyPassword().toCharArray());
+ .httpProxyPassword(configStorage.getHttpProxyPassword() == null ? null :
+ configStorage.getHttpProxyPassword().toCharArray());
if (configStorage.getHttpProxyHost() != null && configStorage.getHttpProxyPort()> 0) {
this.httpProxy = new HttpHost(configStorage.getHttpProxyHost(), configStorage.getHttpProxyPort());
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpServiceImpl.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpServiceImpl.java
index 79c3fad266..7cef64e576 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpServiceImpl.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpServiceImpl.java
@@ -2,11 +2,11 @@
/**
*
- * 默认接口实现类,使用apache httpclient实现
+ * 默认接口实现类,使用apache httpClient 5实现
* Created by Binary Wang on 2017年5月27日.
*
*
* @author Binary Wang
*/
-public class WxMpServiceImpl extends WxMpServiceHttpClientImpl {
+public class WxMpServiceImpl extends WxMpServiceHttpComponentsImpl {
}
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/WxMpMassOpenIdsMessage.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/WxMpMassOpenIdsMessage.java
index 80e1658c16..936996ef69 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/WxMpMassOpenIdsMessage.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/WxMpMassOpenIdsMessage.java
@@ -25,11 +25,11 @@ public class WxMpMassOpenIdsMessage implements Serializable {
/**
*
* 请使用
- * {@link WxConsts.MassMsgType#IMAGE}
- * {@link WxConsts.MassMsgType#MPNEWS}
- * {@link WxConsts.MassMsgType#TEXT}
- * {@link WxConsts.MassMsgType#MPVIDEO}
- * {@link WxConsts.MassMsgType#VOICE}
+ * {@link me.chanjar.weixin.common.api.WxConsts.MassMsgType#IMAGE}
+ * {@link me.chanjar.weixin.common.api.WxConsts.MassMsgType#MPNEWS}
+ * {@link me.chanjar.weixin.common.api.WxConsts.MassMsgType#TEXT}
+ * {@link me.chanjar.weixin.common.api.WxConsts.MassMsgType#MPVIDEO}
+ * {@link me.chanjar.weixin.common.api.WxConsts.MassMsgType#VOICE}
* 如果msgtype和media_id不匹配的话,会返回系统繁忙的错误
*
*/
@@ -60,6 +60,8 @@ public String toJson() {
/**
* 添加openid,最多支持10,000个
+ *
+ * @param openid 用户openid
*/
public void addUser(String openid) {
this.toUsers.add(openid);
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/WxMpMassPreviewMessage.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/WxMpMassPreviewMessage.java
index dca743c9c3..57b34d352a 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/WxMpMassPreviewMessage.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/WxMpMassPreviewMessage.java
@@ -19,11 +19,11 @@ public class WxMpMassPreviewMessage implements Serializable {
*
* 消息类型
* 请使用
- * {@link WxConsts.MassMsgType#IMAGE}
- * {@link WxConsts.MassMsgType#MPNEWS}
- * {@link WxConsts.MassMsgType#TEXT}
- * {@link WxConsts.MassMsgType#MPVIDEO}
- * {@link WxConsts.MassMsgType#VOICE}
+ * {@link me.chanjar.weixin.common.api.WxConsts.MassMsgType#IMAGE}
+ * {@link me.chanjar.weixin.common.api.WxConsts.MassMsgType#MPNEWS}
+ * {@link me.chanjar.weixin.common.api.WxConsts.MassMsgType#TEXT}
+ * {@link me.chanjar.weixin.common.api.WxConsts.MassMsgType#MPVIDEO}
+ * {@link me.chanjar.weixin.common.api.WxConsts.MassMsgType#VOICE}
* 如果msgtype和media_id不匹配的话,会返回系统繁忙的错误
*
*/
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/WxMpMassTagMessage.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/WxMpMassTagMessage.java
index 598e5754f1..466ef8d96f 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/WxMpMassTagMessage.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/WxMpMassTagMessage.java
@@ -24,11 +24,11 @@ public class WxMpMassTagMessage implements Serializable {
*
* 消息类型.
* 请使用
- * {@link WxConsts.MassMsgType#IMAGE}
- * {@link WxConsts.MassMsgType#MPNEWS}
- * {@link WxConsts.MassMsgType#TEXT}
- * {@link WxConsts.MassMsgType#MPVIDEO}
- * {@link WxConsts.MassMsgType#VOICE}
+ * {@link me.chanjar.weixin.common.api.WxConsts.MassMsgType#IMAGE}
+ * {@link me.chanjar.weixin.common.api.WxConsts.MassMsgType#MPNEWS}
+ * {@link me.chanjar.weixin.common.api.WxConsts.MassMsgType#TEXT}
+ * {@link me.chanjar.weixin.common.api.WxConsts.MassMsgType#MPVIDEO}
+ * {@link me.chanjar.weixin.common.api.WxConsts.MassMsgType#VOICE}
* 如果msgtype和media_id不匹配的话,会返回系统繁忙的错误
*
*/
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/WxMpUserQuery.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/WxMpUserQuery.java
index 9e73b46159..ac4a596dd8 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/WxMpUserQuery.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/WxMpUserQuery.java
@@ -63,9 +63,8 @@ public WxMpUserQuery add(String openid, String lang) {
/**
* 添加一个OpenId到列表中,并返回本对象
*
- *
* 该方法默认lang = zh_CN
- *
+ *
*
* @param openid openid
* @return {@link WxMpUserQuery}
@@ -100,6 +99,8 @@ public WxMpUserQuery remove(String openid, String lang) {
/**
* 获取查询参数列表
+ *
+ * @return 查询参数列表
*/
public List getQueryParamList() {
return this.queryParamList;
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/card/WxMpCardMpnewsGethtmlResult.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/card/WxMpCardMpnewsGethtmlResult.java
index 6d7dde1ad6..34a9c56b99 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/card/WxMpCardMpnewsGethtmlResult.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/card/WxMpCardMpnewsGethtmlResult.java
@@ -8,7 +8,9 @@
/**
- * @author S
+ * 卡券图文消息HTML结果
+ *
+ * @author S (sshzh90@gmail.com)
*/
@Data
public class WxMpCardMpnewsGethtmlResult implements Serializable {
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/card/membercard/MemberCardActivateUserFormRequest.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/card/membercard/MemberCardActivateUserFormRequest.java
index d8634cfa3c..0adb413869 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/card/membercard/MemberCardActivateUserFormRequest.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/card/membercard/MemberCardActivateUserFormRequest.java
@@ -39,8 +39,8 @@ public class MemberCardActivateUserFormRequest implements Serializable {
/**
* 绑定老会员卡信息
*
- * @param name
- * @param url
+ * @param name 名称
+ * @param url 链接地址
*/
public void setBindOldCard(String name, String url) {
if (StringUtils.isAnyEmpty(name, url)) {
@@ -56,8 +56,8 @@ public void setBindOldCard(String name, String url) {
/**
* 设置服务声明,用于放置商户会员卡守则
*
- * @param name
- * @param url
+ * @param name 名称
+ * @param url 链接地址
*/
public void setServiceStatement(String name, String url) {
if (StringUtils.isAnyEmpty(name, url)) {
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/card/membercard/MemberCardUserForm.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/card/membercard/MemberCardUserForm.java
index 0c0fae3e2b..b3b0c9be5e 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/card/membercard/MemberCardUserForm.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/card/membercard/MemberCardUserForm.java
@@ -50,6 +50,7 @@ public class MemberCardUserForm implements Serializable {
/**
* 添加富文本类型字段
*
+ * @param field 富文本字段
*/
public void addRichField(MemberCardUserFormRichField field) {
if (field == null) {
@@ -64,6 +65,7 @@ public void addRichField(MemberCardUserFormRichField field) {
/**
* 添加微信选项类型字段
*
+ * @param fieldType 微信字段类型
*/
public void addWechatField(CardWechatFieldType fieldType) {
if (fieldType == null) {
@@ -78,6 +80,7 @@ public void addWechatField(CardWechatFieldType fieldType) {
/**
* 添加文本类型字段
*
+ * @param field 文本字段
*/
public void addCustomField(String field) {
if (StringUtils.isBlank(field)) {
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/card/membercard/NotifyOptional.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/card/membercard/NotifyOptional.java
index 139db68557..1ba8a0e60c 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/card/membercard/NotifyOptional.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/card/membercard/NotifyOptional.java
@@ -6,12 +6,13 @@
import java.io.Serializable;
/**
- *
* 控制原生消息结构体,包含各字段的消息控制字段。
- *
+ *
* 用于 `7 更新会员信息` 的接口参数调用
- * https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1451025283
- *
+ *
+ *
+ * {@code 参考:会员卡接口}
+ *
*
* @author YuJian(mgcnrx11@gmail.com)
* @version 2017年7月15日
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/card/membercard/WxMpMemberCardUpdateResult.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/card/membercard/WxMpMemberCardUpdateResult.java
index 663fe1f1e5..b4ad8eb139 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/card/membercard/WxMpMemberCardUpdateResult.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/card/membercard/WxMpMemberCardUpdateResult.java
@@ -6,10 +6,10 @@
import me.chanjar.weixin.mp.util.json.WxMpGsonBuilder;
/**
- *
* 用于 `7 更新会员信息` 的接口调用后的返回结果
- * https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1451025283
- *
+ *
+ * {@code 参考:会员卡接口}
+ *
*
* @author YuJian(mgcnrx11@gmail.com)
* @version 2017年7月15日
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/card/membercard/WxMpMemberCardUserInfoResult.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/card/membercard/WxMpMemberCardUserInfoResult.java
index 8fad40ccf8..9a2b47f5bf 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/card/membercard/WxMpMemberCardUserInfoResult.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/card/membercard/WxMpMemberCardUserInfoResult.java
@@ -6,11 +6,10 @@
import me.chanjar.weixin.mp.util.json.WxMpGsonBuilder;
/**
- *
* 拉取会员信息返回的结果
- *
- * 字段格式参考https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1451025283 6.2.1小节的步骤5
- *
+ *
+ * {@code 字段格式参考:会员卡接口 6.2.1小节的步骤5}
+ *
*
* @author YuJian
* @version 2017年7月9日
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/kefu/WxMpKefuMessage.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/kefu/WxMpKefuMessage.java
index f066c1d934..01be3c08d2 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/kefu/WxMpKefuMessage.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/kefu/WxMpKefuMessage.java
@@ -45,6 +45,8 @@ public class WxMpKefuMessage implements Serializable {
/**
* 获得文本消息builder.
+ *
+ * @return 文本消息builder
*/
public static TextBuilder TEXT() {
return new TextBuilder();
@@ -52,6 +54,8 @@ public static TextBuilder TEXT() {
/**
* 获得图片消息builder.
+ *
+ * @return 图片消息builder
*/
public static ImageBuilder IMAGE() {
return new ImageBuilder();
@@ -59,6 +63,8 @@ public static ImageBuilder IMAGE() {
/**
* 获得语音消息builder.
+ *
+ * @return 语音消息builder
*/
public static VoiceBuilder VOICE() {
return new VoiceBuilder();
@@ -66,6 +72,8 @@ public static VoiceBuilder VOICE() {
/**
* 获得视频消息builder.
+ *
+ * @return 视频消息builder
*/
public static VideoBuilder VIDEO() {
return new VideoBuilder();
@@ -73,6 +81,8 @@ public static VideoBuilder VIDEO() {
/**
* 获得音乐消息builder.
+ *
+ * @return 音乐消息builder
*/
public static MusicBuilder MUSIC() {
return new MusicBuilder();
@@ -80,6 +90,8 @@ public static MusicBuilder MUSIC() {
/**
* 获得图文消息(点击跳转到外链)builder.
+ *
+ * @return 图文消息builder
*/
public static NewsBuilder NEWS() {
return new NewsBuilder();
@@ -87,6 +99,8 @@ public static NewsBuilder NEWS() {
/**
* 获得图文消息(点击跳转到图文消息页面)builder.
+ *
+ * @return 图文消息builder
*/
public static MpNewsBuilder MPNEWS() {
return new MpNewsBuilder();
@@ -94,6 +108,8 @@ public static MpNewsBuilder MPNEWS() {
/**
* 获得卡券消息builder.
+ *
+ * @return 卡券消息builder
*/
public static WxCardBuilder WXCARD() {
return new WxCardBuilder();
@@ -101,6 +117,8 @@ public static WxCardBuilder WXCARD() {
/**
* 获得菜单消息builder.
+ *
+ * @return 菜单消息builder
*/
public static WxMsgMenuBuilder MSGMENU() {
return new WxMsgMenuBuilder();
@@ -108,20 +126,25 @@ public static WxMsgMenuBuilder MSGMENU() {
/**
* 小程序卡片.
+ *
+ * @return 小程序卡片builder
*/
public static MiniProgramPageBuilder MINIPROGRAMPAGE() {
return new MiniProgramPageBuilder();
}
/**
- * 发送图文消息(点击跳转到图文消息页面)使用通过 "发布" 系列接口得到的 article_id(草稿箱功能上线后不再支持客服接口中带 media_id 的 mpnews 类型的图文消息)
+ * 发送图文消息(点击跳转到图文消息页面)使用通过 "发布" 系列接口得到的 article_id
+ *
+ * @return 图文消息builder
*/
public static MpNewsArticleBuilder MPNEWSARTICLE() {
return new MpNewsArticleBuilder();
}
/**
- *
+ * 设置消息类型
+ *
* 请使用
* {@link me.chanjar.weixin.common.api.WxConsts.KefuMsgType#TEXT}
* {@link me.chanjar.weixin.common.api.WxConsts.KefuMsgType#IMAGE}
@@ -135,7 +158,9 @@ public static MpNewsArticleBuilder MPNEWSARTICLE() {
* {@link me.chanjar.weixin.common.api.WxConsts.KefuMsgType#TASKCARD}
* {@link me.chanjar.weixin.common.api.WxConsts.KefuMsgType#MSGMENU}
* {@link me.chanjar.weixin.common.api.WxConsts.KefuMsgType#MP_NEWS_ARTICLE}
- *
+ *
+ *
+ * @param msgType 消息类型
*/
public void setMsgType(String msgType) {
this.msgType = msgType;
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/message/WxMpXmlMessage.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/message/WxMpXmlMessage.java
index 3d5f4ac3a0..dfc88ab13b 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/message/WxMpXmlMessage.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/message/WxMpXmlMessage.java
@@ -927,6 +927,7 @@ public static WxMpXmlMessage fromXml(InputStream is) {
* @param timestamp 时间戳
* @param nonce 随机串
* @param msgSignature 签名串
+ * @return 解密后的消息对象
*/
public static WxMpXmlMessage fromEncryptedXml(String encryptedXml, WxMpConfigStorage wxMpConfigStorage,
String timestamp, String nonce, String msgSignature) {
@@ -956,14 +957,16 @@ public WxMpXmlMessage decryptField(WxMpConfigStorage wxMpConfigStorage,
/**
*
* 当接受用户消息时,可能会获得以下值:
- * {@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}
*
+ *
+ * @return 消息类型
*/
public String getMsgType() {
return this.msgType;
@@ -972,13 +975,15 @@ public String getMsgType() {
/**
*
* 当发送消息的时候使用:
- * {@link WxConsts.XmlMsgType#TEXT}
- * {@link WxConsts.XmlMsgType#IMAGE}
- * {@link WxConsts.XmlMsgType#VOICE}
- * {@link WxConsts.XmlMsgType#VIDEO}
- * {@link WxConsts.XmlMsgType#NEWS}
- * {@link WxConsts.XmlMsgType#MUSIC}
+ * {@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}
+ * {@link me.chanjar.weixin.common.api.WxConsts.XmlMsgType#MUSIC}
*
+ *
+ * @param msgType 消息类型
*/
public void setMsgType(String msgType) {
this.msgType = msgType;
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/message/WxMpXmlOutMessage.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/message/WxMpXmlOutMessage.java
index a44aea740c..1f3143df7e 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/message/WxMpXmlOutMessage.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/message/WxMpXmlOutMessage.java
@@ -70,6 +70,8 @@ public abstract class WxMpXmlOutMessage implements Serializable {
/**
* 获得文本消息builder
+ *
+ * @return 文本消息构建器
*/
public static TextBuilder TEXT() {
return new TextBuilder();
@@ -77,6 +79,8 @@ public static TextBuilder TEXT() {
/**
* 获得图片消息builder
+ *
+ * @return 图片消息构建器
*/
public static ImageBuilder IMAGE() {
return new ImageBuilder();
@@ -84,6 +88,8 @@ public static ImageBuilder IMAGE() {
/**
* 获得语音消息builder
+ *
+ * @return 语音消息构建器
*/
public static VoiceBuilder VOICE() {
return new VoiceBuilder();
@@ -91,6 +97,8 @@ public static VoiceBuilder VOICE() {
/**
* 获得视频消息builder
+ *
+ * @return 视频消息构建器
*/
public static VideoBuilder VIDEO() {
return new VideoBuilder();
@@ -98,6 +106,8 @@ public static VideoBuilder VIDEO() {
/**
* 获得音乐消息builder
+ *
+ * @return 音乐消息构建器
*/
public static MusicBuilder MUSIC() {
return new MusicBuilder();
@@ -105,6 +115,8 @@ public static MusicBuilder MUSIC() {
/**
* 获得图文消息builder
+ *
+ * @return 图文消息构建器
*/
public static NewsBuilder NEWS() {
return new NewsBuilder();
@@ -112,6 +124,8 @@ public static NewsBuilder NEWS() {
/**
* 获得客服消息builder
+ *
+ * @return 客服消息构建器
*/
public static TransferCustomerServiceBuilder TRANSFER_CUSTOMER_SERVICE() {
return new TransferCustomerServiceBuilder();
@@ -119,11 +133,18 @@ public static TransferCustomerServiceBuilder TRANSFER_CUSTOMER_SERVICE() {
/**
* 获得设备消息builder
+ *
+ * @return 设备消息builder
*/
public static DeviceBuilder DEVICE() {
return new DeviceBuilder();
}
+ /**
+ * 转换成xml格式
+ *
+ * @return xml格式字符串
+ */
@SuppressWarnings("unchecked")
public String toXml() {
return XStreamTransformer.toXml((Class) this.getClass(), this);
@@ -131,6 +152,9 @@ public String toXml() {
/**
* 转换成加密的结果
+ *
+ * @param wxMpConfigStorage 公众号配置
+ * @return 加密后的消息对象
*/
public WxMpXmlOutMessage toEncrypted(WxMpConfigStorage wxMpConfigStorage) {
String plainXml = toXml();
@@ -146,6 +170,9 @@ public WxMpXmlOutMessage toEncrypted(WxMpConfigStorage wxMpConfigStorage) {
/**
* 转换成加密的xml格式
+ *
+ * @param wxMpConfigStorage 公众号配置
+ * @return 加密后的xml格式字符串
*/
public String toEncryptedXml(WxMpConfigStorage wxMpConfigStorage) {
String plainXml = toXml();
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/result/WxMpMassGetResult.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/result/WxMpMassGetResult.java
index fe8f6e4043..58f2ea2693 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/result/WxMpMassGetResult.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/result/WxMpMassGetResult.java
@@ -9,7 +9,8 @@
/**
*
* 查询群发消息发送状态【订阅号与服务号认证后均可用】
- * https://developers.weixin.qq.com/doc/offiaccount/Message_Management/Batch_Sends_and_Originality_Checks.html#%E6%9F%A5%E8%AF%A2%E7%BE%A4%E5%8F%91%E6%B6%88%E6%81%AF%E5%8F%91%E9%80%81%E7%8A%B6%E6%80%81%E3%80%90%E8%AE%A2%E9%98%85%E5%8F%B7%E4%B8%8E%E6%9C%8D%E5%8A%A1%E5%8F%B7%E8%AE%A4%E8%AF%81%E5%90%8E%E5%9D%87%E5%8F%AF%E7%94%A8%E3%80%91
+ * 文档地址:https://developers.weixin.qq.com/doc/offiaccount/Message_Management/Batch_Sends_and_Originality_Checks.html
+ *
* @author S
*/
@Data
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/config/WxMpConfigStorage.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/config/WxMpConfigStorage.java
index 11aeef6124..1bebe86885 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/config/WxMpConfigStorage.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/config/WxMpConfigStorage.java
@@ -24,7 +24,7 @@ public interface WxMpConfigStorage {
* Is use stable access token api
*
* @return the boolean
- * @link https://developers.weixin.qq.com/doc/offiaccount/Basic_Information/getStableAccessToken.html
+ * @see 文档
*/
boolean isStableAccessToken();
@@ -211,6 +211,8 @@ public interface WxMpConfigStorage {
*
* {@link me.chanjar.weixin.mp.api.impl.BaseWxMpServiceImpl#setRetrySleepMillis(int)}
*
+ *
+ * @return 重试间隔毫秒数
*/
int getRetrySleepMillis();
@@ -219,6 +221,8 @@ public interface WxMpConfigStorage {
*
* {@link me.chanjar.weixin.mp.api.impl.BaseWxMpServiceImpl#setMaxRetryTimes(int)}
*
+ *
+ * @return 最大重试次数
*/
int getMaxRetryTimes();
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/config/impl/WxMpRedisConfigImpl.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/config/impl/WxMpRedisConfigImpl.java
index 870fa1e276..7939d57a18 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/config/impl/WxMpRedisConfigImpl.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/config/impl/WxMpRedisConfigImpl.java
@@ -39,6 +39,8 @@ public WxMpRedisConfigImpl(WxRedisOps redisOps, String keyPrefix) {
/**
* 每个公众号生成独有的存储key.
+ *
+ * @param appId 公众号appId
*/
@Override
public void setAppId(String appId) {
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/config/impl/WxMpRedissonConfigImpl.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/config/impl/WxMpRedissonConfigImpl.java
index e0d9e92af1..4982336f8a 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/config/impl/WxMpRedissonConfigImpl.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/config/impl/WxMpRedissonConfigImpl.java
@@ -42,6 +42,8 @@ private WxMpRedissonConfigImpl(@NonNull WxRedisOps redisOps, String keyPrefix) {
/**
* 每个公众号生成独有的存储key.
+ *
+ * @param appId 公众号appId
*/
@Override
public void setAppId(String appId) {
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/constant/WxMpEventConstants.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/constant/WxMpEventConstants.java
index b2e984b0f9..4dfee6295e 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/constant/WxMpEventConstants.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/constant/WxMpEventConstants.java
@@ -20,7 +20,7 @@ public class WxMpEventConstants {
public static final String SUBMIT_MEMBERCARD_USER_INFO = "submit_membercard_user_info";
/**
- * 微信摇一摇周边>>摇一摇事件通知.
+ * 微信摇一摇周边-摇一摇事件通知.
*/
public static final String SHAKEAROUND_USER_SHAKE = "ShakearoundUserShake";
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/crypto/WxMpCryptUtil.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/crypto/WxMpCryptUtil.java
index 99d759f32f..7757ad78bf 100755
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/crypto/WxMpCryptUtil.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/crypto/WxMpCryptUtil.java
@@ -27,7 +27,7 @@ public class WxMpCryptUtil extends me.chanjar.weixin.common.util.crypto.WxCryptU
/**
* 构造函数
*
- * @param wxMpConfigStorage
+ * @param wxMpConfigStorage 公众号配置存储
*/
public WxMpCryptUtil(WxMpConfigStorage wxMpConfigStorage) {
/*
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/xml/XStreamTransformer.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/xml/XStreamTransformer.java
index ace711a236..55e92700de 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/xml/XStreamTransformer.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/xml/XStreamTransformer.java
@@ -33,7 +33,12 @@ public class XStreamTransformer {
}
/**
- * xml -> pojo.
+ * {@code xml -> pojo.}
+ *
+ * @param 返回类型
+ * @param clazz 类型
+ * @param xml xml字符串
+ * @return 转换后的对象
*/
@SuppressWarnings("unchecked")
public static T fromXml(Class clazz, String xml) {
@@ -41,6 +46,14 @@ public static T fromXml(Class clazz, String xml) {
return object;
}
+ /**
+ * {@code xml -> pojo.}
+ *
+ * @param 返回类型
+ * @param clazz 类型
+ * @param is 输入流
+ * @return 转换后的对象
+ */
@SuppressWarnings("unchecked")
public static T fromXml(Class clazz, InputStream is) {
T object = (T) CLASS_2_XSTREAM_INSTANCE.get(clazz).fromXML(is);
@@ -48,7 +61,12 @@ public static T fromXml(Class clazz, InputStream is) {
}
/**
- * pojo -> xml.
+ * {@code pojo -> xml.}
+ *
+ * @param 类型参数
+ * @param clazz 类型
+ * @param object 对象
+ * @return xml字符串
*/
public static String toXml(Class clazz, T object) {
return CLASS_2_XSTREAM_INSTANCE.get(clazz).toXML(object);
diff --git a/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/impl/WxMpCommentServiceImplTest.java b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/impl/WxMpCommentServiceImplTest.java
index 0109f676ae..060dee10f8 100644
--- a/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/impl/WxMpCommentServiceImplTest.java
+++ b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/impl/WxMpCommentServiceImplTest.java
@@ -10,7 +10,7 @@
import org.testng.annotations.Test;
import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.Matchers.anyString;
+import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.spy;
diff --git a/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/impl/WxMpOcrServiceImplTest.java b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/impl/WxMpOcrServiceImplTest.java
index 2cc8b80119..7e9d477872 100644
--- a/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/impl/WxMpOcrServiceImplTest.java
+++ b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/impl/WxMpOcrServiceImplTest.java
@@ -24,7 +24,7 @@
import java.util.UUID;
import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.Matchers.anyString;
+import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
diff --git a/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/impl/WxMpWifiServiceImplTest.java b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/impl/WxMpWifiServiceImplTest.java
index d9225c7bc5..a8f79603fc 100644
--- a/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/impl/WxMpWifiServiceImplTest.java
+++ b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/impl/WxMpWifiServiceImplTest.java
@@ -11,8 +11,8 @@
import static me.chanjar.weixin.mp.enums.WxMpApiUrl.Wifi.BIZWIFI_SHOP_GET;
import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.Matchers.anyString;
-import static org.mockito.Matchers.eq;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
diff --git a/weixin-java-open/pom.xml b/weixin-java-open/pom.xml
index 4c34786dad..6720cd2b30 100644
--- a/weixin-java-open/pom.xml
+++ b/weixin-java-open/pom.xml
@@ -48,6 +48,16 @@
okhttp
provided
+
+ org.apache.httpcomponents
+ httpclient
+ provided
+
+
+ org.apache.httpcomponents
+ httpmime
+ provided
+
org.apache.httpcomponents.client5
httpclient5
diff --git a/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenServiceHttpComponentsImpl.java b/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenServiceHttpComponentsImpl.java
new file mode 100644
index 0000000000..913c1971cb
--- /dev/null
+++ b/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenServiceHttpComponentsImpl.java
@@ -0,0 +1,74 @@
+package me.chanjar.weixin.open.api.impl;
+
+import me.chanjar.weixin.common.bean.result.WxMinishopImageUploadResult;
+import me.chanjar.weixin.common.error.WxErrorException;
+import me.chanjar.weixin.common.util.http.HttpClientType;
+import me.chanjar.weixin.common.util.http.MinishopUploadRequestExecutor;
+import me.chanjar.weixin.common.util.http.SimpleGetRequestExecutor;
+import me.chanjar.weixin.common.util.http.SimplePostRequestExecutor;
+import me.chanjar.weixin.common.util.http.hc.DefaultHttpComponentsClientBuilder;
+import me.chanjar.weixin.common.util.http.hc.HttpComponentsClientBuilder;
+import me.chanjar.weixin.open.api.WxOpenConfigStorage;
+import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
+import org.apache.hc.core5.http.HttpHost;
+
+import java.io.File;
+
+/**
+ * httpclient 5 实现
+ *
+ * @author zhangyl
+ */
+public class WxOpenServiceHttpComponentsImpl extends WxOpenServiceAbstractImpl {
+ private CloseableHttpClient httpClient;
+ private HttpHost httpProxy;
+
+ @Override
+ public void initHttp() {
+ WxOpenConfigStorage configStorage = this.getWxOpenConfigStorage();
+ HttpComponentsClientBuilder apacheHttpClientBuilder = DefaultHttpComponentsClientBuilder.get();
+
+ apacheHttpClientBuilder.httpProxyHost(configStorage.getHttpProxyHost())
+ .httpProxyPort(configStorage.getHttpProxyPort())
+ .httpProxyUsername(configStorage.getHttpProxyUsername())
+ .httpProxyPassword(configStorage.getHttpProxyPassword() == null ? null :
+ configStorage.getHttpProxyPassword().toCharArray());
+
+ if (configStorage.getHttpProxyHost() != null && configStorage.getHttpProxyPort()> 0) {
+ this.httpProxy = new HttpHost(configStorage.getHttpProxyHost(), configStorage.getHttpProxyPort());
+ }
+
+ this.httpClient = apacheHttpClientBuilder.build();
+
+ }
+
+ @Override
+ public CloseableHttpClient getRequestHttpClient() {
+ return httpClient;
+ }
+
+ @Override
+ public HttpHost getRequestHttpProxy() {
+ return httpProxy;
+ }
+
+ @Override
+ public HttpClientType getRequestType() {
+ return HttpClientType.HTTP_COMPONENTS;
+ }
+
+ @Override
+ public String get(String url, String queryParam) throws WxErrorException {
+ return execute(SimpleGetRequestExecutor.create(this), url, queryParam);
+ }
+
+ @Override
+ public String post(String url, String postData) throws WxErrorException {
+ return execute(SimplePostRequestExecutor.create(this), url, postData);
+ }
+
+ @Override
+ public WxMinishopImageUploadResult uploadMinishopMediaFile(String url, File file) throws WxErrorException {
+ return execute(MinishopUploadRequestExecutor.create(this), url, file);
+ }
+}
diff --git a/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenServiceImpl.java b/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenServiceImpl.java
index c807ccdf99..83e831df70 100644
--- a/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenServiceImpl.java
+++ b/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenServiceImpl.java
@@ -3,6 +3,6 @@
/**
* @author 007
*/
-public class WxOpenServiceImpl extends WxOpenServiceApacheHttpClientImpl {
+public class WxOpenServiceImpl extends WxOpenServiceHttpComponentsImpl {
}
diff --git a/weixin-java-pay/MULTI_APPID_USAGE.md b/weixin-java-pay/MULTI_APPID_USAGE.md
new file mode 100644
index 0000000000..e4a7d0b9eb
--- /dev/null
+++ b/weixin-java-pay/MULTI_APPID_USAGE.md
@@ -0,0 +1,200 @@
+# 支持一个商户号对应多个 appId 的使用说明
+
+## 背景
+
+在实际业务中,经常会遇到一个微信支付商户号需要绑定多个小程序的场景。例如:
+- 一个商家有多个小程序(主店、分店、活动小程序等)
+- 所有小程序共用同一个支付商户号
+- 支付配置(商户号、密钥、证书等)完全相同,只有 appId 不同
+
+## 解决方案
+
+WxJava 支持在配置多个相同商户号、不同 appId 的情况下,**可以仅通过商户号进行配置切换**,无需每次都指定 appId。
+
+## 使用方式
+
+### 1. 配置多个 appId
+
+```java
+WxPayService payService = new WxPayServiceImpl();
+
+String mchId = "1234567890"; // 商户号
+
+// 配置小程序1
+WxPayConfig config1 = new WxPayConfig();
+config1.setMchId(mchId);
+config1.setAppId("wx1111111111111111"); // 小程序1的appId
+config1.setMchKey("your_mch_key");
+config1.setApiV3Key("your_api_v3_key");
+// ... 其他配置
+
+// 配置小程序2
+WxPayConfig config2 = new WxPayConfig();
+config2.setMchId(mchId);
+config2.setAppId("wx2222222222222222"); // 小程序2的appId
+config2.setMchKey("your_mch_key");
+config2.setApiV3Key("your_api_v3_key");
+// ... 其他配置
+
+// 配置小程序3
+WxPayConfig config3 = new WxPayConfig();
+config3.setMchId(mchId);
+config3.setAppId("wx3333333333333333"); // 小程序3的appId
+config3.setMchKey("your_mch_key");
+config3.setApiV3Key("your_api_v3_key");
+// ... 其他配置
+
+// 添加到配置映射
+Map configMap = new HashMap();
+configMap.put(mchId + "_" + config1.getAppId(), config1);
+configMap.put(mchId + "_" + config2.getAppId(), config2);
+configMap.put(mchId + "_" + config3.getAppId(), config3);
+
+payService.setMultiConfig(configMap);
+```
+
+### 2. 切换配置的方式
+
+#### 方式一:精确切换(原有方式,向后兼容)
+
+```java
+// 切换到小程序1的配置
+payService.switchover("1234567890", "wx1111111111111111");
+
+// 切换到小程序2的配置
+payService.switchover("1234567890", "wx2222222222222222");
+```
+
+#### 方式二:仅使用商户号切换(新功能)
+
+```java
+// 仅使用商户号切换,会自动匹配该商户号的某个配置
+// 适用于不关心具体使用哪个 appId 的场景
+boolean success = payService.switchover("1234567890");
+```
+
+**注意**:当使用仅商户号切换时,会按照以下逻辑查找配置:
+1. 先尝试精确匹配商户号(针对只配置商户号、没有 appId 的情况)
+2. 如果未找到,则尝试前缀匹配(查找以 `商户号_` 开头的配置)
+3. 如果有多个匹配项,将返回其中任意一个匹配项,具体选择结果不保证稳定或可预测,如需确定性行为请使用精确匹配方式(同时指定商户号和 appId)
+
+#### 方式三:链式调用
+
+```java
+// 精确切换,支持链式调用
+WxPayUnifiedOrderResult result = payService
+ .switchoverTo("1234567890", "wx1111111111111111")
+ .unifiedOrder(request);
+
+// 仅商户号切换,支持链式调用
+WxPayUnifiedOrderResult result = payService
+ .switchoverTo("1234567890")
+ .unifiedOrder(request);
+```
+
+### 3. 动态添加配置
+
+```java
+// 运行时动态添加新的 appId 配置
+WxPayConfig newConfig = new WxPayConfig();
+newConfig.setMchId("1234567890");
+newConfig.setAppId("wx4444444444444444");
+// ... 其他配置
+
+payService.addConfig("1234567890", "wx4444444444444444", newConfig);
+
+// 切换到新添加的配置
+payService.switchover("1234567890", "wx4444444444444444");
+```
+
+### 4. 移除配置
+
+```java
+// 移除特定的 appId 配置
+payService.removeConfig("1234567890", "wx1111111111111111");
+```
+
+## 实际应用场景
+
+### 场景1:根据用户来源切换 appId
+
+```java
+// 在支付前,根据订单来源切换到对应小程序的配置
+String orderSource = order.getSource(); // 例如: "miniapp1", "miniapp2"
+String appId = getAppIdBySource(orderSource);
+
+// 精确切换到特定小程序
+payService.switchover(mchId, appId);
+
+// 创建订单
+WxPayUnifiedOrderRequest request = new WxPayUnifiedOrderRequest();
+// ... 设置订单参数
+WxPayUnifiedOrderResult result = payService.unifiedOrder(request);
+```
+
+### 场景2:处理支付回调
+
+```java
+@PostMapping("/pay/notify")
+public String handlePayNotify(@RequestBody String xmlData) {
+ try {
+ // 解析回调通知
+ WxPayOrderNotifyResult notifyResult = payService.parseOrderNotifyResult(xmlData);
+
+ // 注意:parseOrderNotifyResult 方法内部会自动调用
+ // switchover(notifyResult.getMchId(), notifyResult.getAppid())
+ // 切换到正确的配置进行签名验证
+
+ // 处理业务逻辑
+ processOrder(notifyResult);
+
+ return WxPayNotifyResponse.success("成功");
+ } catch (WxPayException e) {
+ log.error("支付回调处理失败", e);
+ return WxPayNotifyResponse.fail("失败");
+ }
+}
+```
+
+### 场景3:不关心具体 appId 的场景
+
+```java
+// 某些场景下,只要是该商户号的配置即可,不关心具体是哪个 appId
+// 例如:查询订单、退款等操作
+
+// 仅使用商户号切换
+payService.switchover(mchId);
+
+// 查询订单
+WxPayOrderQueryResult queryResult = payService.queryOrder(null, outTradeNo);
+
+// 申请退款
+WxPayRefundRequest refundRequest = new WxPayRefundRequest();
+// ... 设置退款参数
+WxPayRefundResult refundResult = payService.refund(refundRequest);
+```
+
+## 注意事项
+
+1. **向后兼容**:所有原有的使用方式继续有效,不需要修改现有代码。
+
+2. **配置隔离**:每个 `mchId + appId` 组合都是独立的配置,修改一个配置不会影响其他配置。
+
+3. **线程安全**:配置切换使用 `WxPayConfigHolder`(基于 `ThreadLocal`),是线程安全的。
+
+4. **自动切换**:在处理支付回调时,SDK 会自动根据回调中的 `mchId` 和 `appId` 切换到正确的配置。
+
+5. **推荐实践**:
+ - 如果知道具体的 appId,建议使用精确切换方式,避免歧义
+ - 如果使用仅商户号切换,确保该商户号下至少有一个可用的配置
+
+## 相关 API
+
+| 方法 | 参数 | 返回值 | 说明 |
+|-----|------|--------|------|
+| `switchover(String mchId, String appId)` | 商户号, appId | boolean | 精确切换到指定配置 |
+| `switchover(String mchId)` | 商户号 | boolean | 仅使用商户号切换 |
+| `switchoverTo(String mchId, String appId)` | 商户号, appId | WxPayService | 精确切换,支持链式调用 |
+| `switchoverTo(String mchId)` | 商户号 | WxPayService | 仅商户号切换,支持链式调用 |
+| `addConfig(String mchId, String appId, WxPayConfig)` | 商户号, appId, 配置 | void | 动态添加配置 |
+| `removeConfig(String mchId, String appId)` | 商户号, appId | void | 移除指定配置 |
diff --git a/weixin-java-pay/pom.xml b/weixin-java-pay/pom.xml
index 9bf6d49a7a..43851df85d 100644
--- a/weixin-java-pay/pom.xml
+++ b/weixin-java-pay/pom.xml
@@ -34,6 +34,14 @@
jodd-util
6.1.0
+
+ org.apache.httpcomponents
+ httpclient
+
+
+ org.apache.httpcomponents
+ httpmime
+
org.apache.httpcomponents.client5
httpclient5
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/notify/MiPayNotifyV3Result.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/notify/MiPayNotifyV3Result.java
index a0641379fb..346d427fc4 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/notify/MiPayNotifyV3Result.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/notify/MiPayNotifyV3Result.java
@@ -13,7 +13,7 @@
*
*
* @author xgl
- * @date 2025年12月20日
+ * @since 2025年12月20日
*/
@Data
@NoArgsConstructor
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/notify/WxPayBaseNotifyV3Result.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/notify/WxPayBaseNotifyV3Result.java
index 86915d0956..364c9080d8 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/notify/WxPayBaseNotifyV3Result.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/notify/WxPayBaseNotifyV3Result.java
@@ -5,7 +5,8 @@
*
* @author Pursuer
* @version 1.0
- * @date 2023年6月15日
+ * @since 2023年6月15日
+ * @param 解密后的数据类型
*/
public interface WxPayBaseNotifyV3Result {
/**
@@ -13,9 +14,8 @@ public interface WxPayBaseNotifyV3Result {
*
* @param rawData 原始数据
* @author Pursuer
- * @date 2023年6月15日
- * @since 1.0
- **/
+ * @since 2023年6月15日
+ */
void setRawData(OriginNotifyResponse rawData);
/**
@@ -23,8 +23,7 @@ public interface WxPayBaseNotifyV3Result {
*
* @param data 解密后的数据
* @author Pursuer
- * @date 2023年6月15日
- * @since 1.0
- **/
+ * @since 2023年6月15日
+ */
void setResult(T data);
}
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/notify/WxPayNotifyV3Response.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/notify/WxPayNotifyV3Response.java
index b9d7f4d9f6..eac5f7c9de 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/notify/WxPayNotifyV3Response.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/notify/WxPayNotifyV3Response.java
@@ -28,8 +28,8 @@ public class WxPayNotifyV3Response {
/**
* 返回成功
*
- * @param msg
- * @return
+ * @param msg 返回消息
+ * @return 成功响应的JSON字符串
*/
public static String success(String msg) {
WxPayNotifyV3Response response = new WxPayNotifyV3Response(SUCCESS, msg);
@@ -40,7 +40,7 @@ public static String success(String msg) {
* 返回失败
*
* @param msg 返回信息,如非空,为错误原因
- * @return
+ * @return 失败响应的JSON字符串
*/
public static String fail(String msg) {
WxPayNotifyV3Response response = new WxPayNotifyV3Response(FAIL, msg);
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/request/WxPayRefundRequest.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/request/WxPayRefundRequest.java
index e145644d91..b0cbcf4e70 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/request/WxPayRefundRequest.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/request/WxPayRefundRequest.java
@@ -230,7 +230,9 @@ public void checkAndSign(WxPayConfig config) throws WxPayException {
if (StringUtils.isBlank(this.getOpUserId())) {
this.setOpUserId(config.getMchId());
}
-
+ if (StringUtils.isBlank(this.getNotifyUrl())) {
+ this.setNotifyUrl(config.getRefundNotifyUrl());
+ }
super.checkAndSign(config);
}
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/result/WxSignQueryResult.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/result/WxSignQueryResult.java
index 5241597194..af19aec60a 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/result/WxSignQueryResult.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/result/WxSignQueryResult.java
@@ -112,7 +112,7 @@ protected void loadXml(Document d) {
contractDisplayAccount = readXmlString(d, "contract_display_account");
contractState = readXmlInteger(d, "contract_state");
contractSignedTime = readXmlString(d, "contract_signed_time");
- contractExpiredTime = readXmlString(d, "contrace_Expired_time");
+ contractExpiredTime = readXmlString(d, "contract_expired_time");
contractTerminatedTime = readXmlString(d, "contract_terminated_time");
contractTerminatedMode = readXmlInteger(d, "contract_termination_mode");
contractTerminationRemark = readXmlString(d, "contract_termination_remark");
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/transfer/BusinessOperationTransferRequest.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/transfer/BusinessOperationTransferRequest.java
index 91d9438833..0129798ed9 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/transfer/BusinessOperationTransferRequest.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/transfer/BusinessOperationTransferRequest.java
@@ -6,8 +6,10 @@
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
+import lombok.experimental.Accessors;
import java.io.Serializable;
+import java.util.List;
/**
* 运营工具-商家转账请求参数
@@ -37,11 +39,13 @@ public class BusinessOperationTransferRequest implements Serializable {
private String outBillNo;
/**
- * 运营工具转账场景ID
- * 必须,用于标识运营工具转账的具体业务场景
+ * 转账场景ID
+ * 必须,该笔转账使用的转账场景,可前往"商户平台-产品中心-商家转账"中申请。
+ * 运营工具场景ID如:2001(现金营销)、2002(佣金报酬)、2003(推广奖励)等
+ * 可使用 {@link com.github.binarywang.wxpay.constant.WxPayConstants.OperationSceneId} 中定义的常量
*/
- @SerializedName("operation_scene_id")
- private String operationSceneId;
+ @SerializedName("transfer_scene_id")
+ private String transferSceneId;
/**
* 用户在直连商户应用下的用户标示
@@ -86,4 +90,36 @@ public class BusinessOperationTransferRequest implements Serializable {
*/
@SerializedName("notify_url")
private String notifyUrl;
-}
\ No newline at end of file
+
+ /**
+ * 转账场景报备信息
+ * 必须,需按转账场景准确填写报备信息,参考 转账场景报备信息字段说明
+ */
+ @SerializedName("transfer_scene_report_infos")
+ private List transferSceneReportInfos;
+
+ /**
+ * 转账场景报备信息
+ */
+ @Data
+ @Accessors(chain = true)
+ public static class TransferSceneReportInfo implements Serializable {
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 信息类型
+ * 必须,不能超过15个字符,商户所属转账场景下的信息类型,此字段内容为固定值,需严格按照
转账场景报备信息字段说明 传参。
+ */
+ @SerializedName("info_type")
+ private String infoType;
+
+ /**
+ * 信息内容
+ * 必须,不能超过32个字符,商户所属转账场景下的信息内容,商户可按实际业务场景自定义传参,需严格按照
转账场景报备信息字段说明 传参。
+ */
+ @SerializedName("info_content")
+ private String infoContent;
+
+ }
+
+}
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/transfer/BusinessOperationTransferResult.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/transfer/BusinessOperationTransferResult.java
index a380d6133e..91771b43e1 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/transfer/BusinessOperationTransferResult.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/transfer/BusinessOperationTransferResult.java
@@ -31,15 +31,27 @@ public class BusinessOperationTransferResult implements Serializable {
private String transferBillNo;
/**
- * 转账状态
- * WAIT_PAY:等待确认
- * PROCESSING:转账中
- * SUCCESS:转账成功
- * FAIL:转账失败
- * REFUND:已退款
+ * 单据状态
+ * 商家转账订单状态
+ * ACCEPTED:转账已受理,可原单重试(非终态)。
+ * PROCESSING: 转账锁定资金中。如果一直停留在该状态,建议检查账户余额是否足够,如余额不足,可充值后再原单重试(非终态)。
+ * WAIT_USER_CONFIRM: 待收款用户确认,当前转账单据资金已锁定,可拉起微信收款确认页面进行收款确认(非终态)。
+ * TRANSFERING: 转账中,可拉起微信收款确认页面再次重试确认收款(非终态)。
+ * SUCCESS: 转账成功,表示转账单据已成功(终态)。
+ * FAIL: 转账失败,表示该笔转账单据已失败。若需重新向用户转账,请重新生成单据并再次发起(终态)。
+ * CANCELING: 转账撤销中,商户撤销请求受理成功,该笔转账正在撤销中,需查单确认撤销的转账单据状态(非终态)。
+ * CANCELLED: 转账撤销完成,代表转账单据已撤销成功(终态)。
*/
- @SerializedName("transfer_state")
- private String transferState;
+ @SerializedName("state")
+ private String state;
+
+ /**
+ * 跳转领取页面的package信息
+ * 跳转微信支付收款页的package信息,
APP调起用户确认收款 或者
JSAPI调起用户确认收款 时需要使用的参数。仅当转账单据状态为WAIT_USER_CONFIRM时返回。
+ * 单据创建后,用户24小时内不领取将过期关闭,建议拉起用户确认收款页面前,先查单据状态:如单据状态为WAIT_USER_CONFIRM,可用之前的package信息拉起;单据到终态时需更换单号重新发起转账。
+ */
+ @SerializedName("package_info")
+ private String packageInfo;
/**
* 发起转账的时间
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/config/WxPayConfig.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/config/WxPayConfig.java
index a3a9dc7a92..88e544e675 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/config/WxPayConfig.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/config/WxPayConfig.java
@@ -97,9 +97,13 @@ public class WxPayConfig {
*/
private String subMchId;
/**
- * 微信支付异步回掉地址,通知url必须为直接可访问的url,不能携带参数.
+ * 微信支付异步回调地址,通知url必须为直接可访问的url,不能携带参数.
*/
private String notifyUrl;
+ /**
+ * 退款结果异步回调地址,通知url必须为直接可访问的url,不能携带参数.
+ */
+ private String refundNotifyUrl;
/**
* 交易类型.
*
@@ -325,7 +329,8 @@ public SSLContext initSSLContext() throws WxPayException {
*
* @return org.apache.http.impl.client.CloseableHttpClient
* @author doger.wang
- **/
+ * @throws WxPayException 微信支付异常
+ */
public CloseableHttpClient initApiV3HttpClient() throws WxPayException {
if (StringUtils.isBlank(this.getApiV3Key())) {
throw new WxPayException("请确保apiV3Key值已设置");
@@ -663,6 +668,8 @@ public CloseableHttpClient initSslHttpClient() throws WxPayException {
/**
* 配置HTTP代理
+ *
+ * @param httpClientBuilder HttpClient构建器
*/
private void configureProxy(org.apache.http.impl.client.HttpClientBuilder httpClientBuilder) {
if (StringUtils.isNotBlank(this.getHttpProxyHost()) && this.getHttpProxyPort()> 0) {
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/example/BusinessOperationTransferExample.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/example/BusinessOperationTransferExample.java
index d11738816b..117395ba62 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/example/BusinessOperationTransferExample.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/example/BusinessOperationTransferExample.java
@@ -8,6 +8,8 @@
import com.github.binarywang.wxpay.service.WxPayService;
import com.github.binarywang.wxpay.service.impl.WxPayServiceImpl;
+import java.util.Arrays;
+
/**
* 运营工具-商家转账API使用示例
*
@@ -41,10 +43,15 @@ public void init() {
public void createOperationTransferExample() {
try {
// 构建转账请求
+ BusinessOperationTransferRequest.TransferSceneReportInfo reportInfo = new BusinessOperationTransferRequest.TransferSceneReportInfo();
+ reportInfo.setInfoType("活动名称");
+ reportInfo.setInfoContent("新会员有礼");
+
BusinessOperationTransferRequest request = BusinessOperationTransferRequest.newBuilder()
.appid("your_app_id") // 应用ID
.outBillNo("OT" + System.currentTimeMillis()) // 商户转账单号
- .operationSceneId(WxPayConstants.OperationSceneId.OPERATION_CASH_MARKETING) // 运营工具转账场景ID
+ .transferSceneId(WxPayConstants.OperationSceneId.OPERATION_CASH_MARKETING) // 运营工具转账场景ID
+ .transferSceneReportInfos(Arrays.asList(reportInfo)) // 转账场景报备信息
.openid("user_openid") // 用户openid
.userName("张三") // 用户姓名(可选)
.transferAmount(100) // 转账金额,单位分
@@ -59,7 +66,8 @@ public void createOperationTransferExample() {
System.out.println("转账成功!");
System.out.println("商户单号: " + result.getOutBillNo());
System.out.println("微信转账单号: " + result.getTransferBillNo());
- System.out.println("转账状态: " + result.getTransferState());
+ System.out.println("单据状态: " + result.getState());
+ System.out.println("跳转领取页面的package信息: " + result.getPackageInfo());
System.out.println("创建时间: " + result.getCreateTime());
} catch (WxPayException e) {
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/WxPayService.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/WxPayService.java
index dab89a0142..2db2987d12 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/WxPayService.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/WxPayService.java
@@ -78,6 +78,18 @@ public interface WxPayService {
*/
boolean switchover(String mchId, String appId);
+ /**
+ * 仅根据商户号进行切换.
+ * 适用于一个商户号对应多个appId的场景,切换时会匹配符合该商户号的配置.
+ * 注意:由于HashMap迭代顺序不确定,当存在多个匹配项时返回的配置是不可预测的,建议使用精确匹配方式.
+ *
+ * @param mchId 商户标识
+ * @return 切换是否成功,如果找不到匹配的配置则返回false
+ */
+ default boolean switchover(String mchId) {
+ return false;
+ }
+
/**
* 进行相应的商户切换.
*
@@ -87,6 +99,19 @@ public interface WxPayService {
*/
WxPayService switchoverTo(String mchId, String appId);
+ /**
+ * 仅根据商户号进行切换.
+ * 适用于一个商户号对应多个appId的场景,切换时会匹配符合该商户号的配置.
+ * 注意:由于HashMap迭代顺序不确定,当存在多个匹配项时返回的配置是不可预测的,建议使用精确匹配方式.
+ *
+ * @param mchId 商户标识
+ * @return 切换成功,则返回当前对象,方便链式调用
+ * @throws me.chanjar.weixin.common.error.WxRuntimeException 如果找不到匹配的配置
+ */
+ default WxPayService switchoverTo(String mchId) {
+ throw new me.chanjar.weixin.common.error.WxRuntimeException("子类需要实现此方法");
+ }
+
/**
* 发送post请求,得到响应字节数组.
*
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/BaseWxPayServiceImpl.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/BaseWxPayServiceImpl.java
index 2e896cda7e..4b51c498d2 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/BaseWxPayServiceImpl.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/BaseWxPayServiceImpl.java
@@ -212,6 +212,34 @@ public boolean switchover(String mchId, String appId) {
return false;
}
+ @Override
+ public boolean switchover(String mchId) {
+ // 参数校验
+ if (StringUtils.isBlank(mchId)) {
+ log.error("商户号mchId不能为空");
+ return false;
+ }
+
+ // 先尝试精确匹配(针对只有mchId没有appId的配置)
+ if (this.configMap.containsKey(mchId)) {
+ WxPayConfigHolder.set(mchId);
+ return true;
+ }
+
+ // 尝试前缀匹配(查找以 mchId_ 开头的配置)
+ String prefix = mchId + "_";
+ for (String key : this.configMap.keySet()) {
+ if (key.startsWith(prefix)) {
+ WxPayConfigHolder.set(key);
+ log.debug("根据mchId=【{}】找到配置key=【{}】", mchId, key);
+ return true;
+ }
+ }
+
+ log.error("无法找到对应mchId=【{}】的商户号配置信息,请核实!", mchId);
+ return false;
+ }
+
@Override
public WxPayService switchoverTo(String mchId, String appId) {
String configKey = this.getConfigKey(mchId, appId);
@@ -222,6 +250,32 @@ public WxPayService switchoverTo(String mchId, String appId) {
throw new WxRuntimeException(String.format("无法找到对应mchId=【%s】,appId=【%s】的商户号配置信息,请核实!", mchId, appId));
}
+ @Override
+ public WxPayService switchoverTo(String mchId) {
+ // 参数校验
+ if (StringUtils.isBlank(mchId)) {
+ throw new WxRuntimeException("商户号mchId不能为空");
+ }
+
+ // 先尝试精确匹配(针对只有mchId没有appId的配置)
+ if (this.configMap.containsKey(mchId)) {
+ WxPayConfigHolder.set(mchId);
+ return this;
+ }
+
+ // 尝试前缀匹配(查找以 mchId_ 开头的配置)
+ String prefix = mchId + "_";
+ for (String key : this.configMap.keySet()) {
+ if (key.startsWith(prefix)) {
+ WxPayConfigHolder.set(key);
+ log.debug("根据mchId=【{}】找到配置key=【{}】", mchId, key);
+ return this;
+ }
+ }
+
+ throw new WxRuntimeException(String.format("无法找到对应mchId=【%s】的商户号配置信息,请核实!", mchId));
+ }
+
public String getConfigKey(String mchId, String appId) {
return mchId + "_" + appId;
}
@@ -263,6 +317,9 @@ public WxPayRefundResult refundV2(WxPayRefundRequest request) throws WxPayExcept
@Override
public WxPayRefundV3Result refundV3(WxPayRefundV3Request request) throws WxPayException {
+ if (StringUtils.isBlank(request.getNotifyUrl())) {
+ request.setNotifyUrl(this.getConfig().getRefundNotifyUrl());
+ }
String url = String.format("%s/v3/refund/domestic/refunds", this.getPayBaseUrl());
String response = this.postV3WithWechatpaySerial(url, GSON.toJson(request));
return GSON.fromJson(response, WxPayRefundV3Result.class);
@@ -270,6 +327,9 @@ public WxPayRefundV3Result refundV3(WxPayRefundV3Request request) throws WxPayEx
@Override
public WxPayRefundV3Result partnerRefundV3(WxPayPartnerRefundV3Request request) throws WxPayException {
+ if (StringUtils.isBlank(request.getNotifyUrl())) {
+ request.setNotifyUrl(this.getConfig().getRefundNotifyUrl());
+ }
if (StringUtils.isBlank(request.getSubMchid())) {
request.setSubMchid(this.getConfig().getSubMchId());
}
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/WxPayServiceImpl.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/WxPayServiceImpl.java
index 8e795966f4..4316bafa40 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/WxPayServiceImpl.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/WxPayServiceImpl.java
@@ -2,11 +2,11 @@
/**
*
- * 微信支付接口请求实现类,默认使用Apache HttpClient实现
+ * 微信支付接口请求实现类,默认使用Apache HttpClient 5实现
* Created by Binary Wang on 2017年7月8日.
*
*
* @author Binary Wang
*/
-public class WxPayServiceImpl extends WxPayServiceApacheHttpImpl {
+public class WxPayServiceImpl extends WxPayServiceHttpComponentsImpl {
}
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/util/RequestUtils.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/util/RequestUtils.java
index b641cbe537..c4ad966415 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/util/RequestUtils.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/util/RequestUtils.java
@@ -17,7 +17,7 @@ public class RequestUtils {
/**
* 获取请求头数据,微信V3版本回调使用
*
- * @param request
+ * @param request HTTP请求对象
* @return 字符串
*/
public static String readData(HttpServletRequest request) {
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/util/ResourcesUtils.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/util/ResourcesUtils.java
index ac68b00bb4..51dd8fbbb6 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/util/ResourcesUtils.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/util/ResourcesUtils.java
@@ -23,6 +23,10 @@ public class ResourcesUtils {
* - {@link Class#getClassLoader() ClassLoaderUtil.class.getClassLoader()}
*
- if
callingClass is provided: {@link Class#getClassLoader() callingClass.getClassLoader()}
*
+ *
+ * @param resourceName 资源名称
+ * @param classLoader 类加载器
+ * @return 资源URL
*/
public static URL getResourceUrl(String resourceName, final ClassLoader classLoader) {
@@ -64,6 +68,9 @@ public static URL getResourceUrl(String resourceName, final ClassLoader classLoa
/**
* Opens a resource of the specified name for reading.
*
+ * @param resourceName 资源名称
+ * @return 输入流
+ * @throws IOException IO异常
* @see #getResourceAsStream(String, ClassLoader)
*/
public static InputStream getResourceAsStream(final String resourceName) throws IOException {
@@ -73,6 +80,10 @@ public static InputStream getResourceAsStream(final String resourceName) throws
/**
* Opens a resource of the specified name for reading.
*
+ * @param resourceName 资源名称
+ * @param callingClass 类加载器
+ * @return 输入流
+ * @throws IOException IO异常
* @see #getResourceUrl(String, ClassLoader)
*/
public static InputStream getResourceAsStream(final String resourceName, final ClassLoader callingClass) throws IOException {
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/util/SignUtils.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/util/SignUtils.java
index 6c0009fd18..9d4a9b0237 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/util/SignUtils.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/util/SignUtils.java
@@ -112,7 +112,16 @@ public static String createSign(Map
params, String signType, Str
/**
* 企业微信签名
*
- * @param signType md5 目前接口要求使用的加密类型
+ * @param actName 活动名称
+ * @param mchBillNo 商户订单号
+ * @param mchId 商户号
+ * @param nonceStr 随机字符串
+ * @param reOpenid 用户openid
+ * @param totalAmount 金额
+ * @param wxAppId 微信appid
+ * @param signKey 签名密钥
+ * @param signType md5 目前接口要求使用的加密类型
+ * @return 签名字符串
*/
public static String createEntSign(String actName, String mchBillNo, String mchId, String nonceStr,
String reOpenid, Integer totalAmount, String wxAppId, String signKey,
@@ -131,7 +140,18 @@ public static String createEntSign(String actName, String mchBillNo, String mchI
/**
* 企业微信签名
- * @param signType md5 目前接口要求使用的加密类型
+ *
+ * @param totalAmount 金额
+ * @param appId 应用ID
+ * @param description 描述
+ * @param mchId 商户号
+ * @param nonceStr 随机字符串
+ * @param openid 用户openid
+ * @param partnerTradeNo 商户订单号
+ * @param wwMsgType 消息类型
+ * @param signKey 签名密钥
+ * @param signType md5 目前接口要求使用的加密类型
+ * @return 签名字符串
*/
public static String createEntSign(Integer totalAmount, String appId, String description, String mchId,
String nonceStr, String openid, String partnerTradeNo, String wwMsgType,
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/v3/util/RsaCryptoUtil.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/v3/util/RsaCryptoUtil.java
index 8c3e2ace53..0143022ece 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/v3/util/RsaCryptoUtil.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/v3/util/RsaCryptoUtil.java
@@ -14,8 +14,11 @@
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
+import java.util.ArrayList;
import java.util.Base64;
import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
/**
* 微信支付敏感信息加密
@@ -36,10 +39,26 @@ public static void encryptFields(Object encryptObject, X509Certificate certifica
}
}
+ /**
+ * 递归获取类的所有字段,包括父类中的字段
+ *
+ * @param clazz 要获取字段的类
+ * @return 所有字段的列表
+ */
+ private static List getAllFields(Class> clazz) {
+ List fields = new ArrayList();
+ while (clazz != null && clazz != Object.class) {
+ Field[] declaredFields = clazz.getDeclaredFields();
+ Collections.addAll(fields, declaredFields);
+ clazz = clazz.getSuperclass();
+ }
+ return fields;
+ }
+
private static void encryptField(Object encryptObject, X509Certificate certificate) throws IllegalAccessException, IllegalBlockSizeException {
Class> infoClass = encryptObject.getClass();
- Field[] infoFieldArray = infoClass.getDeclaredFields();
- for (Field field : infoFieldArray) {
+ List infoFieldList = getAllFields(infoClass);
+ for (Field field : infoFieldList) {
if (field.isAnnotationPresent(SpecEncrypt.class)) {
//字段使用了@SpecEncrypt进行标识
if (field.getType().getTypeName().equals(JAVA_LANG_STRING)) {
diff --git a/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/bean/result/WxSignQueryResultTest.java b/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/bean/result/WxSignQueryResultTest.java
new file mode 100644
index 0000000000..52df2b6e2b
--- /dev/null
+++ b/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/bean/result/WxSignQueryResultTest.java
@@ -0,0 +1,125 @@
+package com.github.binarywang.wxpay.bean.result;
+
+import com.github.binarywang.wxpay.util.XmlConfig;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+/**
+ * WxSignQueryResult 单元测试
+ *
+ * @author Binary Wang
+ */
+public class WxSignQueryResultTest {
+
+ /**
+ * 测试 XML 解析,特别是 contract_expired_time 字段
+ */
+ @Test
+ public void testFromXML() {
+ /*
+ * xml样例字符串来自于官方文档
+ * https://pay.weixin.qq.com/doc/v2/merchant/4011987640
+ */
+ String xmlString = "\n" +
+ " \n" +
+ " \n" +
+ " \n" +
+ " \n" +
+ " 203\n" +
+ " 66\n" +
+ " \n" +
+ " 123\n" +
+ " \n" +
+ " \n" +
+ " 1\n" +
+ " 2015年07月01日 10:00:00\n" +
+ " 2015年07月01日 10:00:00\n" +
+ " 2015年07月01日 10:00:00\n" +
+ " 3\n" +
+ " \n" +
+ " 0\n" +
+ " \n" +
+ " \n" +
+ "";
+
+ // 启用 fastMode 以覆盖 WxSignQueryResult#loadXml 分支
+ XmlConfig.fastMode = true;
+ try {
+ WxSignQueryResult result = WxSignQueryResult.fromXML(xmlString, WxSignQueryResult.class);
+
+ // 验证基本字段
+ Assert.assertEquals(result.getReturnCode(), "SUCCESS");
+ Assert.assertEquals(result.getResultCode(), "SUCCESS");
+ Assert.assertEquals(result.getMchId(), "80000000");
+ Assert.assertEquals(result.getAppid(), "wx426b3015555b46be");
+
+ // 验证签约相关字段
+ Assert.assertEquals(result.getContractId(), "203");
+ Assert.assertEquals(result.getPlanId(), "66");
+ Assert.assertEquals(result.getOpenId(), "oHZx6uMbIG46UXQ3SKxVYEgw1LZs");
+ Assert.assertEquals(result.getRequestSerial().longValue(), 123L);
+ Assert.assertEquals(result.getContractCode(), "1005");
+ Assert.assertEquals(result.getContractDisplayAccount(), "test");
+ Assert.assertEquals(result.getContractState().intValue(), 1);
+
+ // 重点测试时间字段,特别是 contract_expired_time
+ Assert.assertEquals(result.getContractSignedTime(), "2015-07-01 10:00:00");
+ Assert.assertEquals(result.getContractExpiredTime(), "2015-07-01 10:00:00");
+ Assert.assertEquals(result.getContractTerminatedTime(), "2015-07-01 10:00:00");
+
+ // 验证其他字段
+ Assert.assertEquals(result.getContractTerminatedMode().intValue(), 3);
+ Assert.assertEquals(result.getContractTerminationRemark(), "delete ....");
+ } finally {
+ // 恢复默认值
+ XmlConfig.fastMode = false;
+ }
+ }
+
+ /**
+ * 测试 XML 解析 - 只包含必填字段
+ */
+ @Test
+ public void testFromXML_RequiredFieldsOnly() {
+ String xmlString = "\n" +
+ " \n" +
+ " \n" +
+ " \n" +
+ " \n" +
+ " Wx15463511252015071056489715\n" +
+ " 123\n" +
+ " 1695\n" +
+ " \n" +
+ " \n" +
+ " 0\n" +
+ " 2015年07月01日 10:00:00\n" +
+ " 2016年07月01日 10:00:00\n" +
+ " \n" +
+ " \n" +
+ "";
+
+ // 启用 fastMode 以覆盖 WxSignQueryResult#loadXml 分支
+ XmlConfig.fastMode = true;
+ try {
+ WxSignQueryResult result = WxSignQueryResult.fromXML(xmlString, WxSignQueryResult.class);
+
+ // 验证必填字段
+ Assert.assertEquals(result.getReturnCode(), "SUCCESS");
+ Assert.assertEquals(result.getResultCode(), "SUCCESS");
+ Assert.assertEquals(result.getContractId(), "Wx15463511252015071056489715");
+ Assert.assertEquals(result.getPlanId(), "123");
+ Assert.assertEquals(result.getContractState().intValue(), 0);
+
+ // 验证 contract_expired_time 字段能正确解析
+ Assert.assertEquals(result.getContractExpiredTime(), "2016-07-01 10:00:00");
+
+ // 验证非必填字段为 null
+ Assert.assertNull(result.getContractTerminatedTime());
+ Assert.assertNull(result.getContractTerminatedMode());
+ Assert.assertNull(result.getContractTerminationRemark());
+ } finally {
+ // 恢复默认值
+ XmlConfig.fastMode = false;
+ }
+ }
+}
diff --git a/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/service/BusinessOperationTransferServiceTest.java b/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/service/BusinessOperationTransferServiceTest.java
index 4107be4347..672483f96b 100644
--- a/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/service/BusinessOperationTransferServiceTest.java
+++ b/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/service/BusinessOperationTransferServiceTest.java
@@ -7,6 +7,8 @@
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
+import java.util.Arrays;
+
import static org.assertj.core.api.Assertions.assertThat;
/**
@@ -36,10 +38,17 @@ public void testServiceInitialization() {
@Test
public void testRequestBuilder() {
+
+ // 构建转账请求
+ BusinessOperationTransferRequest.TransferSceneReportInfo reportInfo = new BusinessOperationTransferRequest.TransferSceneReportInfo();
+ reportInfo.setInfoType("test_info_type");
+ reportInfo.setInfoContent("test_info_content");
+
BusinessOperationTransferRequest request = BusinessOperationTransferRequest.newBuilder()
.appid("test_app_id")
.outBillNo("OT" + System.currentTimeMillis())
- .operationSceneId(WxPayConstants.OperationSceneId.OPERATION_CASH_MARKETING)
+ .transferSceneId(WxPayConstants.OperationSceneId.OPERATION_CASH_MARKETING)
+ .transferSceneReportInfos(Arrays.asList(reportInfo))
.openid("test_openid")
.transferAmount(100)
.transferRemark("测试转账")
@@ -47,7 +56,7 @@ public void testRequestBuilder() {
.build();
assertThat(request.getAppid()).isEqualTo("test_app_id");
- assertThat(request.getOperationSceneId()).isEqualTo(WxPayConstants.OperationSceneId.OPERATION_CASH_MARKETING);
+ assertThat(request.getTransferSceneId()).isEqualTo(WxPayConstants.OperationSceneId.OPERATION_CASH_MARKETING);
assertThat(request.getTransferAmount()).isEqualTo(100);
assertThat(request.getTransferRemark()).isEqualTo("测试转账");
}
@@ -77,11 +86,13 @@ public void testResultClasses() {
BusinessOperationTransferResult result = new BusinessOperationTransferResult();
result.setOutBillNo("test_out_bill_no");
result.setTransferBillNo("test_transfer_bill_no");
- result.setTransferState("SUCCESS");
+ result.setState("SUCCESS");
+ result.setPackageInfo("test_package_info");
assertThat(result.getOutBillNo()).isEqualTo("test_out_bill_no");
assertThat(result.getTransferBillNo()).isEqualTo("test_transfer_bill_no");
- assertThat(result.getTransferState()).isEqualTo("SUCCESS");
+ assertThat(result.getState()).isEqualTo("SUCCESS");
+ assertThat(result.getPackageInfo()).isEqualTo("test_package_info");
BusinessOperationTransferQueryResult queryResult = new BusinessOperationTransferQueryResult();
queryResult.setOperationSceneId("2001");
diff --git a/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/service/impl/MultiAppIdSwitchoverManualTest.java b/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/service/impl/MultiAppIdSwitchoverManualTest.java
new file mode 100644
index 0000000000..010f15fc69
--- /dev/null
+++ b/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/service/impl/MultiAppIdSwitchoverManualTest.java
@@ -0,0 +1,127 @@
+package com.github.binarywang.wxpay.service.impl;
+
+import com.github.binarywang.wxpay.config.WxPayConfig;
+import com.github.binarywang.wxpay.service.WxPayService;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 手动验证多appId切换功能
+ */
+public class MultiAppIdSwitchoverManualTest {
+
+ public static void main(String[] args) {
+ WxPayService payService = new WxPayServiceImpl();
+
+ String testMchId = "1234567890";
+ String testAppId1 = "wx1111111111111111";
+ String testAppId2 = "wx2222222222222222";
+ String testAppId3 = "wx3333333333333333";
+
+ // 配置同一个商户号,三个不同的appId
+ WxPayConfig config1 = new WxPayConfig();
+ config1.setMchId(testMchId);
+ config1.setAppId(testAppId1);
+ config1.setMchKey("test_key_1");
+
+ WxPayConfig config2 = new WxPayConfig();
+ config2.setMchId(testMchId);
+ config2.setAppId(testAppId2);
+ config2.setMchKey("test_key_2");
+
+ WxPayConfig config3 = new WxPayConfig();
+ config3.setMchId(testMchId);
+ config3.setAppId(testAppId3);
+ config3.setMchKey("test_key_3");
+
+ Map configMap = new HashMap();
+ configMap.put(testMchId + "_" + testAppId1, config1);
+ configMap.put(testMchId + "_" + testAppId2, config2);
+ configMap.put(testMchId + "_" + testAppId3, config3);
+
+ payService.setMultiConfig(configMap);
+
+ // 测试1: 使用 mchId + appId 精确切换
+ System.out.println("=== 测试1: 使用 mchId + appId 精确切换 ===");
+ boolean success = payService.switchover(testMchId, testAppId1);
+ System.out.println("切换结果: " + success);
+ System.out.println("当前配置 - MchId: " + payService.getConfig().getMchId() + ", AppId: " + payService.getConfig().getAppId() + ", MchKey: " + payService.getConfig().getMchKey());
+ verify(success, "切换应该成功");
+ verify(testAppId1.equals(payService.getConfig().getAppId()), "AppId应该是 " + testAppId1);
+ System.out.println("✓ 测试1通过\n");
+
+ // 测试2: 仅使用 mchId 切换
+ System.out.println("=== 测试2: 仅使用 mchId 切换 ===");
+ success = payService.switchover(testMchId);
+ System.out.println("切换结果: " + success);
+ System.out.println("当前配置 - MchId: " + payService.getConfig().getMchId() + ", AppId: " + payService.getConfig().getAppId() + ", MchKey: " + payService.getConfig().getMchKey());
+ verify(success, "仅使用mchId切换应该成功");
+ verify(testMchId.equals(payService.getConfig().getMchId()), "MchId应该是 " + testMchId);
+ System.out.println("✓ 测试2通过\n");
+
+ // 测试3: 使用 switchoverTo 链式调用(精确匹配)
+ System.out.println("=== 测试3: 使用 switchoverTo 链式调用(精确匹配) ===");
+ WxPayService result = payService.switchoverTo(testMchId, testAppId2);
+ System.out.println("返回对象: " + (result == payService ? "同一实例" : "不同实例"));
+ System.out.println("当前配置 - MchId: " + payService.getConfig().getMchId() + ", AppId: " + payService.getConfig().getAppId() + ", MchKey: " + payService.getConfig().getMchKey());
+ verify(result == payService, "应该返回同一实例");
+ verify(testAppId2.equals(payService.getConfig().getAppId()), "AppId应该是 " + testAppId2);
+ System.out.println("✓ 测试3通过\n");
+
+ // 测试4: 使用 switchoverTo 链式调用(仅mchId)
+ System.out.println("=== 测试4: 使用 switchoverTo 链式调用(仅mchId) ===");
+ result = payService.switchoverTo(testMchId);
+ System.out.println("返回对象: " + (result == payService ? "同一实例" : "不同实例"));
+ System.out.println("当前配置 - MchId: " + payService.getConfig().getMchId() + ", AppId: " + payService.getConfig().getAppId() + ", MchKey: " + payService.getConfig().getMchKey());
+ verify(result == payService, "应该返回同一实例");
+ verify(testMchId.equals(payService.getConfig().getMchId()), "MchId应该是 " + testMchId);
+ System.out.println("✓ 测试4通过\n");
+
+ // 测试5: 切换到不存在的商户号
+ System.out.println("=== 测试5: 切换到不存在的商户号 ===");
+ success = payService.switchover("nonexistent_mch_id");
+ System.out.println("切换结果: " + success);
+ verify(!success, "切换到不存在的商户号应该失败");
+ System.out.println("✓ 测试5通过\n");
+
+ // 测试6: 切换到不存在的 appId
+ System.out.println("=== 测试6: 切换到不存在的 appId ===");
+ success = payService.switchover(testMchId, "wx9999999999999999");
+ System.out.println("切换结果: " + success);
+ verify(!success, "切换到不存在的appId应该失败");
+ System.out.println("✓ 测试6通过\n");
+
+ // 测试7: 添加新配置后切换
+ System.out.println("=== 测试7: 添加新配置后切换 ===");
+ String newAppId = "wx4444444444444444";
+ WxPayConfig newConfig = new WxPayConfig();
+ newConfig.setMchId(testMchId);
+ newConfig.setAppId(newAppId);
+ newConfig.setMchKey("test_key_4");
+ payService.addConfig(testMchId, newAppId, newConfig);
+
+ success = payService.switchover(testMchId, newAppId);
+ System.out.println("切换结果: " + success);
+ System.out.println("当前配置 - MchId: " + payService.getConfig().getMchId() + ", AppId: " + payService.getConfig().getAppId() + ", MchKey: " + payService.getConfig().getMchKey());
+ verify(success, "切换到新添加的配置应该成功");
+ verify(newAppId.equals(payService.getConfig().getAppId()), "AppId应该是 " + newAppId);
+ System.out.println("✓ 测试7通过\n");
+
+ System.out.println("==================");
+ System.out.println("所有测试通过! ✓");
+ System.out.println("==================");
+ }
+
+ /**
+ * 验证条件是否为真,如果为假则抛出异常
+ *
+ * @param condition 待验证的条件
+ * @param message 验证失败时的错误信息
+ */
+ private static void verify(boolean condition, String message) {
+ if (!condition) {
+ throw new RuntimeException("验证失败: " + message);
+ }
+ }
+}
diff --git a/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/service/impl/MultiAppIdSwitchoverTest.java b/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/service/impl/MultiAppIdSwitchoverTest.java
new file mode 100644
index 0000000000..c1c1460fec
--- /dev/null
+++ b/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/service/impl/MultiAppIdSwitchoverTest.java
@@ -0,0 +1,310 @@
+package com.github.binarywang.wxpay.service.impl;
+
+import com.github.binarywang.wxpay.config.WxPayConfig;
+import com.github.binarywang.wxpay.service.WxPayService;
+import me.chanjar.weixin.common.error.WxRuntimeException;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.testng.Assert.*;
+
+/**
+ * 测试一个商户号配置多个appId的场景
+ *
+ * @author Binary Wang
+ */
+public class MultiAppIdSwitchoverTest {
+
+ private WxPayService payService;
+ private final String testMchId = "1234567890";
+ private final String testAppId1 = "wx1111111111111111";
+ private final String testAppId2 = "wx2222222222222222";
+ private final String testAppId3 = "wx3333333333333333";
+
+ @BeforeMethod
+ public void setup() {
+ payService = new WxPayServiceImpl();
+
+ // 配置同一个商户号,三个不同的appId
+ WxPayConfig config1 = new WxPayConfig();
+ config1.setMchId(testMchId);
+ config1.setAppId(testAppId1);
+ config1.setMchKey("test_key_1");
+
+ WxPayConfig config2 = new WxPayConfig();
+ config2.setMchId(testMchId);
+ config2.setAppId(testAppId2);
+ config2.setMchKey("test_key_2");
+
+ WxPayConfig config3 = new WxPayConfig();
+ config3.setMchId(testMchId);
+ config3.setAppId(testAppId3);
+ config3.setMchKey("test_key_3");
+
+ Map configMap = new HashMap();
+ configMap.put(testMchId + "_" + testAppId1, config1);
+ configMap.put(testMchId + "_" + testAppId2, config2);
+ configMap.put(testMchId + "_" + testAppId3, config3);
+
+ payService.setMultiConfig(configMap);
+ }
+
+ /**
+ * 测试使用 mchId + appId 精确切换(原有功能,确保向后兼容)
+ */
+ @Test
+ public void testSwitchoverWithMchIdAndAppId() {
+ // 切换到第一个配置
+ boolean success = payService.switchover(testMchId, testAppId1);
+ assertTrue(success);
+ assertEquals(payService.getConfig().getAppId(), testAppId1);
+ assertEquals(payService.getConfig().getMchKey(), "test_key_1");
+
+ // 切换到第二个配置
+ success = payService.switchover(testMchId, testAppId2);
+ assertTrue(success);
+ assertEquals(payService.getConfig().getAppId(), testAppId2);
+ assertEquals(payService.getConfig().getMchKey(), "test_key_2");
+
+ // 切换到第三个配置
+ success = payService.switchover(testMchId, testAppId3);
+ assertTrue(success);
+ assertEquals(payService.getConfig().getAppId(), testAppId3);
+ assertEquals(payService.getConfig().getMchKey(), "test_key_3");
+ }
+
+ /**
+ * 测试仅使用 mchId 切换(新功能)
+ * 应该能够成功切换到该商户号的某个配置
+ */
+ @Test
+ public void testSwitchoverWithMchIdOnly() {
+ // 仅使用商户号切换,应该能够成功切换到该商户号的某个配置
+ boolean success = payService.switchover(testMchId);
+ assertTrue(success, "应该能够通过mchId切换配置");
+
+ // 验证配置确实是该商户号的配置之一
+ WxPayConfig currentConfig = payService.getConfig();
+ assertNotNull(currentConfig);
+ assertEquals(currentConfig.getMchId(), testMchId);
+
+ // appId应该是三个中的一个
+ String currentAppId = currentConfig.getAppId();
+ assertTrue(
+ testAppId1.equals(currentAppId) || testAppId2.equals(currentAppId) || testAppId3.equals(currentAppId),
+ "当前appId应该是配置的appId之一"
+ );
+ }
+
+ /**
+ * 测试 switchoverTo 方法(带链式调用,使用 mchId + appId)
+ */
+ @Test
+ public void testSwitchoverToWithMchIdAndAppId() {
+ WxPayService result = payService.switchoverTo(testMchId, testAppId2);
+ assertNotNull(result);
+ assertEquals(result, payService, "switchoverTo应该返回当前服务实例,支持链式调用");
+ assertEquals(payService.getConfig().getAppId(), testAppId2);
+ }
+
+ /**
+ * 测试 switchoverTo 方法(带链式调用,仅使用 mchId)
+ */
+ @Test
+ public void testSwitchoverToWithMchIdOnly() {
+ WxPayService result = payService.switchoverTo(testMchId);
+ assertNotNull(result);
+ assertEquals(result, payService, "switchoverTo应该返回当前服务实例,支持链式调用");
+ assertEquals(payService.getConfig().getMchId(), testMchId);
+ }
+
+ /**
+ * 测试切换到不存在的商户号
+ */
+ @Test
+ public void testSwitchoverToNonexistentMchId() {
+ boolean success = payService.switchover("nonexistent_mch_id");
+ assertFalse(success, "切换到不存在的商户号应该失败");
+ }
+
+ /**
+ * 测试 switchoverTo 切换到不存在的商户号(应该抛出异常)
+ */
+ @Test(expectedExceptions = WxRuntimeException.class)
+ public void testSwitchoverToNonexistentMchIdThrowsException() {
+ payService.switchoverTo("nonexistent_mch_id");
+ }
+
+ /**
+ * 测试切换到不存在的 mchId + appId 组合
+ */
+ @Test
+ public void testSwitchoverToNonexistentAppId() {
+ boolean success = payService.switchover(testMchId, "wx9999999999999999");
+ assertFalse(success, "切换到不存在的appId应该失败");
+ }
+
+ /**
+ * 测试添加配置后能够正常切换
+ */
+ @Test
+ public void testAddConfigAndSwitchover() {
+ String newAppId = "wx4444444444444444";
+
+ // 动态添加一个新的配置
+ WxPayConfig newConfig = new WxPayConfig();
+ newConfig.setMchId(testMchId);
+ newConfig.setAppId(newAppId);
+ newConfig.setMchKey("test_key_4");
+
+ payService.addConfig(testMchId, newAppId, newConfig);
+
+ // 切换到新添加的配置
+ boolean success = payService.switchover(testMchId, newAppId);
+ assertTrue(success);
+ assertEquals(payService.getConfig().getAppId(), newAppId);
+ assertEquals(payService.getConfig().getMchKey(), "test_key_4");
+
+ // 使用仅mchId切换也应该能够找到配置
+ success = payService.switchover(testMchId);
+ assertTrue(success);
+ assertEquals(payService.getConfig().getMchId(), testMchId);
+ }
+
+ /**
+ * 测试移除配置后切换
+ */
+ @Test
+ public void testRemoveConfigAndSwitchover() {
+ // 移除一个配置
+ payService.removeConfig(testMchId, testAppId1);
+
+ // 切换到已移除的配置应该失败
+ boolean success = payService.switchover(testMchId, testAppId1);
+ assertFalse(success);
+
+ // 但仍然能够切换到其他配置
+ success = payService.switchover(testMchId, testAppId2);
+ assertTrue(success);
+
+ // 使用仅mchId切换应该仍然有效(因为还有其他appId的配置)
+ success = payService.switchover(testMchId);
+ assertTrue(success);
+ }
+
+ /**
+ * 测试单个配置的场景(确保向后兼容)
+ */
+ @Test
+ public void testSingleConfig() {
+ WxPayService singlePayService = new WxPayServiceImpl();
+ WxPayConfig singleConfig = new WxPayConfig();
+ singleConfig.setMchId("single_mch_id");
+ singleConfig.setAppId("single_app_id");
+ singleConfig.setMchKey("single_key");
+
+ singlePayService.setConfig(singleConfig);
+
+ // 直接获取配置应该成功
+ assertEquals(singlePayService.getConfig().getMchId(), "single_mch_id");
+ assertEquals(singlePayService.getConfig().getAppId(), "single_app_id");
+
+ // 使用精确匹配切换
+ boolean success = singlePayService.switchover("single_mch_id", "single_app_id");
+ assertTrue(success);
+
+ // 使用仅mchId切换
+ success = singlePayService.switchover("single_mch_id");
+ assertTrue(success);
+ }
+
+ /**
+ * 测试空参数或null参数的处理
+ */
+ @Test
+ public void testSwitchoverWithNullOrEmptyMchId() {
+ // 测试 null 参数
+ boolean success = payService.switchover(null);
+ assertFalse(success, "使用null作为mchId应该返回false");
+
+ // 测试空字符串
+ success = payService.switchover("");
+ assertFalse(success, "使用空字符串作为mchId应该返回false");
+
+ // 测试空白字符串
+ success = payService.switchover(" ");
+ assertFalse(success, "使用空白字符串作为mchId应该返回false");
+ }
+
+ /**
+ * 测试 switchoverTo 方法对空参数或null参数的处理
+ */
+ @Test(expectedExceptions = WxRuntimeException.class)
+ public void testSwitchoverToWithNullMchId() {
+ payService.switchoverTo((String) null);
+ }
+
+ @Test(expectedExceptions = WxRuntimeException.class)
+ public void testSwitchoverToWithEmptyMchId() {
+ payService.switchoverTo("");
+ }
+
+ @Test(expectedExceptions = WxRuntimeException.class)
+ public void testSwitchoverToWithBlankMchId() {
+ payService.switchoverTo(" ");
+ }
+
+ /**
+ * 测试商户号存在包含关系的场景
+ * 例如同时配置 "123" 和 "1234",验证前缀匹配不会错误匹配
+ */
+ @Test
+ public void testSwitchoverWithOverlappingMchIds() {
+ WxPayService testService = new WxPayServiceImpl();
+
+ // 配置两个有包含关系的商户号
+ String mchId1 = "123";
+ String mchId2 = "1234";
+ String appId1 = "wx_app_123";
+ String appId2 = "wx_app_1234";
+
+ WxPayConfig config1 = new WxPayConfig();
+ config1.setMchId(mchId1);
+ config1.setAppId(appId1);
+ config1.setMchKey("key_123");
+
+ WxPayConfig config2 = new WxPayConfig();
+ config2.setMchId(mchId2);
+ config2.setAppId(appId2);
+ config2.setMchKey("key_1234");
+
+ Map configMap = new HashMap();
+ configMap.put(mchId1 + "_" + appId1, config1);
+ configMap.put(mchId2 + "_" + appId2, config2);
+ testService.setMultiConfig(configMap);
+
+ // 切换到 "123",应该只匹配 "123_wx_app_123"
+ boolean success = testService.switchover(mchId1);
+ assertTrue(success);
+ assertEquals(testService.getConfig().getMchId(), mchId1);
+ assertEquals(testService.getConfig().getAppId(), appId1);
+
+ // 切换到 "1234",应该只匹配 "1234_wx_app_1234"
+ success = testService.switchover(mchId2);
+ assertTrue(success);
+ assertEquals(testService.getConfig().getMchId(), mchId2);
+ assertEquals(testService.getConfig().getAppId(), appId2);
+
+ // 精确切换验证
+ success = testService.switchover(mchId1, appId1);
+ assertTrue(success);
+ assertEquals(testService.getConfig().getAppId(), appId1);
+
+ success = testService.switchover(mchId2, appId2);
+ assertTrue(success);
+ assertEquals(testService.getConfig().getAppId(), appId2);
+ }
+}
diff --git a/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/v3/util/RsaCryptoUtilTest.java b/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/v3/util/RsaCryptoUtilTest.java
new file mode 100644
index 0000000000..18f46c687f
--- /dev/null
+++ b/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/v3/util/RsaCryptoUtilTest.java
@@ -0,0 +1,179 @@
+package com.github.binarywang.wxpay.v3.util;
+
+import com.github.binarywang.wxpay.bean.profitsharing.request.ProfitSharingReceiverV3Request;
+import com.github.binarywang.wxpay.bean.profitsharing.request.ProfitSharingV3Request;
+import com.github.binarywang.wxpay.exception.WxPayException;
+import com.github.binarywang.wxpay.v3.SpecEncrypt;
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+import org.testng.annotations.Test;
+
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.testng.Assert.*;
+
+/**
+ * RsaCryptoUtil 测试类
+ */
+public class RsaCryptoUtilTest {
+
+ /**
+ * 测试反射能否找到嵌套类中的 @SpecEncrypt 注解字段
+ */
+ @Test
+ public void testFindAnnotatedFieldsInNestedClass() {
+ // 创建 Receiver 对象
+ ProfitSharingV3Request.Receiver receiver = new ProfitSharingV3Request.Receiver();
+ receiver.setName("测试姓名");
+
+ // 使用反射查找带有 @SpecEncrypt 注解的字段
+ Class> receiverClass = receiver.getClass();
+ Field[] fields = receiverClass.getDeclaredFields();
+
+ boolean foundNameField = false;
+ boolean nameFieldHasAnnotation = false;
+
+ for (Field field : fields) {
+ if (field.getName().equals("name")) {
+ foundNameField = true;
+ if (field.isAnnotationPresent(SpecEncrypt.class)) {
+ nameFieldHasAnnotation = true;
+ }
+ }
+ }
+
+ // 验证能够找到 name 字段并且它有 @SpecEncrypt 注解
+ assertTrue(foundNameField, "应该能找到 name 字段");
+ assertTrue(nameFieldHasAnnotation, "name 字段应该有 @SpecEncrypt 注解");
+ }
+
+ /**
+ * 测试嵌套对象中的字段加密
+ * 验证 List 中每个 Receiver 对象的 name 字段是否能被正确找到和处理
+ */
+ @Test
+ public void testEncryptFieldsWithNestedObjects() {
+ // 创建测试对象
+ ProfitSharingV3Request request = ProfitSharingV3Request.newBuilder()
+ .appid("test-appid")
+ .subMchId("test-submchid")
+ .transactionId("test-transaction")
+ .outOrderNo("test-order-no")
+ .unfreezeUnsplit(true)
+ .build();
+
+ List receivers = new ArrayList();
+ ProfitSharingV3Request.Receiver receiver = new ProfitSharingV3Request.Receiver();
+ receiver.setName("张三"); // 设置需要加密的字段
+ receiver.setAccount("test-account");
+ receiver.setType("PERSONAL_OPENID");
+ receiver.setAmount(100);
+ receiver.setRelationType("STORE");
+ receiver.setDescription("测试分账");
+
+ receivers.add(receiver);
+ request.setReceivers(receivers);
+
+ // 验证 receivers 字段有 @SpecEncrypt 注解
+ try {
+ Field receiversField = ProfitSharingV3Request.class.getDeclaredField("receivers");
+ boolean hasAnnotation = receiversField.isAnnotationPresent(SpecEncrypt.class);
+ assertTrue(hasAnnotation, "receivers 字段应该有 @SpecEncrypt 注解");
+ } catch (NoSuchFieldException e) {
+ fail("应该能找到 receivers 字段");
+ }
+
+ // 验证name字段不为null
+ assertNotNull(receiver.getName());
+ assertEquals(receiver.getName(), "张三");
+ }
+
+ /**
+ * 测试单个对象中的字段加密
+ * 验证直接在对象上的 @SpecEncrypt 字段是否能被正确找到
+ */
+ @Test
+ public void testEncryptFieldsWithDirectField() {
+ // 创建测试对象
+ ProfitSharingReceiverV3Request request = ProfitSharingReceiverV3Request.newBuilder()
+ .appid("test-appid")
+ .subMchId("test-submchid")
+ .type("PERSONAL_OPENID")
+ .account("test-account")
+ .name("李四")
+ .relationType("STORE")
+ .build();
+
+ // 验证 name 字段有 @SpecEncrypt 注解
+ try {
+ Field nameField = ProfitSharingReceiverV3Request.class.getDeclaredField("name");
+ boolean hasAnnotation = nameField.isAnnotationPresent(SpecEncrypt.class);
+ assertTrue(hasAnnotation, "name 字段应该有 @SpecEncrypt 注解");
+ } catch (NoSuchFieldException e) {
+ fail("应该能找到 name 字段");
+ }
+
+ // 验证name字段不为null
+ assertNotNull(request.getName());
+ assertEquals(request.getName(), "李四");
+ }
+
+ /**
+ * 测试类继承场景下的字段加密
+ * 验证父类中带 @SpecEncrypt 注解的字段是否能被正确找到和加密
+ */
+ @Test
+ public void testEncryptFieldsWithInheritance() {
+ // 定义测试用的父类和子类
+ @Data
+ class ParentRequest {
+ @SpecEncrypt
+ @SerializedName("parent_name")
+ private String parentName;
+ }
+
+ @Data
+ @lombok.EqualsAndHashCode(callSuper = false)
+ class ChildRequest extends ParentRequest {
+ @SpecEncrypt
+ @SerializedName("child_name")
+ private String childName;
+
+ @Override
+ protected boolean canEqual(final Object other) {
+ return other instanceof ChildRequest;
+ }
+ }
+
+ // 创建子类实例
+ ChildRequest request = new ChildRequest();
+ request.setParentName("父类字段");
+ request.setChildName("子类字段");
+
+ // 验证能够找到父类和子类的字段
+ // 使用 getDeclaredFields 只能找到子类字段
+ Field[] childFields = ChildRequest.class.getDeclaredFields();
+
+ // 使用反射调用 RsaCryptoUtil 的私有 getAllFields 方法
+ int annotatedFieldCount = 0;
+ try {
+ java.lang.reflect.Method getAllFieldsMethod = RsaCryptoUtil.class.getDeclaredMethod("getAllFields", Class.class);
+ getAllFieldsMethod.setAccessible(true);
+ @SuppressWarnings("unchecked")
+ List allFields = (List) getAllFieldsMethod.invoke(null, ChildRequest.class);
+
+ for (Field field : allFields) {
+ if (field.isAnnotationPresent(SpecEncrypt.class)) {
+ annotatedFieldCount++;
+ }
+ }
+ } catch (Exception e) {
+ fail("无法调用 getAllFields 方法: " + e.getMessage());
+ }
+
+ // 应该找到2个带注解的字段(parentName 和 childName)
+ assertTrue(annotatedFieldCount>= 2, "应该能找到至少2个带 @SpecEncrypt 注解的字段");
+ }
+}
diff --git a/weixin-java-qidian/pom.xml b/weixin-java-qidian/pom.xml
index 293a4655cf..7b06feb08e 100644
--- a/weixin-java-qidian/pom.xml
+++ b/weixin-java-qidian/pom.xml
@@ -31,6 +31,11 @@
okhttp
provided
+
+ org.apache.httpcomponents
+ httpclient
+ provided
+
org.apache.httpcomponents.client5
httpclient5
@@ -42,11 +47,7 @@
testng
test
-
- org.mockito
- mockito-all
- test
-
+
com.google.inject
guice
diff --git a/weixin-java-qidian/src/main/java/me/chanjar/weixin/qidian/api/impl/WxQidianServiceHttpComponentsImpl.java b/weixin-java-qidian/src/main/java/me/chanjar/weixin/qidian/api/impl/WxQidianServiceHttpComponentsImpl.java
index a5cc23f0a2..90486efc8f 100644
--- a/weixin-java-qidian/src/main/java/me/chanjar/weixin/qidian/api/impl/WxQidianServiceHttpComponentsImpl.java
+++ b/weixin-java-qidian/src/main/java/me/chanjar/weixin/qidian/api/impl/WxQidianServiceHttpComponentsImpl.java
@@ -48,8 +48,10 @@ public void initHttp() {
HttpComponentsClientBuilder apacheHttpClientBuilder = DefaultHttpComponentsClientBuilder.get();
apacheHttpClientBuilder.httpProxyHost(configStorage.getHttpProxyHost())
- .httpProxyPort(configStorage.getHttpProxyPort()).httpProxyUsername(configStorage.getHttpProxyUsername())
- .httpProxyPassword(configStorage.getHttpProxyPassword().toCharArray());
+ .httpProxyPort(configStorage.getHttpProxyPort())
+ .httpProxyUsername(configStorage.getHttpProxyUsername())
+ .httpProxyPassword(configStorage.getHttpProxyPassword() == null ? null :
+ configStorage.getHttpProxyPassword().toCharArray());
if (configStorage.getHttpProxyHost() != null && configStorage.getHttpProxyPort()> 0) {
this.httpProxy = new HttpHost(configStorage.getHttpProxyHost(), configStorage.getHttpProxyPort());
diff --git a/weixin-java-qidian/src/main/java/me/chanjar/weixin/qidian/api/impl/WxQidianServiceImpl.java b/weixin-java-qidian/src/main/java/me/chanjar/weixin/qidian/api/impl/WxQidianServiceImpl.java
index 45e87204cb..2e1314b3b1 100644
--- a/weixin-java-qidian/src/main/java/me/chanjar/weixin/qidian/api/impl/WxQidianServiceImpl.java
+++ b/weixin-java-qidian/src/main/java/me/chanjar/weixin/qidian/api/impl/WxQidianServiceImpl.java
@@ -2,11 +2,11 @@
/**
*
- * 默认接口实现类,使用apache httpclient实现
+ * 默认接口实现类,使用apache httpClient 5实现
* Created by Binary Wang on 2017年5月27日.
*
*
* @author Binary Wang
*/
-public class WxQidianServiceImpl extends WxQidianServiceHttpClientImpl {
+public class WxQidianServiceImpl extends WxQidianServiceHttpComponentsImpl {
}