diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 0000000000..cec0d76c6b --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,198 @@ +# WxJava - WeChat Java SDK Development Instructions + +WxJava is a comprehensive WeChat Java SDK supporting multiple WeChat platforms including Official Accounts (公众号), Mini Programs (小程序), WeChat Pay (微信支付), Enterprise WeChat (企业微信), Open Platform (开放平台), and Channel/Video (视频号). This is a Maven multi-module project with Spring Boot and Solon framework integrations. + +**ALWAYS reference these instructions first and fallback to search or bash commands only when you encounter unexpected information that does not match the information here.** + +## Working Effectively + +### Prerequisites and Environment Setup +- **Java Requirements**: JDK 8+ required (project uses Java 8 as minimum target) +- **Maven**: Maven 3.6+ recommended (Maven 3.9.11 validated) +- **IDE**: IntelliJ IDEA recommended (project optimized for IDEA) + +### Bootstrap, Build, and Validate +Execute these commands in sequence after cloning: + +```bash +# 1. Basic compilation (NEVER CANCEL - takes 4-5 minutes) +mvn clean compile -DskipTests=true --no-transfer-progress +# Timeout: Set 8+ minutes. Actual time: ~4 minutes + +# 2. Full package build (NEVER CANCEL - takes 2-3 minutes) +mvn clean package -DskipTests=true --no-transfer-progress +# Timeout: Set 5+ minutes. Actual time: ~2 minutes + +# 3. Code quality validation (NEVER CANCEL - takes 45-60 seconds) +mvn checkstyle:check --no-transfer-progress +# Timeout: Set 3+ minutes. Actual time: ~50 seconds +``` + +**CRITICAL TIMING NOTES:** +- **NEVER CANCEL** any Maven build command +- Compilation phase takes longest (~4 minutes) due to 34 modules +- Full builds are faster on subsequent runs due to incremental compilation +- Always use `--no-transfer-progress` to reduce log noise + +### Testing Structure +- **Test Framework**: TestNG (not JUnit) +- **Test Files**: 298 test files across all modules +- **Default Behavior**: Tests are DISABLED by default in pom.xml (`true`) +- **Test Configuration**: Tests require external WeChat API credentials via test-config.xml files +- **DO NOT** attempt to run tests without proper WeChat API credentials as they will fail + +## Project Structure and Navigation + +### Core SDK Modules (Main Development Areas) +- `weixin-java-common/` - Common utilities and base classes (most important) +- `weixin-java-mp/` - WeChat Official Account SDK (公众号) +- `weixin-java-pay/` - WeChat Pay SDK (微信支付) +- `weixin-java-miniapp/` - Mini Program SDK (小程序) +- `weixin-java-cp/` - Enterprise WeChat SDK (企业微信) +- `weixin-java-open/` - Open Platform SDK (开放平台) +- `weixin-java-channel/` - Channel/Video SDK (视频号) +- `weixin-java-qidian/` - Qidian SDK (企点) + +### Framework Integration Modules +- `spring-boot-starters/` - Spring Boot auto-configuration starters +- `solon-plugins/` - Solon framework plugins +- `weixin-graal/` - GraalVM native image support + +### Configuration and Quality +- `quality-checks/google_checks.xml` - Checkstyle configuration +- `.editorconfig` - Code formatting rules (2 spaces = 1 tab) +- `pom.xml` - Root Maven configuration + +## Development Workflow + +### Making Code Changes +1. **Always build first** to establish clean baseline: + ```bash + mvn clean compile --no-transfer-progress + ``` + +2. **Follow code style** (enforced by checkstyle): + - Use 2 spaces for indentation (not tabs) + - Follow Google Java Style Guide + - Install EditorConfig plugin in your IDE + +3. **Validate changes incrementally**: + ```bash + # After each change: + mvn compile --no-transfer-progress + mvn checkstyle:check --no-transfer-progress + ``` + +### Before Submitting Changes +**ALWAYS run these validation steps in sequence:** + +1. **Code Style Validation**: + ```bash + mvn checkstyle:check --no-transfer-progress + # Must pass - takes ~50 seconds + ``` + +2. **Full Clean Build**: + ```bash + mvn clean package -DskipTests=true --no-transfer-progress + # Must succeed - takes ~2 minutes + ``` + +3. **Documentation**: Update javadoc for public methods and classes +4. **Contribution Guidelines**: Follow `CONTRIBUTING.md` - PRs must target `develop` branch + +## Module Dependencies and Build Order + +### Core Module Dependencies (Build Order) +1. `weixin-graal` (GraalVM support) +2. `weixin-java-common` (foundation for all other modules) +3. Core SDK modules (mp, pay, miniapp, cp, open, channel, qidian) +4. Framework integrations (spring-boot-starters, solon-plugins) + +### Key Relationship Patterns +- All SDK modules depend on `weixin-java-common` +- Spring Boot starters depend on corresponding SDK modules +- Solon plugins follow same pattern as Spring Boot starters +- Each module has both single and multi-account configurations + +## Common Tasks and Commands + +### Validate Specific Module +```bash +# Build single module (replace 'weixin-java-mp' with target module): +cd weixin-java-mp +mvn clean compile --no-transfer-progress +``` + +### Check Dependencies +```bash +# Analyze dependencies: +mvn dependency:tree --no-transfer-progress + +# Check for dependency updates: +./others/check-dependency-updates.sh +``` + +### Release and Publishing +```bash +# Version check: +mvn versions:display-property-updates --no-transfer-progress + +# Deploy (requires credentials): +mvn clean deploy -P release --no-transfer-progress +``` + +## Important Files and Locations + +### Configuration Files +- `pom.xml` - Root Maven configuration with dependency management +- `quality-checks/google_checks.xml` - Checkstyle rules +- `.editorconfig` - IDE formatting configuration +- `.github/workflows/maven-publish.yml` - CI/CD pipeline + +### Documentation +- `README.md` - Project overview and usage (Chinese) +- `CONTRIBUTING.md` - Development contribution guidelines +- `demo.md` - Links to demo projects and examples +- Each module has dedicated documentation and examples + +### Test Resources +- `*/src/test/resources/test-config.sample.xml` - Template for test configuration +- Tests require real WeChat API credentials to run + +## SDK Usage Patterns + +### Maven Dependency Usage +```xml + + com.github.binarywang + weixin-java-mp + 4.7.0 + +``` + +### Common Development Areas +- **API Client Implementation**: Located in `*/service/impl/` directories +- **Model Classes**: Located in `*/bean/` directories +- **Configuration**: Located in `*/config/` directories +- **Utilities**: Located in `*/util/` directories in weixin-java-common + +## Troubleshooting + +### Build Issues +- **OutOfMemoryError**: Increase Maven memory: `export MAVEN_OPTS="-Xmx2g"` +- **Compilation Failures**: Usually dependency issues - run `mvn clean` first +- **Checkstyle Failures**: Check `.editorconfig` settings in IDE + +### Common Gotchas +- **Tests Always Skip**: This is normal - tests require WeChat API credentials +- **Multi-Module Changes**: Always build from root, not individual modules +- **Branch Target**: PRs must target `develop` branch, not `master`/`release` + +## Performance Notes +- **First Build**: Takes 4-5 minutes due to dependency downloads +- **Incremental Builds**: Much faster (~30-60 seconds) +- **Checkstyle**: Runs quickly (~50 seconds) and should be run frequently +- **IDE Performance**: Project uses Lombok - ensure annotation processing is enabled + +Remember: This is a SDK library project, not a runnable application. Changes should focus on API functionality, not application behavior. \ No newline at end of file diff --git a/.github/workflows b/.github/workflows deleted file mode 100644 index e420c7d44d..0000000000 --- a/.github/workflows +++ /dev/null @@ -1,4 +0,0 @@ -- name: Upload coverage reports to Codecov - uses: codecov/codecov-action@v3 - env: - CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} diff --git a/.github/workflows/maven-publish.yml b/.github/workflows/maven-publish.yml new file mode 100644 index 0000000000..de68370ae1 --- /dev/null +++ b/.github/workflows/maven-publish.yml @@ -0,0 +1,94 @@ +name: Publish to Maven Central +on: + push: + branches: + - develop + +permissions: + contents: write + +concurrency: + group: maven-publish-${{ github.ref }} + cancel-in-progress: true + +jobs: + build-and-publish: + runs-on: ubuntu-latest + + steps: + - name: Checkout Code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Detect and tag release version from commit message + id: version_detect + run: | + COMMIT_MSG=$(git log -1 --pretty=%B) + VERSION="" + TAG="" + IS_RELEASE="false" + if [[ "$COMMIT_MSG" =~ ^:bookmark:\ 发布\ ([0-9]+\.[0-9]+\.[0-9]+)\.B\ 测试版本 ]]; then + BASE_VER="${BASH_REMATCH[1]}" + VERSION="${BASE_VER}.B" + TAG="v${BASE_VER}" + IS_RELEASE="true" + echo "Matched release commit: VERSION=$VERSION, TAG=$TAG" + # 检查并打tag + if git tag | grep -q "^$TAG$"; then + echo "Tag $TAG already exists." + else + git config user.name "Binary Wang" + git config user.email "a@binarywang.com" + git tag -a "$TAG" -m "Release $TAG" + git push origin "$TAG" + echo "Tag $TAG created and pushed." + fi + fi + echo "is_release=$IS_RELEASE" >> $GITHUB_OUTPUT + echo "version=$VERSION" >> $GITHUB_OUTPUT + + - name: Set up Java + uses: actions/setup-java@v4 + with: + java-version: '8' + distribution: 'temurin' + server-id: central + server-username: MAVEN_USERNAME + server-password: MAVEN_PASSWORD + gpg-private-key: ${{ secrets.GPG_PRIVATE_KEY }} + gpg-passphrase: MAVEN_GPG_PASSPHRASE + cache: maven + + - name: Verify GPG keys + run: | + echo "Available GPG Keys:" + gpg --list-secret-keys --keyid-format LONG + + - name: Generate and set version + id: set_version + run: | + if [[ "${{ steps.version_detect.outputs.is_release }}" == "true" ]]; then + VERSION="${{ steps.version_detect.outputs.version }}" + else + git describe --tags 2>/dev/null || echo "no tag" + TIMESTAMP=$(date +'%Y%m%d.%H%M%S') + GIT_DESCRIBE=$(git describe --tags --abbrev=0 2>/dev/null | sed 's/^v//' || echo "0.0.1") + VERSION="${GIT_DESCRIBE}-${TIMESTAMP}" + fi + echo "Final version: $VERSION" + echo "VERSION=$VERSION" >> $GITHUB_ENV + mvn versions:set -DnewVersion=$VERSION --no-transfer-progress + env: + TZ: Asia/Shanghai + + - name: Publish to Maven Central + run: | + mvn clean deploy -P release \ + -Dmaven.test.skip=true \ + -Dgpg.args="--batch --yes --pinentry-mode loopback" \ + --no-transfer-progress + env: + MAVEN_USERNAME: ${{ secrets.OSSRH_USERNAME }} + MAVEN_PASSWORD: ${{ secrets.OSSRH_TOKEN }} + MAVEN_GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c703964824..0b16b4779e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -28,7 +28,7 @@ $ git push * 定期使用项目仓库内容更新自己仓库内容。 ```bash -$ git remote add upstream https://github.com/Wechat-Group/WxJava +$ git remote add upstream https://github.com/binarywang/WxJava $ git fetch upstream $ git checkout develop $ git rebase upstream/develop diff --git a/NEW_TRANSFER_API_SUPPORT.md b/NEW_TRANSFER_API_SUPPORT.md new file mode 100644 index 0000000000..c7e9eaf490 --- /dev/null +++ b/NEW_TRANSFER_API_SUPPORT.md @@ -0,0 +1,129 @@ +# 微信支付新版商户转账API支持 + +## 问题解答 + +**问题**: 新开通的商户号只能使用最新版本的商户转账接口,WxJava是否支持? + +**答案**: **WxJava 已经完整支持新版商户转账API!** 从2025年1月15日开始生效的新版转账API已在WxJava中实现。 + +## 新版转账API特性 + +### 1. API接口对比 + +| 特性 | 传统转账API | 新版转账API (2025.1.15+) | +|------|-------------|-------------------------| +| **服务类** | `MerchantTransferService` | `TransferService` | +| **API路径** | `/v3/transfer/batches` | `/v3/fund-app/mch-transfer/transfer-bills` | +| **转账方式** | 批量转账 | 单笔转账 | +| **场景支持** | 基础场景 | 丰富场景(如佣金报酬等) | +| **撤销功能** | ❌ 不支持 | ✅ 支持 | +| **适用范围** | 所有商户 | **新开通商户必须使用** | + +### 2. 新版API功能列表 + +✅ **发起转账** - `transferBills()` +✅ **查询转账** - `getBillsByOutBillNo()` / `getBillsByTransferBillNo()` +✅ **撤销转账** - `transformBillsCancel()` +✅ **回调通知** - `parseTransferBillsNotifyResult()` +✅ **RSA加密** - 自动处理用户姓名加密 +✅ **场景支持** - 支持多种转账场景ID + +## 快速开始 + +### 1. 获取服务实例 + +```java +// 获取WxPayService实例 +WxPayService wxPayService = new WxPayServiceImpl(); +wxPayService.setConfig(config); + +// 获取新版转账服务 - 这就是新开通商户需要使用的服务! +TransferService transferService = wxPayService.getTransferService(); +``` + +### 2. 发起转账(新版API) + +```java +// 构建转账请求 +TransferBillsRequest request = TransferBillsRequest.newBuilder() + .appid("your_appid") // 应用ID + .outBillNo("T" + System.currentTimeMillis()) // 商户转账单号 + .transferSceneId("1005") // 转账场景ID(佣金报酬) + .openid("user_openid") // 用户openid + .userName("张三") // 收款用户姓名(可选,自动加密) + .transferAmount(100) // 转账金额(分) + .transferRemark("佣金报酬") // 转账备注 + .build(); + +// 发起转账 +TransferBillsResult result = transferService.transferBills(request); +System.out.println("转账成功,微信转账单号:" + result.getTransferBillNo()); +``` + +### 3. 查询转账结果 + +```java +// 通过商户单号查询 +TransferBillsGetResult result = transferService.getBillsByOutBillNo("T1642567890123"); + +// 通过微信转账单号查询 +TransferBillsGetResult result2 = transferService.getBillsByTransferBillNo("wx_transfer_bill_no"); + +System.out.println("转账状态:" + result.getState()); +``` + +### 4. 撤销转账(新功能) + +```java +// 撤销转账 +TransferBillsCancelResult cancelResult = transferService.transformBillsCancel("T1642567890123"); +System.out.println("撤销状态:" + cancelResult.getState()); +``` + +## 重要说明 + +### 转账场景ID (transfer_scene_id) +- **1005**: 佣金报酬(常用场景) +- 其他场景需要在微信商户平台申请 + +### 转账状态说明 +- **ACCEPTED**: 转账已受理 +- **PROCESSING**: 转账处理中 +- **SUCCESS**: 转账成功 +- **FAIL**: 转账失败 +- **CANCELLED**: 转账撤销完成 + +### 新开通商户使用建议 + +1. **优先使用** `TransferService` (新版API) +2. **不要使用** `MerchantTransferService` (可能不支持) +3. **必须设置** 转账场景ID (`transfer_scene_id`) +4. **建议开启** 回调通知以实时获取转账结果 + +## 完整示例代码 + +详细的使用示例请参考: +- 📄 [NEW_TRANSFER_API_USAGE.md](./NEW_TRANSFER_API_USAGE.md) - 详细使用指南 +- 💻 [NewTransferApiExample.java](./weixin-java-pay/src/main/java/com/github/binarywang/wxpay/example/NewTransferApiExample.java) - 完整代码示例 + +## 常见问题 + +**Q: 我是新开通的商户,应该使用哪个服务?** +A: 使用 `TransferService`,这是专为新版API设计的服务。 + +**Q: 新版API和旧版API有什么区别?** +A: 新版API使用单笔转账模式,支持更丰富的转账场景,并且支持撤销功能。 + +**Q: 如何设置转账场景ID?** +A: 在商户平台申请相应场景,常用的佣金报酬场景ID是"1005"。 + +**Q: 用户姓名需要加密吗?** +A: WxJava会自动处理RSA加密,您只需要传入明文姓名即可。 + +## 版本要求 + +- WxJava 版本:4.7.0+ +- 支持时间:2025年1月15日+ +- 适用商户:所有商户(新开通商户强制使用) + +通过以上说明,新开通的微信支付商户可以放心使用WxJava进行商户转账操作! \ No newline at end of file diff --git a/NEW_TRANSFER_API_USAGE.md b/NEW_TRANSFER_API_USAGE.md new file mode 100644 index 0000000000..9d1ac8254a --- /dev/null +++ b/NEW_TRANSFER_API_USAGE.md @@ -0,0 +1,148 @@ +# 微信支付新版商户转账API使用指南 + +## 概述 + +从2025年1月15日开始,微信支付推出了新版的商户转账API。新开通的商户号只能使用最新版本的商户转账接口。WxJava 已经完整支持新版转账API。 + +## API对比 + +### 传统转账API (仍然支持) +- **服务类**: `MerchantTransferService` +- **API前缀**: `/v3/transfer/batches` +- **特点**: 支持批量转账,一次可以转账给多个用户 + +### 新版转账API (2025.1.15+) +- **服务类**: `TransferService` +- **API前缀**: `/v3/fund-app/mch-transfer/transfer-bills` +- **特点**: 单笔转账,支持更丰富的转账场景 + +## 使用新版转账API + +### 1. 获取服务实例 + +```java +// 获取WxPayService实例 +WxPayService wxPayService = new WxPayServiceImpl(); +wxPayService.setConfig(config); + +// 获取新版转账服务 +TransferService transferService = wxPayService.getTransferService(); +``` + +### 2. 发起转账 + +```java +// 构建转账请求 +TransferBillsRequest request = TransferBillsRequest.newBuilder() + .appid("your_appid") // 应用ID + .outBillNo("T" + System.currentTimeMillis()) // 商户转账单号 + .transferSceneId("1005") // 转账场景ID(佣金报酬) + .openid("user_openid") // 用户openid + .userName("张三") // 收款用户姓名(可选,需要加密) + .transferAmount(100) // 转账金额(分) + .transferRemark("佣金报酬") // 转账备注 + .notifyUrl("https://your-domain.com/notify") // 回调地址(可选) + .userRecvPerception("Y") // 用户收款感知(可选) + .build(); + +try { + TransferBillsResult result = transferService.transferBills(request); + System.out.println("转账成功,微信转账单号:" + result.getTransferBillNo()); + System.out.println("状态:" + result.getState()); +} catch (WxPayException e) { + System.err.println("转账失败:" + e.getMessage()); +} +``` + +### 3. 查询转账结果 + +```java +// 通过商户单号查询 +String outBillNo = "T1642567890123"; +TransferBillsGetResult result = transferService.getBillsByOutBillNo(outBillNo); + +// 通过微信转账单号查询 +String transferBillNo = "1000000000000000000000000001"; +TransferBillsGetResult result2 = transferService.getBillsByTransferBillNo(transferBillNo); + +System.out.println("转账状态:" + result.getState()); +System.out.println("转账金额:" + result.getTransferAmount()); +``` + +### 4. 撤销转账 + +```java +// 撤销转账(仅在特定状态下可撤销) +String outBillNo = "T1642567890123"; +TransferBillsCancelResult cancelResult = transferService.transformBillsCancel(outBillNo); +System.out.println("撤销结果:" + cancelResult.getState()); +``` + +### 5. 处理回调通知 + +```java +// 在回调接口中处理通知 +@PostMapping("/transfer/notify") +public String handleTransferNotify(HttpServletRequest request) throws Exception { + String notifyData = StreamUtils.copyToString(request.getInputStream(), StandardCharsets.UTF_8); + + // 构建签名头 + SignatureHeader header = new SignatureHeader(); + header.setTimeStamp(request.getHeader("Wechatpay-Timestamp")); + header.setNonce(request.getHeader("Wechatpay-Nonce")); + header.setSignature(request.getHeader("Wechatpay-Signature")); + header.setSerial(request.getHeader("Wechatpay-Serial")); + + try { + TransferBillsNotifyResult notifyResult = transferService.parseTransferBillsNotifyResult(notifyData, header); + + // 处理业务逻辑 + String outBillNo = notifyResult.getOutBillNo(); + String state = notifyResult.getState(); + + System.out.println("转账单号:" + outBillNo + ",状态:" + state); + + return "SUCCESS"; + } catch (WxPayException e) { + System.err.println("验签失败:" + e.getMessage()); + return "FAIL"; + } +} +``` + +## 重要参数说明 + +### 转账场景ID (transfer_scene_id) +- **1005**: 佣金报酬(常用) +- 其他场景ID需要在商户平台申请 + +### 转账状态 +- **PROCESSING**: 转账中 +- **SUCCESS**: 转账成功 +- **FAILED**: 转账失败 +- **REFUNDED**: 已退款 + +### 用户收款感知 (user_recv_perception) +- **Y**: 用户会收到微信转账通知 +- **N**: 用户不会收到微信转账通知 + +## 新旧API对比总结 + +| 特性 | 传统API (MerchantTransferService) | 新版API (TransferService) | +|------|----------------------------------|---------------------------| +| 发起方式 | 批量转账 | 单笔转账 | +| API路径 | `/v3/transfer/batches` | `/v3/fund-app/mch-transfer/transfer-bills` | +| 场景支持 | 基础转账场景 | 丰富的转账场景 | +| 回调通知 | 支持 | 支持 | +| 撤销功能 | 不支持 | 支持 | +| 适用商户 | 所有商户 | 新开通商户必须使用 | + +## 注意事项 + +1. **新开通的商户号**: 必须使用新版API (`TransferService`) +2. **转账场景ID**: 需要在商户平台申请相应的转账场景 +3. **用户姓名加密**: 如果传入用户姓名,会自动进行RSA加密 +4. **回调验签**: 建议开启回调验签以确保安全性 +5. **错误处理**: 妥善处理各种异常情况 + +通过以上指南,您可以轻松使用WxJava的新版商户转账API功能。 \ No newline at end of file diff --git a/README.md b/README.md index e02e046b33..12b516c1b7 100644 --- a/README.md +++ b/README.md @@ -1,65 +1,65 @@ ## WxJava - 微信开发 Java SDK +[![Github](https://img.shields.io/github/stars/binarywang/WxJava?logo=github&style=flat&label=Stars)](https://github.com/binarywang/WxJava) +[![Gitee](https://gitee.com/binary/weixin-java-tools/badge/star.svg?theme=blue)](https://gitee.com/binary/weixin-java-tools) +[![GitCode](https://gitcode.com/binary/WxJava/star/badge.svg)](https://gitcode.com/binary/WxJava) -[![码云Gitee](https://gitee.com/binary/weixin-java-tools/badge/star.svg?theme=blue)](https://gitee.com/binary/weixin-java-tools) -[![Github](https://img.shields.io/github/stars/binarywang/WxJava?logo=github&style=flat)](https://github.com/binarywang/WxJava) -[![GitHub release](https://img.shields.io/github/release/binarywang/WxJava.svg)](https://github.com/binarywang/WxJava/releases) -[![Maven Central](https://img.shields.io/maven-central/v/com.github.binarywang/wx-java.svg)](http://mvnrepository.com/artifact/com.github.binarywang/wx-java) -[![Build Status](https://img.shields.io/circleci/project/github/binarywang/WxJava/develop.svg?sanitize=true)](https://circleci.com/gh/binarywang/WxJava/tree/develop) +[![GitHub release](https://img.shields.io/github/release/binarywang/WxJava?label=Release)](https://github.com/binarywang/WxJava/releases) +[![Maven Central](https://img.shields.io/maven-central/v/com.github.binarywang/wx-java?label=Maven)](https://central.sonatype.com/artifact/com.github.binarywang/wx-java/versions) +[![Build Status](https://img.shields.io/circleci/project/github/binarywang/WxJava/develop.svg?sanitize=true&label=Build)](https://circleci.com/gh/binarywang/WxJava/tree/develop) [![使用IntelliJ IDEA开发维护](https://img.shields.io/badge/IntelliJ%20IDEA-支持-blue.svg)](https://www.jetbrains.com/?from=WxJava-weixin-java-tools) [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) -#### 微信`Java`开发工具包,支持包括微信支付、开放平台、公众号、企业微信、视频号、小程序等微信功能模块的后端开发。 +
+ + Featured|HelloGitHub + + + binarywang%2FWxJava | 趋势转变 + +
+ +### 微信`Java`开发工具包,支持包括微信支付、开放平台、公众号、企业微信、视频号、小程序等微信功能模块的后端开发。
特别赞助 + + + + + + + + + + + + + +
+ + ccflow + +
+ + 计全支付Jeepay,开源支付系统 + + + + Mall4j + +
+ + mp qrcode + + + + diboot低代码开发平台 + + + + ad + +
- - - - - - - - - - - - - - - - -
- - ccflow - -
- - 计全支付Jeepay,开源支付系统 - - - - Mall4j - -
- - mp qrcode - - - - diboot低代码开发平台 - - - - aliyun ad - -
- - Featured|HelloGitHub - - binarywang%2FWxJava | 趋势转变 - -
### 重要信息 1. [`WxJava` 荣获 `GitCode` 2024年度十大开源社区奖项](https://mp.weixin.qq.com/s/wM_UlMsDm3IZ1CPPDvcvQw)。 @@ -89,7 +89,7 @@ --------------------------------- ### Maven 引用方式 -注意:最新版本(包括测试版)为 [![Maven Central](https://img.shields.io/maven-central/v/com.github.binarywang/wx-java.svg)](http://mvnrepository.com/artifact/com.github.binarywang/wx-java),以下为最新正式版。 +注意:最新版本(包括测试版)为 [![Maven Central](https://img.shields.io/maven-central/v/com.github.binarywang/wx-java.svg)](https://central.sonatype.com/artifact/com.github.binarywang/wx-java/versions),以下为最新正式版。 ```xml @@ -106,6 +106,13 @@ - 企业微信:`weixin-java-cp` - 微信视频号/微信小店:`weixin-java-channel` +**注意**: +- **移动应用开发**:如果你的移动应用(iOS/Android App)需要接入微信登录、分享等功能: + - 微信登录(网页授权):使用 `weixin-java-open` 模块,在服务端处理 OAuth 授权 + - 微信支付:使用 `weixin-java-pay` 模块 + - 客户端集成:需使用微信官方提供的移动端SDK(iOS/Android),本项目为服务端SDK +- **微信开放平台**(`weixin-java-open`)主要用于第三方平台,代公众号或小程序进行开发和管理 + --------------------------------- ### 版本说明 @@ -113,10 +120,13 @@
点此展开查看 -1. 本项目定为大约每两个月发布一次正式版(同时 `develop` 分支代码合并进入 `release` 分支),版本号格式为 `X.X.0`(如`2.1.0`,`2.2.0`等),遇到重大问题需修复会及时提交新版本,欢迎大家随时提交Pull Request; -2. BUG修复和新特性一般会先发布成小版本作为临时测试版本(如`3.6.8.B`,即尾号不为0,并添加B,以区别于正式版),代码仅存在于 `develop` 分支中; -3. 目前最新版本号为 [![Maven Central](https://img.shields.io/maven-central/v/com.github.binarywang/wx-java.svg)](http://mvnrepository.com/artifact/com.github.binarywang/wx-java) ,也可以通过访问链接 [【微信支付】](http://search.maven.org/#search%7Cgav%7C1%7Cg%3A%22com.github.binarywang%22%20AND%20a%3A%22weixin-java-pay%22) 、[【微信小程序】](http://search.maven.org/#search%7Cgav%7C1%7Cg%3A%22com.github.binarywang%22%20AND%20a%3A%22weixin-java-miniapp%22) 、[【公众号】](http://search.maven.org/#search%7Cgav%7C1%7Cg%3A%22com.github.binarywang%22%20AND%20a%3A%22weixin-java-mp%22) 、[【企业微信】](http://search.maven.org/#search%7Cgav%7C1%7Cg%3A%22com.github.binarywang%22%20AND%20a%3A%22weixin-java-cp%22)、[【开放平台】](http://search.maven.org/#search%7Cgav%7C1%7Cg%3A%22com.github.binarywang%22%20AND%20a%3A%22weixin-java-open%22) -分别查看所有最新的版本。 +1. 本项目定为大约每半年左右发布一次正式版,遇到重大问题需修复会及时提交新版本,欢迎大家随时提交 `Pull Request`; +2. 每次代码更新都会自动构建出新版本方便及时尝鲜,版本号格式为 `x.x.x-时间戳`; +3. 发布正式版时,`develop` 分支代码合并进入 `release` 分支),版本号格式为 `X.X.0`(如`2.1.0`,`2.2.0`等); +4. 每隔一段时间后,会发布测试版本(如`3.6.8.B`,即尾号不为0,并添加B,以区别于正式版),代码仅存在于 `develop` 分支中; +5. 目前最新版本号为 [![Maven Central](https://img.shields.io/maven-central/v/com.github.binarywang/wx-java.svg)](http://mvnrepository.com/artifact/com.github.binarywang/wx-java) ,也可以通过访问以下链接分别查看各个模块最新的版本: +[【微信支付】](https://central.sonatype.com/artifact/com.github.binarywang/weixin-java-pay/versions) 、[【小程序】](https://central.sonatype.com/artifact/com.github.binarywang/weixin-java-miniapp/versions) 、[【公众号】](https://central.sonatype.com/artifact/com.github.binarywang/weixin-java-mp/versions) 、[【企业微信】](https://central.sonatype.com/artifact/com.github.binarywang/weixin-java-cp/versions)、[【开放平台】](https://central.sonatype.com/artifact/com.github.binarywang/weixin-java-open/versions)、[【视频号】](https://central.sonatype.com/artifact/com.github.binarywang/weixin-java-channel/versions) +
diff --git a/images/banners/ccflow.png b/images/banners/ccflow.png deleted file mode 100644 index 1209739f6a..0000000000 Binary files a/images/banners/ccflow.png and /dev/null differ diff --git a/images/banners/diboot.png b/images/banners/diboot.png deleted file mode 100644 index c22d0b8ed8..0000000000 Binary files a/images/banners/diboot.png and /dev/null differ diff --git a/images/banners/planB.jpg b/images/banners/planB.jpg deleted file mode 100644 index 139957fbef..0000000000 Binary files a/images/banners/planB.jpg and /dev/null differ diff --git a/others/mvnw b/others/mvnw old mode 100644 new mode 100755 diff --git a/pom.xml b/pom.xml index c7c40521b5..0b164611ca 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 com.github.binarywang wx-java - 4.7.2.B + 4.7.8.B pom WxJava - Weixin/Wechat Java SDK 微信开发Java SDK @@ -136,10 +136,8 @@ UTF-8 4.5.13 - 9.4.56.v20240826 - + 9.4.57.v20241219 - @@ -156,7 +154,13 @@ com.squareup.okhttp3 okhttp - 4.5.0 + 4.12.0 + provided + + + org.apache.httpcomponents.client5 + httpclient5 + 5.5 provided @@ -183,7 +187,7 @@ org.apache.commons commons-lang3 - 3.10 + 3.18.0 org.slf4j @@ -198,17 +202,19 @@ com.google.guava guava - 32.1.2-jre + 33.3.1-jre com.google.code.gson gson - 2.10.1 + 2.13.1 - com.fasterxml.jackson.dataformat - jackson-dataformat-xml - 2.15.2 + com.fasterxml.jackson + jackson-bom + 2.18.4 + pom + import @@ -327,22 +333,11 @@ org.bouncycastle bcpkix-jdk18on - 1.78.1 + 1.80 - - - ossrh - https://oss.sonatype.org/content/repositories/snapshots - - - ossrh - https://oss.sonatype.org/service/local/staging/deploy/maven2/ - - - doclint-java8-disable @@ -432,14 +427,14 @@ - org.sonatype.plugins - nexus-staging-maven-plugin - 1.6.3 + org.sonatype.central + central-publishing-maven-plugin + 0.7.0 true - ossrh - https://oss.sonatype.org/ - true + Release WxJava:${project.version} + central + true @@ -473,6 +468,21 @@ + + org.apache.maven.plugins + maven-source-plugin + 3.3.0 + + + attach-sources + + verify + + jar-no-fork + + + + diff --git a/solon-plugins/pom.xml b/solon-plugins/pom.xml index 9585e96179..5a4dd8728f 100644 --- a/solon-plugins/pom.xml +++ b/solon-plugins/pom.xml @@ -6,7 +6,7 @@ com.github.binarywang wx-java - 4.7.2.B + 4.7.8.B pom wx-java-solon-plugins @@ -14,7 +14,7 @@ WxJava 各个模块的 Solon Plugin - 3.0.1 + 3.2.0 diff --git a/solon-plugins/wx-java-channel-multi-solon-plugin/pom.xml b/solon-plugins/wx-java-channel-multi-solon-plugin/pom.xml index a9b794a965..983e6ef38e 100644 --- a/solon-plugins/wx-java-channel-multi-solon-plugin/pom.xml +++ b/solon-plugins/wx-java-channel-multi-solon-plugin/pom.xml @@ -5,7 +5,7 @@ wx-java-solon-plugins com.github.binarywang - 4.7.2.B + 4.7.8.B 4.0.0 diff --git a/solon-plugins/wx-java-channel-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/channel/configuration/services/AbstractWxChannelConfiguration.java b/solon-plugins/wx-java-channel-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/channel/configuration/services/AbstractWxChannelConfiguration.java index 8531d92658..eb80b5f7f3 100644 --- a/solon-plugins/wx-java-channel-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/channel/configuration/services/AbstractWxChannelConfiguration.java +++ b/solon-plugins/wx-java-channel-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/channel/configuration/services/AbstractWxChannelConfiguration.java @@ -8,6 +8,7 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import me.chanjar.weixin.channel.api.WxChannelService; +import me.chanjar.weixin.channel.api.impl.WxChannelServiceHttpComponentsImpl; import me.chanjar.weixin.channel.api.impl.WxChannelServiceHttpClientImpl; import me.chanjar.weixin.channel.api.impl.WxChannelServiceImpl; import me.chanjar.weixin.channel.config.WxChannelConfig; @@ -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/solon-plugins/wx-java-channel-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/channel/enums/HttpClientType.java b/solon-plugins/wx-java-channel-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/channel/enums/HttpClientType.java index 1899e9e9f6..c34533c6d1 100644 --- a/solon-plugins/wx-java-channel-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/channel/enums/HttpClientType.java +++ b/solon-plugins/wx-java-channel-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/channel/enums/HttpClientType.java @@ -11,6 +11,10 @@ public enum HttpClientType { * HttpClient */ HTTP_CLIENT, + /** + * HttpComponents + */ + HTTP_COMPONENTS // WxChannelServiceOkHttpImpl 实现经测试无法正常完成业务固暂不支持OK_HTTP方式 // /** // * OkHttp. diff --git a/solon-plugins/wx-java-channel-solon-plugin/README.md b/solon-plugins/wx-java-channel-solon-plugin/README.md new file mode 100644 index 0000000000..a7168a8edc --- /dev/null +++ b/solon-plugins/wx-java-channel-solon-plugin/README.md @@ -0,0 +1,92 @@ +# wx-java-channel-solon-plugin + +## 快速开始 +1. 引入依赖 + ```xml + + + com.github.binarywang + wx-java-channel-solon-plugin + ${version} + + + + + redis.clients + jedis + ${jedis.version} + + + + + org.redisson + redisson + ${redisson.version} + + + ``` +2. 添加配置(app.properties) + ```properties + # 视频号配置(必填) + ## 视频号小店的appId和secret + wx.channel.app-id=@appId + wx.channel.secret=@secret + # 视频号配置 选填 + ## 设置视频号小店消息服务器配置的token + wx.channel.token=@token + ## 设置视频号小店消息服务器配置的EncodingAESKey + wx.channel.aes-key= + ## 支持JSON或者XML格式,默认JSON + wx.channel.msg-data-format=JSON + ## 是否使用稳定版 Access Token + wx.channel.use-stable-access-token=false + + + # ConfigStorage 配置(选填) + ## 配置类型: memory(默认), jedis, redisson, redis_template + wx.channel.config-storage.type=memory + ## 相关redis前缀配置: wx:channel(默认) + wx.channel.config-storage.key-prefix=wx:channel + wx.channel.config-storage.redis.host=127.0.0.1 + wx.channel.config-storage.redis.port=6379 + wx.channel.config-storage.redis.password=123456 + + + # http 客户端配置(选填) + ## # http客户端类型: http_client(默认) + wx.channel.config-storage.http-client-type=http_client + wx.channel.config-storage.http-proxy-host= + wx.channel.config-storage.http-proxy-port= + wx.channel.config-storage.http-proxy-username= + wx.channel.config-storage.http-proxy-password= + ## 最大重试次数,默认:5 次,如果小于 0,则为 0 + wx.channel.config-storage.max-retry-times=5 + ## 重试时间间隔步进,默认:1000 毫秒,如果小于 0,则为 1000 + wx.channel.config-storage.retry-sleep-millis=1000 + ``` +3. 自动注入的类型 +- `WxChannelService` +- `WxChannelConfig` +4. 使用样例 + +```java +import me.chanjar.weixin.channel.api.WxChannelService; +import me.chanjar.weixin.channel.bean.shop.ShopInfoResponse; +import me.chanjar.weixin.channel.util.JsonUtils; +import me.chanjar.weixin.common.error.WxErrorException; +import org.noear.solon.annotation.Inject; + +@Component +public class DemoService { + @Inject + private WxChannelService wxChannelService; + + public String getShopInfo() throws WxErrorException { + // 获取店铺基本信息 + ShopInfoResponse response = wxChannelService.getBasicService().getShopInfo(); + // 此处为演示,如果要返回response的结果,建议自己封装一个VO,避免直接返回response + return JsonUtils.encode(response); + } +} +``` + diff --git a/solon-plugins/wx-java-channel-solon-plugin/pom.xml b/solon-plugins/wx-java-channel-solon-plugin/pom.xml index 4e0dc723a8..778b34cad0 100644 --- a/solon-plugins/wx-java-channel-solon-plugin/pom.xml +++ b/solon-plugins/wx-java-channel-solon-plugin/pom.xml @@ -3,7 +3,7 @@ wx-java-solon-plugins com.github.binarywang - 4.7.2.B + 4.7.8.B 4.0.0 diff --git a/solon-plugins/wx-java-cp-multi-solon-plugin/pom.xml b/solon-plugins/wx-java-cp-multi-solon-plugin/pom.xml index 9cf2b31724..28f4006b53 100644 --- a/solon-plugins/wx-java-cp-multi-solon-plugin/pom.xml +++ b/solon-plugins/wx-java-cp-multi-solon-plugin/pom.xml @@ -4,7 +4,7 @@ wx-java-solon-plugins com.github.binarywang - 4.7.2.B + 4.7.8.B 4.0.0 diff --git a/solon-plugins/wx-java-cp-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/cp_multi/configuration/services/AbstractWxCpConfiguration.java b/solon-plugins/wx-java-cp-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/cp_multi/configuration/services/AbstractWxCpConfiguration.java index 8710bba3ca..ada4ac504c 100644 --- a/solon-plugins/wx-java-cp-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/cp_multi/configuration/services/AbstractWxCpConfiguration.java +++ b/solon-plugins/wx-java-cp-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/cp_multi/configuration/services/AbstractWxCpConfiguration.java @@ -7,10 +7,7 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import me.chanjar.weixin.cp.api.WxCpService; -import me.chanjar.weixin.cp.api.impl.WxCpServiceApacheHttpClientImpl; -import me.chanjar.weixin.cp.api.impl.WxCpServiceImpl; -import me.chanjar.weixin.cp.api.impl.WxCpServiceJoddHttpImpl; -import me.chanjar.weixin.cp.api.impl.WxCpServiceOkHttpImpl; +import me.chanjar.weixin.cp.api.impl.*; import me.chanjar.weixin.cp.config.WxCpConfigStorage; import me.chanjar.weixin.cp.config.impl.WxCpDefaultConfigImpl; import org.apache.commons.lang3.StringUtils; @@ -96,6 +93,9 @@ private WxCpService wxCpService(WxCpConfigStorage wxCpConfigStorage, WxCpMultiPr case HTTP_CLIENT: wxCpService = new WxCpServiceApacheHttpClientImpl(); break; + case HTTP_COMPONENTS: + wxCpService = new WxCpServiceHttpComponentsImpl(); + break; default: wxCpService = new WxCpServiceImpl(); break; 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 5544a92e00..821f885f98 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 @@ -117,6 +117,10 @@ public enum HttpClientType { * HttpClient */ HTTP_CLIENT, + /** + * HttpComponents + */ + HTTP_COMPONENTS, /** * OkHttp */ diff --git a/solon-plugins/wx-java-cp-solon-plugin/pom.xml b/solon-plugins/wx-java-cp-solon-plugin/pom.xml index 46266b8e80..5f224f4f99 100644 --- a/solon-plugins/wx-java-cp-solon-plugin/pom.xml +++ b/solon-plugins/wx-java-cp-solon-plugin/pom.xml @@ -4,7 +4,7 @@ wx-java-solon-plugins com.github.binarywang - 4.7.2.B + 4.7.8.B 4.0.0 diff --git a/solon-plugins/wx-java-miniapp-multi-solon-plugin/pom.xml b/solon-plugins/wx-java-miniapp-multi-solon-plugin/pom.xml index 1e5dab26b6..0e9e015ff3 100644 --- a/solon-plugins/wx-java-miniapp-multi-solon-plugin/pom.xml +++ b/solon-plugins/wx-java-miniapp-multi-solon-plugin/pom.xml @@ -5,7 +5,7 @@ wx-java-solon-plugins com.github.binarywang - 4.7.2.B + 4.7.8.B 4.0.0 diff --git a/solon-plugins/wx-java-miniapp-solon-plugin/pom.xml b/solon-plugins/wx-java-miniapp-solon-plugin/pom.xml index 805018b81d..ab9f3aaef3 100644 --- a/solon-plugins/wx-java-miniapp-solon-plugin/pom.xml +++ b/solon-plugins/wx-java-miniapp-solon-plugin/pom.xml @@ -4,7 +4,7 @@ wx-java-solon-plugins com.github.binarywang - 4.7.2.B + 4.7.8.B 4.0.0 diff --git a/solon-plugins/wx-java-mp-multi-solon-plugin/pom.xml b/solon-plugins/wx-java-mp-multi-solon-plugin/pom.xml index 4ccc4fa7d7..405a2cf52a 100644 --- a/solon-plugins/wx-java-mp-multi-solon-plugin/pom.xml +++ b/solon-plugins/wx-java-mp-multi-solon-plugin/pom.xml @@ -5,7 +5,7 @@ wx-java-solon-plugins com.github.binarywang - 4.7.2.B + 4.7.8.B 4.0.0 diff --git a/solon-plugins/wx-java-mp-solon-plugin/pom.xml b/solon-plugins/wx-java-mp-solon-plugin/pom.xml index 3aa2b9be41..43609279e1 100644 --- a/solon-plugins/wx-java-mp-solon-plugin/pom.xml +++ b/solon-plugins/wx-java-mp-solon-plugin/pom.xml @@ -5,7 +5,7 @@ wx-java-solon-plugins com.github.binarywang - 4.7.2.B + 4.7.8.B 4.0.0 diff --git a/solon-plugins/wx-java-open-solon-plugin/pom.xml b/solon-plugins/wx-java-open-solon-plugin/pom.xml index 355ef6f939..49de3701a3 100644 --- a/solon-plugins/wx-java-open-solon-plugin/pom.xml +++ b/solon-plugins/wx-java-open-solon-plugin/pom.xml @@ -5,7 +5,7 @@ wx-java-solon-plugins com.github.binarywang - 4.7.2.B + 4.7.8.B 4.0.0 diff --git a/solon-plugins/wx-java-pay-solon-plugin/pom.xml b/solon-plugins/wx-java-pay-solon-plugin/pom.xml index 6357cc908b..dc32f659d8 100644 --- a/solon-plugins/wx-java-pay-solon-plugin/pom.xml +++ b/solon-plugins/wx-java-pay-solon-plugin/pom.xml @@ -5,7 +5,7 @@ wx-java-solon-plugins com.github.binarywang - 4.7.2.B + 4.7.8.B 4.0.0 diff --git a/solon-plugins/wx-java-qidian-solon-plugin/pom.xml b/solon-plugins/wx-java-qidian-solon-plugin/pom.xml index 4ff6dba008..a577e2cc79 100644 --- a/solon-plugins/wx-java-qidian-solon-plugin/pom.xml +++ b/solon-plugins/wx-java-qidian-solon-plugin/pom.xml @@ -3,7 +3,7 @@ wx-java-solon-plugins com.github.binarywang - 4.7.2.B + 4.7.8.B 4.0.0 diff --git a/solon-plugins/wx-java-qidian-solon-plugin/src/main/java/com/binarywang/solon/wxjava/qidian/config/WxQidianStorageAutoConfiguration.java b/solon-plugins/wx-java-qidian-solon-plugin/src/main/java/com/binarywang/solon/wxjava/qidian/config/WxQidianStorageAutoConfiguration.java index 7f78864226..a99a8178c9 100644 --- a/solon-plugins/wx-java-qidian-solon-plugin/src/main/java/com/binarywang/solon/wxjava/qidian/config/WxQidianStorageAutoConfiguration.java +++ b/solon-plugins/wx-java-qidian-solon-plugin/src/main/java/com/binarywang/solon/wxjava/qidian/config/WxQidianStorageAutoConfiguration.java @@ -18,11 +18,13 @@ import org.noear.solon.annotation.Configuration; import org.noear.solon.annotation.Inject; 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.time.Duration; import java.util.Set; /** @@ -75,7 +77,7 @@ private WxQidianConfigStorage defaultConfigStorage() { } private WxQidianConfigStorage jedisConfigStorage() { - Pool jedisPool; + Pool jedisPool; if (StringUtils.isNotEmpty(redisHost) || StringUtils.isNotEmpty(redisHost2)) { jedisPool = getJedisPool(); } else { @@ -104,7 +106,7 @@ private void setWxMpInfo(WxQidianDefaultConfigImpl config) { } } - private Pool getJedisPool() { + private Pool getJedisPool() { WxQidianProperties.ConfigStorage storage = wxQidianProperties.getConfigStorage(); RedisProperties redis = storage.getRedis(); @@ -116,7 +118,7 @@ private Pool getJedisPool() { config.setMaxIdle(redis.getMaxIdle()); } if (redis.getMaxWaitMillis() != null) { - config.setMaxWaitMillis(redis.getMaxWaitMillis()); + config.setMaxWait(Duration.ofMillis(redis.getMaxWaitMillis())); } if (redis.getMinIdle() != null) { config.setMinIdle(redis.getMinIdle()); diff --git a/spring-boot-starters/pom.xml b/spring-boot-starters/pom.xml index c5cb2954d3..70e8c33395 100644 --- a/spring-boot-starters/pom.xml +++ b/spring-boot-starters/pom.xml @@ -6,7 +6,7 @@ com.github.binarywang wx-java - 4.7.2.B + 4.7.8.B pom wx-java-spring-boot-starters @@ -26,6 +26,7 @@ wx-java-open-spring-boot-starter wx-java-qidian-spring-boot-starter wx-java-cp-multi-spring-boot-starter + wx-java-cp-tp-multi-spring-boot-starter wx-java-cp-spring-boot-starter wx-java-channel-spring-boot-starter wx-java-channel-multi-spring-boot-starter diff --git a/spring-boot-starters/wx-java-channel-multi-spring-boot-starter/pom.xml b/spring-boot-starters/wx-java-channel-multi-spring-boot-starter/pom.xml index dca311c015..f28a510d51 100644 --- a/spring-boot-starters/wx-java-channel-multi-spring-boot-starter/pom.xml +++ b/spring-boot-starters/wx-java-channel-multi-spring-boot-starter/pom.xml @@ -5,7 +5,7 @@ wx-java-spring-boot-starters com.github.binarywang - 4.7.2.B + 4.7.8.B 4.0.0 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 fab65363c4..3462bbc25e 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 @@ -119,6 +119,8 @@ private void configApp(WxChannelDefaultConfigImpl config, WxChannelSinglePropert config.setAesKey(aesKey); } config.setStableAccessToken(useStableAccessToken); + config.setApiHostUrl(StringUtils.trimToNull(wxChannelSingleProperties.getApiHostUrl())); + config.setAccessTokenUrl(StringUtils.trimToNull(wxChannelSingleProperties.getAccessTokenUrl())); } private void configHttp(WxChannelDefaultConfigImpl config, WxChannelMultiProperties.ConfigStorage storage) { diff --git a/spring-boot-starters/wx-java-channel-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/channel/properties/WxChannelSingleProperties.java b/spring-boot-starters/wx-java-channel-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/channel/properties/WxChannelSingleProperties.java index 3e8e2f52bf..4b613e3bf6 100644 --- a/spring-boot-starters/wx-java-channel-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/channel/properties/WxChannelSingleProperties.java +++ b/spring-boot-starters/wx-java-channel-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/channel/properties/WxChannelSingleProperties.java @@ -40,4 +40,16 @@ public class WxChannelSingleProperties implements Serializable { * 是否使用稳定版 Access Token */ private boolean useStableAccessToken = false; + + /** + * 自定义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-channel-spring-boot-starter/README.md b/spring-boot-starters/wx-java-channel-spring-boot-starter/README.md index 058a957359..398001a286 100644 --- a/spring-boot-starters/wx-java-channel-spring-boot-starter/README.md +++ b/spring-boot-starters/wx-java-channel-spring-boot-starter/README.md @@ -6,7 +6,7 @@ com.github.binarywang - wx-java-channel-multi-spring-boot-starter + wx-java-channel-spring-boot-starter ${version} diff --git a/spring-boot-starters/wx-java-channel-spring-boot-starter/pom.xml b/spring-boot-starters/wx-java-channel-spring-boot-starter/pom.xml index 8f427c6d03..c8ced3b3b1 100644 --- a/spring-boot-starters/wx-java-channel-spring-boot-starter/pom.xml +++ b/spring-boot-starters/wx-java-channel-spring-boot-starter/pom.xml @@ -3,7 +3,7 @@ wx-java-spring-boot-starters com.github.binarywang - 4.7.2.B + 4.7.8.B 4.0.0 diff --git a/spring-boot-starters/wx-java-channel-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/channel/config/storage/AbstractWxChannelConfigStorageConfiguration.java b/spring-boot-starters/wx-java-channel-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/channel/config/storage/AbstractWxChannelConfigStorageConfiguration.java index d554c31eca..2a7978640d 100644 --- a/spring-boot-starters/wx-java-channel-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/channel/config/storage/AbstractWxChannelConfigStorageConfiguration.java +++ b/spring-boot-starters/wx-java-channel-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/channel/config/storage/AbstractWxChannelConfigStorageConfiguration.java @@ -16,6 +16,8 @@ protected WxChannelDefaultConfigImpl config(WxChannelDefaultConfigImpl config, W config.setAesKey(StringUtils.trimToNull(properties.getAesKey())); config.setMsgDataFormat(StringUtils.trimToNull(properties.getMsgDataFormat())); config.setStableAccessToken(properties.isUseStableAccessToken()); + config.setApiHostUrl(StringUtils.trimToNull(properties.getApiHostUrl())); + config.setAccessTokenUrl(StringUtils.trimToNull(properties.getAccessTokenUrl())); WxChannelProperties.ConfigStorage configStorageProperties = properties.getConfigStorage(); config.setHttpProxyHost(configStorageProperties.getHttpProxyHost()); 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 f2628b2ec3..43c35fbd1d 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 @@ -46,6 +46,18 @@ public class WxChannelProperties { */ private boolean useStableAccessToken = false; + /** + * 自定义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-cp-multi-spring-boot-starter/pom.xml b/spring-boot-starters/wx-java-cp-multi-spring-boot-starter/pom.xml index 6ea0eb005f..cdf607ed30 100644 --- a/spring-boot-starters/wx-java-cp-multi-spring-boot-starter/pom.xml +++ b/spring-boot-starters/wx-java-cp-multi-spring-boot-starter/pom.xml @@ -4,7 +4,7 @@ wx-java-spring-boot-starters com.github.binarywang - 4.7.2.B + 4.7.8.B 4.0.0 diff --git a/spring-boot-starters/wx-java-cp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/configuration/services/AbstractWxCpConfiguration.java b/spring-boot-starters/wx-java-cp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/configuration/services/AbstractWxCpConfiguration.java index ec8aaa4f26..9b959222e0 100644 --- a/spring-boot-starters/wx-java-cp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/configuration/services/AbstractWxCpConfiguration.java +++ b/spring-boot-starters/wx-java-cp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/configuration/services/AbstractWxCpConfiguration.java @@ -139,6 +139,9 @@ private void configCorp(WxCpDefaultConfigImpl config, WxCpSingleProperties wxCpS if (StringUtils.isNotBlank(msgAuditLibPath)) { config.setMsgAuditLibPath(msgAuditLibPath); } + if (StringUtils.isNotBlank(wxCpSingleProperties.getBaseApiUrl())) { + config.setBaseApiUrl(wxCpSingleProperties.getBaseApiUrl()); + } } private void configHttp(WxCpDefaultConfigImpl config, WxCpMultiProperties.ConfigStorage storage) { diff --git a/spring-boot-starters/wx-java-cp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/properties/WxCpSingleProperties.java b/spring-boot-starters/wx-java-cp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/properties/WxCpSingleProperties.java index ec1b97899f..8ad7149fe6 100644 --- a/spring-boot-starters/wx-java-cp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/properties/WxCpSingleProperties.java +++ b/spring-boot-starters/wx-java-cp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/properties/WxCpSingleProperties.java @@ -43,4 +43,10 @@ public class WxCpSingleProperties implements Serializable { * 微信企业号应用 会话存档类库路径 */ private String msgAuditLibPath; + + /** + * 自定义企业微信服务器baseUrl,用于替换默认的 https://qyapi.weixin.qq.com + * 例如:http://proxy.company.com:8080 + */ + private String baseApiUrl; } diff --git a/spring-boot-starters/wx-java-cp-spring-boot-starter/pom.xml b/spring-boot-starters/wx-java-cp-spring-boot-starter/pom.xml index fdbe3d1e54..b0044365e8 100644 --- a/spring-boot-starters/wx-java-cp-spring-boot-starter/pom.xml +++ b/spring-boot-starters/wx-java-cp-spring-boot-starter/pom.xml @@ -4,7 +4,7 @@ wx-java-spring-boot-starters com.github.binarywang - 4.7.2.B + 4.7.8.B 4.0.0 diff --git a/spring-boot-starters/wx-java-cp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/properties/WxCpProperties.java b/spring-boot-starters/wx-java-cp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/properties/WxCpProperties.java index b87ddc2454..c93a7e187f 100644 --- a/spring-boot-starters/wx-java-cp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/properties/WxCpProperties.java +++ b/spring-boot-starters/wx-java-cp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/properties/WxCpProperties.java @@ -48,6 +48,12 @@ public class WxCpProperties { */ private String msgAuditLibPath; + /** + * 自定义企业微信服务器baseUrl,用于替换默认的 https://qyapi.weixin.qq.com + * 例如:http://proxy.company.com:8080 + */ + private String baseApiUrl; + /** * 配置存储策略,默认内存 */ diff --git a/spring-boot-starters/wx-java-cp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/storage/AbstractWxCpConfigStorageConfiguration.java b/spring-boot-starters/wx-java-cp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/storage/AbstractWxCpConfigStorageConfiguration.java index 0f2995e967..2b1d8c13c5 100644 --- a/spring-boot-starters/wx-java-cp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/storage/AbstractWxCpConfigStorageConfiguration.java +++ b/spring-boot-starters/wx-java-cp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/storage/AbstractWxCpConfigStorageConfiguration.java @@ -37,6 +37,9 @@ protected WxCpDefaultConfigImpl config(WxCpDefaultConfigImpl config, WxCpPropert if (StringUtils.isNotBlank(msgAuditLibPath)) { config.setMsgAuditLibPath(msgAuditLibPath); } + if (StringUtils.isNotBlank(properties.getBaseApiUrl())) { + config.setBaseApiUrl(properties.getBaseApiUrl()); + } WxCpProperties.ConfigStorage storage = properties.getConfigStorage(); String httpProxyHost = storage.getHttpProxyHost(); diff --git a/spring-boot-starters/wx-java-cp-tp-multi-spring-boot-starter/README.md b/spring-boot-starters/wx-java-cp-tp-multi-spring-boot-starter/README.md new file mode 100644 index 0000000000..624c6b3150 --- /dev/null +++ b/spring-boot-starters/wx-java-cp-tp-multi-spring-boot-starter/README.md @@ -0,0 +1,97 @@ +# wx-java-cp-multi-spring-boot-starter + +企业微信多账号配置 + +- 实现多 WxCpService 初始化。 +- 未实现 WxCpTpService 初始化,需要的小伙伴可以参考多 WxCpService 配置的实现。 +- 未实现 WxCpCgService 初始化,需要的小伙伴可以参考多 WxCpService 配置的实现。 + +## 快速开始 + +1. 引入依赖 + ```xml + + com.github.binarywang + wx-java-cp-multi-spring-boot-starter + ${version} + + ``` +2. 添加配置(application.properties) + ```properties + # 应用 1 配置 + wx.cp.corps.tenantId1.corp-id = @corp-id + wx.cp.corps.tenantId1.corp-secret = @corp-secret + ## 选填 + wx.cp.corps.tenantId1.agent-id = @agent-id + wx.cp.corps.tenantId1.token = @token + wx.cp.corps.tenantId1.aes-key = @aes-key + wx.cp.corps.tenantId1.msg-audit-priKey = @msg-audit-priKey + wx.cp.corps.tenantId1.msg-audit-lib-path = @msg-audit-lib-path + + # 应用 2 配置 + wx.cp.corps.tenantId2.corp-id = @corp-id + wx.cp.corps.tenantId2.corp-secret = @corp-secret + ## 选填 + wx.cp.corps.tenantId2.agent-id = @agent-id + wx.cp.corps.tenantId2.token = @token + wx.cp.corps.tenantId2.aes-key = @aes-key + wx.cp.corps.tenantId2.msg-audit-priKey = @msg-audit-priKey + wx.cp.corps.tenantId2.msg-audit-lib-path = @msg-audit-lib-path + + # 公共配置 + ## ConfigStorage 配置(选填) + wx.cp.config-storage.type=memory # 配置类型: memory(默认), jedis, redisson, redistemplate + ## http 客户端配置(选填) + ## # http客户端类型: http_client(默认), ok_http, jodd_http + wx.cp.config-storage.http-client-type=http_client + wx.cp.config-storage.http-proxy-host= + wx.cp.config-storage.http-proxy-port= + wx.cp.config-storage.http-proxy-username= + wx.cp.config-storage.http-proxy-password= + ## 最大重试次数,默认:5 次,如果小于 0,则为 0 + wx.cp.config-storage.max-retry-times=5 + ## 重试时间间隔步进,默认:1000 毫秒,如果小于 0,则为 1000 + wx.cp.config-storage.retry-sleep-millis=1000 + ``` +3. 支持自动注入的类型: `WxCpMultiServices` + +4. 使用样例 + +```java +import com.binarywang.spring.starter.wxjava.cp.service.WxCpTpMultiServices; +import me.chanjar.weixin.cp.api.WxCpService; +import me.chanjar.weixin.cp.api.WxCpUserService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +@Service +public class DemoService { + @Autowired + private WxCpTpMultiServices wxCpTpMultiServices; + + public void test() { + // 应用 1 的 WxCpService + WxCpService wxCpService1 = wxCpMultiServices.getWxCpService("tenantId1"); + WxCpUserService userService1 = wxCpService1.getUserService(); + userService1.getUserId("xxx"); + // todo ... + + // 应用 2 的 WxCpService + WxCpService wxCpService2 = wxCpMultiServices.getWxCpService("tenantId2"); + WxCpUserService userService2 = wxCpService2.getUserService(); + userService2.getUserId("xxx"); + // todo ... + + // 应用 3 的 WxCpService + WxCpService wxCpService3 = wxCpMultiServices.getWxCpService("tenantId3"); + // 判断是否为空 + if (wxCpService3 == null) { + // todo wxCpService3 为空,请先配置 tenantId3 企业微信应用参数 + return; + } + WxCpUserService userService3 = wxCpService3.getUserService(); + userService3.getUserId("xxx"); + // todo ... + } +} +``` diff --git a/spring-boot-starters/wx-java-cp-tp-multi-spring-boot-starter/pom.xml b/spring-boot-starters/wx-java-cp-tp-multi-spring-boot-starter/pom.xml new file mode 100644 index 0000000000..766021b7e8 --- /dev/null +++ b/spring-boot-starters/wx-java-cp-tp-multi-spring-boot-starter/pom.xml @@ -0,0 +1,60 @@ + + + + wx-java-spring-boot-starters + com.github.binarywang + 4.7.8.B + + 4.0.0 + + wx-java-cp-tp-multi-spring-boot-starter + WxJava - Spring Boot Starter for WxCp::支持多账号配置 + 微信企业号开发的 Spring Boot Starter::支持多账号配置 + + + + com.github.binarywang + weixin-java-cp + ${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-cp-tp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/autoconfigure/WxCpTpMultiAutoConfiguration.java b/spring-boot-starters/wx-java-cp-tp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/autoconfigure/WxCpTpMultiAutoConfiguration.java new file mode 100644 index 0000000000..1ec07c5c5b --- /dev/null +++ b/spring-boot-starters/wx-java-cp-tp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/autoconfigure/WxCpTpMultiAutoConfiguration.java @@ -0,0 +1,16 @@ +package com.binarywang.spring.starter.wxjava.cp.autoconfigure; + +import com.binarywang.spring.starter.wxjava.cp.configuration.WxCpTpMultiServicesAutoConfiguration; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; + +/** + * 企业微信自动注册 + * + * @author yl + * created on 2023/10/16 + */ +@Configuration +@Import(WxCpTpMultiServicesAutoConfiguration.class) +public class WxCpTpMultiAutoConfiguration { +} diff --git a/spring-boot-starters/wx-java-cp-tp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/configuration/WxCpTpMultiServicesAutoConfiguration.java b/spring-boot-starters/wx-java-cp-tp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/configuration/WxCpTpMultiServicesAutoConfiguration.java new file mode 100644 index 0000000000..1f6e784236 --- /dev/null +++ b/spring-boot-starters/wx-java-cp-tp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/configuration/WxCpTpMultiServicesAutoConfiguration.java @@ -0,0 +1,27 @@ +package com.binarywang.spring.starter.wxjava.cp.configuration; + +import com.binarywang.spring.starter.wxjava.cp.configuration.services.WxCpTpInJedisTpConfiguration; +import com.binarywang.spring.starter.wxjava.cp.configuration.services.WxCpTpInMemoryTpConfiguration; +import com.binarywang.spring.starter.wxjava.cp.configuration.services.WxCpTpInRedisTemplateTpConfiguration; +import com.binarywang.spring.starter.wxjava.cp.configuration.services.WxCpTpInRedissonTpConfiguration; +import com.binarywang.spring.starter.wxjava.cp.properties.WxCpTpMultiProperties; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; + +/** + * 企业微信平台相关服务自动注册 + * + * @author yl + * created on 2023/10/16 + */ +@Configuration +@EnableConfigurationProperties(WxCpTpMultiProperties.class) +@Import({ + WxCpTpInJedisTpConfiguration.class, + WxCpTpInMemoryTpConfiguration.class, + WxCpTpInRedissonTpConfiguration.class, + WxCpTpInRedisTemplateTpConfiguration.class +}) +public class WxCpTpMultiServicesAutoConfiguration { +} diff --git a/spring-boot-starters/wx-java-cp-tp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/configuration/services/AbstractWxCpTpConfiguration.java b/spring-boot-starters/wx-java-cp-tp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/configuration/services/AbstractWxCpTpConfiguration.java new file mode 100644 index 0000000000..2404dee068 --- /dev/null +++ b/spring-boot-starters/wx-java-cp-tp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/configuration/services/AbstractWxCpTpConfiguration.java @@ -0,0 +1,139 @@ +package com.binarywang.spring.starter.wxjava.cp.configuration.services; + +import com.binarywang.spring.starter.wxjava.cp.properties.WxCpTpMultiProperties; +import com.binarywang.spring.starter.wxjava.cp.properties.WxCpTpSingleProperties; +import com.binarywang.spring.starter.wxjava.cp.service.WxCpTpMultiServices; +import com.binarywang.spring.starter.wxjava.cp.service.WxCpTpMultiServicesImpl; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import me.chanjar.weixin.cp.config.WxCpTpConfigStorage; +import me.chanjar.weixin.cp.config.impl.WxCpTpDefaultConfigImpl; +import me.chanjar.weixin.cp.tp.service.WxCpTpService; +import me.chanjar.weixin.cp.tp.service.impl.WxCpTpServiceApacheHttpClientImpl; +import me.chanjar.weixin.cp.tp.service.impl.WxCpTpServiceImpl; +import me.chanjar.weixin.cp.tp.service.impl.WxCpTpServiceJoddHttpImpl; +import me.chanjar.weixin.cp.tp.service.impl.WxCpTpServiceOkHttpImpl; +import org.apache.commons.lang3.StringUtils; + +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * WxCpConfigStorage 抽象配置类 + * + * @author yl + * created on 2023/10/16 + */ +@RequiredArgsConstructor +@Slf4j +public abstract class AbstractWxCpTpConfiguration { + + /** + * + * @param wxCpTpMultiProperties 应用列表配置 + * @param services 用于支持,应用启动之后,可以调用这个接口添加新服务对象。主要是配置是从数据库中读取的 + * @return + */ + public WxCpTpMultiServices wxCpMultiServices(WxCpTpMultiProperties wxCpTpMultiProperties,WxCpTpMultiServices services) { + Map corps = wxCpTpMultiProperties.getCorps(); + if (corps == null || corps.isEmpty()) { + log.warn("企业微信应用参数未配置,通过 WxCpMultiServices#getWxCpTpService(\"tenantId\")获取实例将返回空"); + return new WxCpTpMultiServicesImpl(); + } + + if (services == null) { + services = new WxCpTpMultiServicesImpl(); + } + + Set> entries = corps.entrySet(); + for (Map.Entry entry : entries) { + String tenantId = entry.getKey(); + WxCpTpSingleProperties wxCpTpSingleProperties = entry.getValue(); + WxCpTpDefaultConfigImpl storage = this.wxCpTpConfigStorage(wxCpTpMultiProperties); + this.configCorp(storage, wxCpTpSingleProperties); + this.configHttp(storage, wxCpTpMultiProperties.getConfigStorage()); + WxCpTpService wxCpTpService = this.wxCpTpService(storage, wxCpTpMultiProperties.getConfigStorage()); + if (services.getWxCpTpService(tenantId) == null) { + // 不存在的才会添加到服务列表中 + services.addWxCpTpService(tenantId, wxCpTpService); + } + } + return services; + } + + /** + * 配置 WxCpDefaultConfigImpl + * + * @param wxCpTpMultiProperties 参数 + * @return WxCpDefaultConfigImpl + */ + protected abstract WxCpTpDefaultConfigImpl wxCpTpConfigStorage(WxCpTpMultiProperties wxCpTpMultiProperties); + + private WxCpTpService wxCpTpService(WxCpTpConfigStorage wxCpTpConfigStorage, WxCpTpMultiProperties.ConfigStorage storage) { + WxCpTpMultiProperties.HttpClientType httpClientType = storage.getHttpClientType(); + WxCpTpService cpTpService; + switch (httpClientType) { + case OK_HTTP: + cpTpService = new WxCpTpServiceOkHttpImpl(); + break; + case JODD_HTTP: + cpTpService = new WxCpTpServiceJoddHttpImpl(); + break; + case HTTP_CLIENT: + cpTpService = new WxCpTpServiceApacheHttpClientImpl(); + break; + default: + cpTpService = new WxCpTpServiceImpl(); + break; + } + cpTpService.setWxCpTpConfigStorage(wxCpTpConfigStorage); + int maxRetryTimes = storage.getMaxRetryTimes(); + if (maxRetryTimes < 0) { + maxRetryTimes = 0; + } + int retrySleepMillis = storage.getRetrySleepMillis(); + if (retrySleepMillis < 0) { + retrySleepMillis = 1000; + } + cpTpService.setRetrySleepMillis(retrySleepMillis); + cpTpService.setMaxRetryTimes(maxRetryTimes); + return cpTpService; + } + + private void configCorp(WxCpTpDefaultConfigImpl config, WxCpTpSingleProperties wxCpTpSingleProperties) { + String corpId = wxCpTpSingleProperties.getCorpId(); + String providerSecret = wxCpTpSingleProperties.getProviderSecret(); + String suiteId = wxCpTpSingleProperties.getSuiteId(); + String token = wxCpTpSingleProperties.getToken(); + String suiteSecret = wxCpTpSingleProperties.getSuiteSecret(); + // 企业微信,私钥,会话存档路径 + config.setCorpId(corpId); + config.setProviderSecret(providerSecret); + config.setEncodingAESKey(wxCpTpSingleProperties.getEncodingAESKey()); + config.setSuiteId(suiteId); + config.setToken(token); + config.setSuiteSecret(suiteSecret); + } + + private void configHttp(WxCpTpDefaultConfigImpl config, WxCpTpMultiProperties.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); + } + } + } +} diff --git a/spring-boot-starters/wx-java-cp-tp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/configuration/services/WxCpTpInJedisTpConfiguration.java b/spring-boot-starters/wx-java-cp-tp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/configuration/services/WxCpTpInJedisTpConfiguration.java new file mode 100644 index 0000000000..f3034ac007 --- /dev/null +++ b/spring-boot-starters/wx-java-cp-tp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/configuration/services/WxCpTpInJedisTpConfiguration.java @@ -0,0 +1,78 @@ +package com.binarywang.spring.starter.wxjava.cp.configuration.services; + +import com.binarywang.spring.starter.wxjava.cp.properties.WxCpTpMultiProperties; +import com.binarywang.spring.starter.wxjava.cp.properties.WxCpTpMultiRedisProperties; +import com.binarywang.spring.starter.wxjava.cp.service.WxCpTpMultiServices; +import lombok.RequiredArgsConstructor; +import me.chanjar.weixin.cp.config.impl.WxCpDefaultConfigImpl; +import me.chanjar.weixin.cp.config.impl.WxCpJedisConfigImpl; +import me.chanjar.weixin.cp.config.impl.WxCpTpDefaultConfigImpl; +import me.chanjar.weixin.cp.config.impl.WxCpTpJedisConfigImpl; +import org.apache.commons.lang3.StringUtils; +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 yl + * created on 2023/10/16 + */ +@Configuration +@ConditionalOnProperty( + prefix = WxCpTpMultiProperties.PREFIX + ".config-storage", name = "type", havingValue = "jedis" +) +@RequiredArgsConstructor +public class WxCpTpInJedisTpConfiguration extends AbstractWxCpTpConfiguration { + private final WxCpTpMultiProperties wxCpTpMultiProperties; + private final ApplicationContext applicationContext; + + @Bean + public WxCpTpMultiServices wxCpMultiServices() { + return this.wxCpMultiServices(wxCpTpMultiProperties,null); + } + + @Override + protected WxCpTpDefaultConfigImpl wxCpTpConfigStorage(WxCpTpMultiProperties wxCpTpMultiProperties) { + return this.configRedis(wxCpTpMultiProperties); + } + + private WxCpTpDefaultConfigImpl configRedis(WxCpTpMultiProperties wxCpTpMultiProperties) { + WxCpTpMultiRedisProperties wxCpTpMultiRedisProperties = wxCpTpMultiProperties.getConfigStorage().getRedis(); + JedisPool jedisPool; + if (wxCpTpMultiRedisProperties != null && StringUtils.isNotEmpty(wxCpTpMultiRedisProperties.getHost())) { + jedisPool = getJedisPool(wxCpTpMultiProperties); + } else { + jedisPool = applicationContext.getBean(JedisPool.class); + } + return new WxCpTpJedisConfigImpl(jedisPool, wxCpTpMultiProperties.getConfigStorage().getKeyPrefix()); + } + + private JedisPool getJedisPool(WxCpTpMultiProperties wxCpTpMultiProperties) { + WxCpTpMultiProperties.ConfigStorage storage = wxCpTpMultiProperties.getConfigStorage(); + WxCpTpMultiRedisProperties 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-cp-tp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/configuration/services/WxCpTpInMemoryTpConfiguration.java b/spring-boot-starters/wx-java-cp-tp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/configuration/services/WxCpTpInMemoryTpConfiguration.java new file mode 100644 index 0000000000..5e460abb26 --- /dev/null +++ b/spring-boot-starters/wx-java-cp-tp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/configuration/services/WxCpTpInMemoryTpConfiguration.java @@ -0,0 +1,39 @@ +package com.binarywang.spring.starter.wxjava.cp.configuration.services; + +import com.binarywang.spring.starter.wxjava.cp.properties.WxCpTpMultiProperties; +import com.binarywang.spring.starter.wxjava.cp.service.WxCpTpMultiServices; +import lombok.RequiredArgsConstructor; +import me.chanjar.weixin.cp.config.impl.WxCpDefaultConfigImpl; +import me.chanjar.weixin.cp.config.impl.WxCpTpDefaultConfigImpl; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * 自动装配基于内存策略配置 + * + * @author yl + * created on 2023/10/16 + */ +@Configuration +@ConditionalOnProperty( + prefix = WxCpTpMultiProperties.PREFIX + ".config-storage", name = "type", havingValue = "memory", matchIfMissing = true +) +@RequiredArgsConstructor +public class WxCpTpInMemoryTpConfiguration extends AbstractWxCpTpConfiguration { + private final WxCpTpMultiProperties wxCpTpMultiProperties; + + @Bean + public WxCpTpMultiServices wxCpMultiServices() { + return this.wxCpMultiServices(wxCpTpMultiProperties,null); + } + + @Override + protected WxCpTpDefaultConfigImpl wxCpTpConfigStorage(WxCpTpMultiProperties wxCpTpMultiProperties) { + return this.configInMemory(); + } + + private WxCpTpDefaultConfigImpl configInMemory() { + return new WxCpTpDefaultConfigImpl(); + } +} diff --git a/spring-boot-starters/wx-java-cp-tp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/configuration/services/WxCpTpInRedisTemplateTpConfiguration.java b/spring-boot-starters/wx-java-cp-tp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/configuration/services/WxCpTpInRedisTemplateTpConfiguration.java new file mode 100644 index 0000000000..1faa37862c --- /dev/null +++ b/spring-boot-starters/wx-java-cp-tp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/configuration/services/WxCpTpInRedisTemplateTpConfiguration.java @@ -0,0 +1,45 @@ +package com.binarywang.spring.starter.wxjava.cp.configuration.services; + +import com.binarywang.spring.starter.wxjava.cp.properties.WxCpTpMultiProperties; +import com.binarywang.spring.starter.wxjava.cp.service.WxCpTpMultiServices; +import lombok.RequiredArgsConstructor; +import me.chanjar.weixin.cp.config.impl.WxCpDefaultConfigImpl; +import me.chanjar.weixin.cp.config.impl.WxCpRedisTemplateConfigImpl; +import me.chanjar.weixin.cp.config.impl.WxCpTpDefaultConfigImpl; +import me.chanjar.weixin.cp.config.impl.WxCpTpRedisTemplateConfigImpl; +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; + +/** + * 自动装配基于 redisTemplate 策略配置 + * + * @author yl + * created on 2023/10/16 + */ +@Configuration +@ConditionalOnProperty( + prefix = WxCpTpMultiProperties.PREFIX + ".config-storage", name = "type", havingValue = "redistemplate" +) +@RequiredArgsConstructor +public class WxCpTpInRedisTemplateTpConfiguration extends AbstractWxCpTpConfiguration { + private final WxCpTpMultiProperties wxCpTpMultiProperties; + private final ApplicationContext applicationContext; + + @Bean + public WxCpTpMultiServices wxCpMultiServices() { + return this.wxCpMultiServices(wxCpTpMultiProperties,null); + } + + @Override + protected WxCpTpDefaultConfigImpl wxCpTpConfigStorage(WxCpTpMultiProperties wxCpTpMultiProperties) { + return this.configRedisTemplate(wxCpTpMultiProperties); + } + + private WxCpTpDefaultConfigImpl configRedisTemplate(WxCpTpMultiProperties wxCpTpMultiProperties) { + StringRedisTemplate redisTemplate = applicationContext.getBean(StringRedisTemplate.class); + return new WxCpTpRedisTemplateConfigImpl(redisTemplate, wxCpTpMultiProperties.getConfigStorage().getKeyPrefix()); + } +} diff --git a/spring-boot-starters/wx-java-cp-tp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/configuration/services/WxCpTpInRedissonTpConfiguration.java b/spring-boot-starters/wx-java-cp-tp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/configuration/services/WxCpTpInRedissonTpConfiguration.java new file mode 100644 index 0000000000..bd16db37ea --- /dev/null +++ b/spring-boot-starters/wx-java-cp-tp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/configuration/services/WxCpTpInRedissonTpConfiguration.java @@ -0,0 +1,68 @@ +package com.binarywang.spring.starter.wxjava.cp.configuration.services; + +import com.binarywang.spring.starter.wxjava.cp.properties.WxCpTpMultiProperties; +import com.binarywang.spring.starter.wxjava.cp.properties.WxCpTpMultiRedisProperties; +import com.binarywang.spring.starter.wxjava.cp.service.WxCpTpMultiServices; +import lombok.RequiredArgsConstructor; +import me.chanjar.weixin.cp.config.impl.WxCpTpDefaultConfigImpl; +import me.chanjar.weixin.cp.config.impl.AbstractWxCpTpInRedisConfigImpl; +import me.chanjar.weixin.cp.config.impl.WxCpTpRedissonConfigImpl; +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.ConditionalOnProperty; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * 自动装配基于 redisson 策略配置 + * + * @author yl + * created on 2023/10/16 + */ +@Configuration +@ConditionalOnProperty( + prefix = WxCpTpMultiProperties.PREFIX + ".config-storage", name = "type", havingValue = "redisson" +) +@RequiredArgsConstructor +public class WxCpTpInRedissonTpConfiguration extends AbstractWxCpTpConfiguration { + private final WxCpTpMultiProperties wxCpTpMultiProperties; + private final ApplicationContext applicationContext; + + @Bean + public WxCpTpMultiServices wxCpMultiServices() { + return this.wxCpMultiServices(wxCpTpMultiProperties,null); + } + + @Override + protected WxCpTpDefaultConfigImpl wxCpTpConfigStorage(WxCpTpMultiProperties wxCpTpMultiProperties) { + return this.configRedisson(wxCpTpMultiProperties); + } + + private WxCpTpDefaultConfigImpl configRedisson(WxCpTpMultiProperties wxCpTpMultiProperties) { + WxCpTpMultiRedisProperties redisProperties = wxCpTpMultiProperties.getConfigStorage().getRedis(); + RedissonClient redissonClient; + if (redisProperties != null && StringUtils.isNotEmpty(redisProperties.getHost())) { + redissonClient = getRedissonClient(wxCpTpMultiProperties); + } else { + redissonClient = applicationContext.getBean(RedissonClient.class); + } + return new WxCpTpRedissonConfigImpl(redissonClient, wxCpTpMultiProperties.getConfigStorage().getKeyPrefix()); + } + + private RedissonClient getRedissonClient(WxCpTpMultiProperties wxCpTpMultiProperties) { + WxCpTpMultiProperties.ConfigStorage storage = wxCpTpMultiProperties.getConfigStorage(); + WxCpTpMultiRedisProperties 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-cp-tp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/properties/WxCpTpMultiProperties.java b/spring-boot-starters/wx-java-cp-tp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/properties/WxCpTpMultiProperties.java new file mode 100644 index 0000000000..771b1b6de7 --- /dev/null +++ b/spring-boot-starters/wx-java-cp-tp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/properties/WxCpTpMultiProperties.java @@ -0,0 +1,129 @@ +package com.binarywang.spring.starter.wxjava.cp.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 yl + * created on 2023/10/16 + */ +@Data +@NoArgsConstructor +@ConfigurationProperties(prefix = WxCpTpMultiProperties.PREFIX) +public class WxCpTpMultiProperties implements Serializable { + private static final long serialVersionUID = -1569510477055668503L; + public static final String PREFIX = "wx.cp.tp"; + + private Map corps = new HashMap<>(); + + /** + * 配置存储策略,默认内存 + */ + private 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:cp:tp"; + + /** + * redis连接配置 + */ + @NestedConfigurationProperty + private WxCpTpMultiRedisProperties redis = new WxCpTpMultiRedisProperties(); + + /** + * http客户端类型. + */ + private HttpClientType httpClientType = HttpClientType.HTTP_CLIENT; + + /** + * http代理主机 + */ + private String httpProxyHost; + + /** + * http代理端口 + */ + private Integer httpProxyPort; + + /** + * http代理用户名 + */ + private String httpProxyUsername; + + /** + * http代理密码 + */ + private String httpProxyPassword; + + /** + * http 请求最大重试次数 + *
+     *   {@link me.chanjar.weixin.cp.api.WxCpService#setMaxRetryTimes(int)}
+     *   {@link me.chanjar.weixin.cp.api.impl.BaseWxCpServiceImpl#setMaxRetryTimes(int)}
+     * 
+ */ + private int maxRetryTimes = 5; + + /** + * http 请求重试间隔 + *
+     *   {@link me.chanjar.weixin.cp.api.WxCpService#setRetrySleepMillis(int)}
+     *   {@link me.chanjar.weixin.cp.api.impl.BaseWxCpServiceImpl#setRetrySleepMillis(int)}
+     * 
+ */ + private int retrySleepMillis = 1000; + } + + public enum StorageType { + /** + * 内存 + */ + memory, + /** + * jedis + */ + jedis, + /** + * redisson + */ + redisson, + /** + * redistemplate + */ + redistemplate + } + + public enum HttpClientType { + /** + * HttpClient + */ + HTTP_CLIENT, + /** + * OkHttp + */ + OK_HTTP, + /** + * JoddHttp + */ + JODD_HTTP + } +} diff --git a/spring-boot-starters/wx-java-cp-tp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/properties/WxCpTpMultiRedisProperties.java b/spring-boot-starters/wx-java-cp-tp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/properties/WxCpTpMultiRedisProperties.java new file mode 100644 index 0000000000..b94711216f --- /dev/null +++ b/spring-boot-starters/wx-java-cp-tp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/properties/WxCpTpMultiRedisProperties.java @@ -0,0 +1,48 @@ +package com.binarywang.spring.starter.wxjava.cp.properties; + +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * Redis配置. + * + * @author yl + * created on 2023/10/16 + */ +@Data +@NoArgsConstructor +public class WxCpTpMultiRedisProperties implements Serializable { + private static final long serialVersionUID = -5924815351660074401L; + + /** + * 主机地址. + */ + private String host; + + /** + * 端口号. + */ + private int port = 6379; + + /** + * 密码. + */ + private String password; + + /** + * 超时. + */ + private int timeout = 2000; + + /** + * 数据库. + */ + private int database = 0; + + private Integer maxActive; + private Integer maxIdle; + private Integer maxWaitMillis; + private Integer minIdle; +} diff --git a/spring-boot-starters/wx-java-cp-tp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/properties/WxCpTpSingleProperties.java b/spring-boot-starters/wx-java-cp-tp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/properties/WxCpTpSingleProperties.java new file mode 100644 index 0000000000..02a52657db --- /dev/null +++ b/spring-boot-starters/wx-java-cp-tp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/properties/WxCpTpSingleProperties.java @@ -0,0 +1,43 @@ +package com.binarywang.spring.starter.wxjava.cp.properties; + +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * 企业微信企业相关配置属性 + * + * @author yl + * created on 2023/10/16 + */ +@Data +@NoArgsConstructor +public class WxCpTpSingleProperties implements Serializable { + private static final long serialVersionUID = -7502823825007859418L; + /** + * 微信企业号 corpId + */ + private String corpId; + /** + * 微信企业号 服务商 providerSecret + */ + private String providerSecret; + /** + * 微信企业号应用 token + */ + private String token; + + private String encodingAESKey; + + /** + * 微信企业号 第三方 应用 ID + */ + private String suiteId; + /** + * 微信企业号应用 + */ + private String suiteSecret; + + +} diff --git a/spring-boot-starters/wx-java-cp-tp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/service/WxCpTpMultiServices.java b/spring-boot-starters/wx-java-cp-tp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/service/WxCpTpMultiServices.java new file mode 100644 index 0000000000..c0a9faf51e --- /dev/null +++ b/spring-boot-starters/wx-java-cp-tp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/service/WxCpTpMultiServices.java @@ -0,0 +1,29 @@ +package com.binarywang.spring.starter.wxjava.cp.service; + + +import me.chanjar.weixin.cp.tp.service.WxCpTpService; + +/** + * 企业微信 {@link WxCpTpService} 所有实例存放类. + * + * @author yl + * created on 2023/10/16 + */ +public interface WxCpTpMultiServices { + /** + * 通过租户 Id 获取 WxCpTpService + * + * @param tenantId 租户 Id + * @return WxCpTpService + */ + WxCpTpService getWxCpTpService(String tenantId); + + void addWxCpTpService(String tenantId, WxCpTpService wxCpService); + + /** + * 根据租户 Id,从列表中移除一个 WxCpTpService 实例 + * + * @param tenantId 租户 Id + */ + void removeWxCpTpService(String tenantId); +} diff --git a/spring-boot-starters/wx-java-cp-tp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/service/WxCpTpMultiServicesImpl.java b/spring-boot-starters/wx-java-cp-tp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/service/WxCpTpMultiServicesImpl.java new file mode 100644 index 0000000000..84b381230c --- /dev/null +++ b/spring-boot-starters/wx-java-cp-tp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/service/WxCpTpMultiServicesImpl.java @@ -0,0 +1,44 @@ +package com.binarywang.spring.starter.wxjava.cp.service; + + +import me.chanjar.weixin.cp.tp.service.WxCpTpService; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * 企业微信 {@link WxCpTpMultiServices} 默认实现 + * + * @author yl + * created on 2023/10/16 + */ +public class WxCpTpMultiServicesImpl implements WxCpTpMultiServices { + private final Map services = new ConcurrentHashMap<>(); + + /** + * 通过租户 Id 获取 WxCpTpService + * + * @param tenantId 租户 Id + * @return WxCpTpService + */ + @Override + public WxCpTpService getWxCpTpService(String tenantId) { + return this.services.get(tenantId); + } + + /** + * 根据租户 Id,添加一个 WxCpTpService 到列表 + * + * @param tenantId 租户 Id + * @param wxCpService WxCpTpService 实例 + */ + @Override + public void addWxCpTpService(String tenantId, WxCpTpService wxCpService) { + this.services.put(tenantId, wxCpService); + } + + @Override + public void removeWxCpTpService(String tenantId) { + this.services.remove(tenantId); + } +} diff --git a/spring-boot-starters/wx-java-cp-tp-multi-spring-boot-starter/src/main/resources/META-INF/spring.factories b/spring-boot-starters/wx-java-cp-tp-multi-spring-boot-starter/src/main/resources/META-INF/spring.factories new file mode 100644 index 0000000000..9d11107229 --- /dev/null +++ b/spring-boot-starters/wx-java-cp-tp-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.cp.autoconfigure.WxCpTpMultiAutoConfiguration diff --git a/spring-boot-starters/wx-java-cp-tp-multi-spring-boot-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/spring-boot-starters/wx-java-cp-tp-multi-spring-boot-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 0000000000..5de0e9f139 --- /dev/null +++ b/spring-boot-starters/wx-java-cp-tp-multi-spring-boot-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1 @@ +com.binarywang.spring.starter.wxjava.cp.autoconfigure.WxCpTpMultiAutoConfiguration diff --git a/spring-boot-starters/wx-java-miniapp-multi-spring-boot-starter/pom.xml b/spring-boot-starters/wx-java-miniapp-multi-spring-boot-starter/pom.xml index cedde81744..3142e70f90 100644 --- a/spring-boot-starters/wx-java-miniapp-multi-spring-boot-starter/pom.xml +++ b/spring-boot-starters/wx-java-miniapp-multi-spring-boot-starter/pom.xml @@ -5,7 +5,7 @@ wx-java-spring-boot-starters com.github.binarywang - 4.7.2.B + 4.7.8.B 4.0.0 diff --git a/spring-boot-starters/wx-java-miniapp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/miniapp/configuration/WxMaMultiServiceConfiguration.java b/spring-boot-starters/wx-java-miniapp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/miniapp/configuration/WxMaMultiServiceConfiguration.java index 69fb3b9a0e..e1db56cfc7 100644 --- a/spring-boot-starters/wx-java-miniapp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/miniapp/configuration/WxMaMultiServiceConfiguration.java +++ b/spring-boot-starters/wx-java-miniapp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/miniapp/configuration/WxMaMultiServiceConfiguration.java @@ -2,6 +2,7 @@ import com.binarywang.spring.starter.wxjava.miniapp.configuration.services.WxMaInJedisConfiguration; import com.binarywang.spring.starter.wxjava.miniapp.configuration.services.WxMaInMemoryConfiguration; +import com.binarywang.spring.starter.wxjava.miniapp.configuration.services.WxMaInRedisTemplateConfiguration; import com.binarywang.spring.starter.wxjava.miniapp.configuration.services.WxMaInRedissonConfiguration; import com.binarywang.spring.starter.wxjava.miniapp.properties.WxMaMultiProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties; @@ -17,9 +18,10 @@ @Configuration @EnableConfigurationProperties(WxMaMultiProperties.class) @Import({ - WxMaInJedisConfiguration.class, - WxMaInMemoryConfiguration.class, - WxMaInRedissonConfiguration.class, + WxMaInJedisConfiguration.class, + WxMaInMemoryConfiguration.class, + WxMaInRedissonConfiguration.class, + WxMaInRedisTemplateConfiguration.class }) public class WxMaMultiServiceConfiguration { } diff --git a/spring-boot-starters/wx-java-miniapp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/miniapp/configuration/services/AbstractWxMaConfiguration.java b/spring-boot-starters/wx-java-miniapp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/miniapp/configuration/services/AbstractWxMaConfiguration.java index 27ff84763b..15e638f89e 100644 --- a/spring-boot-starters/wx-java-miniapp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/miniapp/configuration/services/AbstractWxMaConfiguration.java +++ b/spring-boot-starters/wx-java-miniapp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/miniapp/configuration/services/AbstractWxMaConfiguration.java @@ -108,12 +108,12 @@ public WxMaService wxMaService(WxMaConfig wxMaConfig, WxMaMultiProperties wxMaMu return wxMaService; } - private void configApp(WxMaDefaultConfigImpl config, WxMaSingleProperties corpProperties) { - String appId = corpProperties.getAppId(); - String appSecret = corpProperties.getAppSecret(); - String token = corpProperties.getToken(); - String aesKey = corpProperties.getAesKey(); - boolean useStableAccessToken = corpProperties.isUseStableAccessToken(); + private void configApp(WxMaDefaultConfigImpl config, WxMaSingleProperties properties) { + String appId = properties.getAppId(); + String appSecret = properties.getAppSecret(); + String token = properties.getToken(); + String aesKey = properties.getAesKey(); + boolean useStableAccessToken = properties.isUseStableAccessToken(); config.setAppid(appId); config.setSecret(appSecret); @@ -123,7 +123,10 @@ private void configApp(WxMaDefaultConfigImpl config, WxMaSingleProperties corpPr if (StringUtils.isNotBlank(aesKey)) { config.setAesKey(aesKey); } + config.setMsgDataFormat(properties.getMsgDataFormat()); config.useStableAccessToken(useStableAccessToken); + config.setApiHostUrl(StringUtils.trimToNull(properties.getApiHostUrl())); + config.setAccessTokenUrl(StringUtils.trimToNull(properties.getAccessTokenUrl())); } private void configHttp(WxMaDefaultConfigImpl config, WxMaMultiProperties.ConfigStorage storage) { diff --git a/spring-boot-starters/wx-java-miniapp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/miniapp/configuration/services/WxMaInRedisTemplateConfiguration.java b/spring-boot-starters/wx-java-miniapp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/miniapp/configuration/services/WxMaInRedisTemplateConfiguration.java new file mode 100644 index 0000000000..fc88a0578a --- /dev/null +++ b/spring-boot-starters/wx-java-miniapp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/miniapp/configuration/services/WxMaInRedisTemplateConfiguration.java @@ -0,0 +1,43 @@ +package com.binarywang.spring.starter.wxjava.miniapp.configuration.services; + +import cn.binarywang.wx.miniapp.config.impl.WxMaDefaultConfigImpl; +import cn.binarywang.wx.miniapp.config.impl.WxMaRedisBetterConfigImpl; +import com.binarywang.spring.starter.wxjava.miniapp.properties.WxMaMultiProperties; +import com.binarywang.spring.starter.wxjava.miniapp.service.WxMaMultiServices; +import lombok.RequiredArgsConstructor; +import me.chanjar.weixin.common.redis.RedisTemplateWxRedisOps; +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; + +/** + * 自动装配基于 redisTemplate 策略配置 + * + * @author hb0730 2025/9/10 + */ +@Configuration +@ConditionalOnProperty(prefix = WxMaMultiProperties.PREFIX + ".config-storage", name = "type", havingValue = "redis_template") +@RequiredArgsConstructor +public class WxMaInRedisTemplateConfiguration extends AbstractWxMaConfiguration { + private final WxMaMultiProperties wxMaMultiProperties; + private final ApplicationContext applicationContext; + + @Bean + public WxMaMultiServices wxMaMultiServices() { + return this.wxMaMultiServices(wxMaMultiProperties); + } + + @Override + protected WxMaDefaultConfigImpl wxMaConfigStorage(WxMaMultiProperties wxMaMultiProperties) { + return this.configRedisTemplate(wxMaMultiProperties); + } + + private WxMaDefaultConfigImpl configRedisTemplate(WxMaMultiProperties wxMaMultiProperties) { + StringRedisTemplate redisTemplate = applicationContext.getBean(StringRedisTemplate.class); + RedisTemplateWxRedisOps wxRedisOps = new RedisTemplateWxRedisOps(redisTemplate); + return new WxMaRedisBetterConfigImpl(wxRedisOps, wxMaMultiProperties.getConfigStorage().getKeyPrefix()); + } + +} diff --git a/spring-boot-starters/wx-java-miniapp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/miniapp/properties/WxMaSingleProperties.java b/spring-boot-starters/wx-java-miniapp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/miniapp/properties/WxMaSingleProperties.java index 2842a2d970..5defae5514 100644 --- a/spring-boot-starters/wx-java-miniapp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/miniapp/properties/WxMaSingleProperties.java +++ b/spring-boot-starters/wx-java-miniapp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/miniapp/properties/WxMaSingleProperties.java @@ -33,8 +33,25 @@ public class WxMaSingleProperties implements Serializable { */ private String aesKey; + /** + * 消息格式,XML或者JSON. + */ + private String msgDataFormat; + /** * 是否使用稳定版 Access Token */ private boolean useStableAccessToken = false; + + /** + * 自定义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-miniapp-spring-boot-starter/pom.xml b/spring-boot-starters/wx-java-miniapp-spring-boot-starter/pom.xml index fba6da9e0a..b5eb39a7e5 100644 --- a/spring-boot-starters/wx-java-miniapp-spring-boot-starter/pom.xml +++ b/spring-boot-starters/wx-java-miniapp-spring-boot-starter/pom.xml @@ -4,7 +4,7 @@ wx-java-spring-boot-starters com.github.binarywang - 4.7.2.B + 4.7.8.B 4.0.0 diff --git a/spring-boot-starters/wx-java-miniapp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/miniapp/config/storage/AbstractWxMaConfigStorageConfiguration.java b/spring-boot-starters/wx-java-miniapp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/miniapp/config/storage/AbstractWxMaConfigStorageConfiguration.java index fef0824767..abcd83e848 100644 --- a/spring-boot-starters/wx-java-miniapp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/miniapp/config/storage/AbstractWxMaConfigStorageConfiguration.java +++ b/spring-boot-starters/wx-java-miniapp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/miniapp/config/storage/AbstractWxMaConfigStorageConfiguration.java @@ -2,6 +2,8 @@ import cn.binarywang.wx.miniapp.config.impl.WxMaDefaultConfigImpl; import com.binarywang.spring.starter.wxjava.miniapp.properties.WxMaProperties; +import me.chanjar.weixin.common.util.http.apache.ApacheHttpClientBuilder; +import me.chanjar.weixin.common.util.http.apache.DefaultApacheHttpClientBuilder; import org.apache.commons.lang3.StringUtils; /** @@ -10,12 +12,15 @@ public abstract class AbstractWxMaConfigStorageConfiguration { protected WxMaDefaultConfigImpl config(WxMaDefaultConfigImpl config, WxMaProperties properties) { + WxMaProperties.ConfigStorage storage = properties.getConfigStorage(); config.setAppid(StringUtils.trimToNull(properties.getAppid())); config.setSecret(StringUtils.trimToNull(properties.getSecret())); config.setToken(StringUtils.trimToNull(properties.getToken())); config.setAesKey(StringUtils.trimToNull(properties.getAesKey())); config.setMsgDataFormat(StringUtils.trimToNull(properties.getMsgDataFormat())); config.useStableAccessToken(properties.isUseStableAccessToken()); + config.setApiHostUrl(StringUtils.trimToNull(properties.getApiHostUrl())); + config.setAccessTokenUrl(StringUtils.trimToNull(properties.getAccessTokenUrl())); WxMaProperties.ConfigStorage configStorageProperties = properties.getConfigStorage(); config.setHttpProxyHost(configStorageProperties.getHttpProxyHost()); @@ -25,6 +30,19 @@ protected WxMaDefaultConfigImpl config(WxMaDefaultConfigImpl config, WxMaPropert 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(storage.getConnectionTimeout()); + defaultBuilder.setSoTimeout(storage.getSoTimeout()); + defaultBuilder.setConnectionRequestTimeout(storage.getConnectionRequestTimeout()); + config.setApacheHttpClientBuilder(defaultBuilder); + } + int maxRetryTimes = configStorageProperties.getMaxRetryTimes(); if (configStorageProperties.getMaxRetryTimes() < 0) { maxRetryTimes = 0; 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 b6384aabd2..7e88db904f 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 @@ -49,6 +49,18 @@ public class WxMaProperties { */ private boolean useStableAccessToken = false; + /** + * 自定义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; + /** * 存储策略 */ @@ -112,6 +124,21 @@ public static class ConfigStorage { * */ private int maxRetryTimes = 5; + + /** + * 连接超时时间,单位毫秒 + */ + private int connectionTimeout = 5000; + + /** + * 读数据超时时间,即socketTimeout,单位毫秒 + */ + private int soTimeout = 5000; + + /** + * 从连接池获取链接的超时时间,单位毫秒 + */ + private int connectionRequestTimeout = 5000; } } diff --git a/spring-boot-starters/wx-java-mp-multi-spring-boot-starter/pom.xml b/spring-boot-starters/wx-java-mp-multi-spring-boot-starter/pom.xml index 867f570558..70cb5df930 100644 --- a/spring-boot-starters/wx-java-mp-multi-spring-boot-starter/pom.xml +++ b/spring-boot-starters/wx-java-mp-multi-spring-boot-starter/pom.xml @@ -5,7 +5,7 @@ wx-java-spring-boot-starters com.github.binarywang - 4.7.2.B + 4.7.8.B 4.0.0 diff --git a/spring-boot-starters/wx-java-mp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/configuration/services/AbstractWxMpConfiguration.java b/spring-boot-starters/wx-java-mp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/configuration/services/AbstractWxMpConfiguration.java index 4e55fb4580..1f431b645d 100644 --- a/spring-boot-starters/wx-java-mp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/configuration/services/AbstractWxMpConfiguration.java +++ b/spring-boot-starters/wx-java-mp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/configuration/services/AbstractWxMpConfiguration.java @@ -7,10 +7,7 @@ import lombok.RequiredArgsConstructor; 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.WxMpServiceImpl; -import me.chanjar.weixin.mp.api.impl.WxMpServiceJoddHttpImpl; -import me.chanjar.weixin.mp.api.impl.WxMpServiceOkHttpImpl; +import me.chanjar.weixin.mp.api.impl.*; import me.chanjar.weixin.mp.config.WxMpConfigStorage; import me.chanjar.weixin.mp.config.WxMpHostConfig; import me.chanjar.weixin.mp.config.impl.WxMpDefaultConfigImpl; @@ -91,6 +88,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/spring-boot-starters/wx-java-mp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/properties/WxMpMultiProperties.java b/spring-boot-starters/wx-java-mp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/properties/WxMpMultiProperties.java index c0d331382f..8b2fa58aa3 100644 --- a/spring-boot-starters/wx-java-mp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/properties/WxMpMultiProperties.java +++ b/spring-boot-starters/wx-java-mp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/properties/WxMpMultiProperties.java @@ -142,6 +142,10 @@ public enum HttpClientType { * HttpClient */ HTTP_CLIENT, + /** + * HttpComponents + */ + HTTP_COMPONENTS, /** * OkHttp */ 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 cddf39300b..e69261574b 100644 --- a/spring-boot-starters/wx-java-mp-spring-boot-starter/pom.xml +++ b/spring-boot-starters/wx-java-mp-spring-boot-starter/pom.xml @@ -5,7 +5,7 @@ wx-java-spring-boot-starters com.github.binarywang - 4.7.2.B + 4.7.8.B 4.0.0 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 deb527e69f..4c0938454a 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 @@ -9,6 +9,8 @@ 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; @@ -122,6 +124,19 @@ private void setWxMpInfo(WxMpDefaultConfigImpl config) { 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()); 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 a01fc0a521..377fb5b6ab 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 @@ -102,6 +102,21 @@ public static class ConfigStorage implements Serializable { */ private String httpProxyPassword; + /** + * 连接超时时间,单位毫秒 + */ + private int connectionTimeout = 5000; + + /** + * 读数据超时时间,即socketTimeout,单位毫秒 + */ + private int soTimeout = 5000; + + /** + * 从连接池获取链接的超时时间,单位毫秒 + */ + private int connectionRequestTimeout = 5000; + } } diff --git a/spring-boot-starters/wx-java-open-spring-boot-starter/pom.xml b/spring-boot-starters/wx-java-open-spring-boot-starter/pom.xml index 189ff94672..4529d80b64 100644 --- a/spring-boot-starters/wx-java-open-spring-boot-starter/pom.xml +++ b/spring-boot-starters/wx-java-open-spring-boot-starter/pom.xml @@ -5,7 +5,7 @@ wx-java-spring-boot-starters com.github.binarywang - 4.7.2.B + 4.7.8.B 4.0.0 diff --git a/spring-boot-starters/wx-java-open-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/open/config/WxOpenServiceAutoConfiguration.java b/spring-boot-starters/wx-java-open-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/open/config/WxOpenServiceAutoConfiguration.java index 22b0a6621d..e532f3c160 100644 --- a/spring-boot-starters/wx-java-open-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/open/config/WxOpenServiceAutoConfiguration.java +++ b/spring-boot-starters/wx-java-open-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/open/config/WxOpenServiceAutoConfiguration.java @@ -28,6 +28,7 @@ public WxOpenService wxOpenService(WxOpenConfigStorage wxOpenConfigStorage) { } @Bean + @ConditionalOnMissingBean public WxOpenMessageRouter wxOpenMessageRouter(WxOpenService wxOpenService) { return new WxOpenMessageRouter(wxOpenService); } diff --git a/spring-boot-starters/wx-java-open-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/open/config/storage/AbstractWxOpenConfigStorageConfiguration.java b/spring-boot-starters/wx-java-open-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/open/config/storage/AbstractWxOpenConfigStorageConfiguration.java index ee0443c9ae..91db545ab9 100644 --- a/spring-boot-starters/wx-java-open-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/open/config/storage/AbstractWxOpenConfigStorageConfiguration.java +++ b/spring-boot-starters/wx-java-open-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/open/config/storage/AbstractWxOpenConfigStorageConfiguration.java @@ -1,7 +1,10 @@ package com.binarywang.spring.starter.wxjava.open.config.storage; import com.binarywang.spring.starter.wxjava.open.properties.WxOpenProperties; +import me.chanjar.weixin.common.util.http.apache.ApacheHttpClientBuilder; +import me.chanjar.weixin.common.util.http.apache.DefaultApacheHttpClientBuilder; import me.chanjar.weixin.open.api.impl.WxOpenInMemoryConfigStorage; +import org.apache.commons.lang3.StringUtils; /** * @author yl @@ -28,6 +31,24 @@ protected WxOpenInMemoryConfigStorage config(WxOpenInMemoryConfigStorage config, } config.setRetrySleepMillis(retrySleepMillis); config.setMaxRetryTimes(maxRetryTimes); + + // 设置URL配置 + config.setApiHostUrl(StringUtils.trimToNull(properties.getApiHostUrl())); + config.setAccessTokenUrl(StringUtils.trimToNull(properties.getAccessTokenUrl())); + + // 设置自定义的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); + } + return config; } } diff --git a/spring-boot-starters/wx-java-open-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/open/properties/WxOpenProperties.java b/spring-boot-starters/wx-java-open-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/open/properties/WxOpenProperties.java index 641c57b005..248c6eedf6 100644 --- a/spring-boot-starters/wx-java-open-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/open/properties/WxOpenProperties.java +++ b/spring-boot-starters/wx-java-open-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/open/properties/WxOpenProperties.java @@ -40,6 +40,18 @@ public class WxOpenProperties { */ 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; + /** * 存储策略. */ @@ -108,6 +120,21 @@ public static class ConfigStorage implements Serializable { */ private int maxRetryTimes = 5; + /** + * 连接超时时间,单位毫秒 + */ + private int connectionTimeout = 5000; + + /** + * 读数据超时时间,即socketTimeout,单位毫秒 + */ + private int soTimeout = 5000; + + /** + * 从连接池获取链接的超时时间,单位毫秒 + */ + private int connectionRequestTimeout = 5000; + } public enum StorageType { diff --git a/spring-boot-starters/wx-java-pay-spring-boot-starter/pom.xml b/spring-boot-starters/wx-java-pay-spring-boot-starter/pom.xml index d4de4e77f8..82125a8e68 100644 --- a/spring-boot-starters/wx-java-pay-spring-boot-starter/pom.xml +++ b/spring-boot-starters/wx-java-pay-spring-boot-starter/pom.xml @@ -5,7 +5,7 @@ wx-java-spring-boot-starters com.github.binarywang - 4.7.2.B + 4.7.8.B 4.0.0 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 e401a8cfba..451cfbe4d0 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 @@ -50,15 +50,18 @@ public WxPayService wxPayService() { payConfig.setSubMchId(StringUtils.trimToNull(this.properties.getSubMchId())); payConfig.setKeyPath(StringUtils.trimToNull(this.properties.getKeyPath())); payConfig.setUseSandboxEnv(this.properties.isUseSandboxEnv()); + payConfig.setNotifyUrl(StringUtils.trimToNull(this.properties.getNotifyUrl())); //以下是apiv3以及支付分相关 payConfig.setServiceId(StringUtils.trimToNull(this.properties.getServiceId())); payConfig.setPayScoreNotifyUrl(StringUtils.trimToNull(this.properties.getPayScoreNotifyUrl())); + payConfig.setPayScorePermissionNotifyUrl(StringUtils.trimToNull(this.properties.getPayScorePermissionNotifyUrl())); payConfig.setPrivateKeyPath(StringUtils.trimToNull(this.properties.getPrivateKeyPath())); payConfig.setPrivateCertPath(StringUtils.trimToNull(this.properties.getPrivateCertPath())); payConfig.setCertSerialNo(StringUtils.trimToNull(this.properties.getCertSerialNo())); payConfig.setApiV3Key(StringUtils.trimToNull(this.properties.getApiv3Key())); payConfig.setPublicKeyId(StringUtils.trimToNull(this.properties.getPublicKeyId())); payConfig.setPublicKeyPath(StringUtils.trimToNull(this.properties.getPublicKeyPath())); + payConfig.setApiHostUrl(StringUtils.trimToNull(this.properties.getApiHostUrl())); wxPayService.setConfig(payConfig); return wxPayService; 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 a1a8cc2297..143b7deefa 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 @@ -59,11 +59,21 @@ public class WxPayProperties { */ private String apiv3Key; + /** + * 微信支付异步回调地址,通知url必须为直接可访问的url,不能携带参数 + */ + private String notifyUrl; + /** * 微信支付分回调地址 */ private String payScoreNotifyUrl; + /** + * 微信支付分授权回调地址 + */ + private String payScorePermissionNotifyUrl; + /** * apiv3 商户apiclient_key.pem */ @@ -90,4 +100,10 @@ public class WxPayProperties { */ private boolean useSandboxEnv; + /** + * 自定义API主机地址,用于替换默认的 https://api.mch.weixin.qq.com + * 例如:http://proxy.company.com:8080 + */ + private String apiHostUrl; + } diff --git a/spring-boot-starters/wx-java-qidian-spring-boot-starter/pom.xml b/spring-boot-starters/wx-java-qidian-spring-boot-starter/pom.xml index e118aba652..a35a311fd7 100644 --- a/spring-boot-starters/wx-java-qidian-spring-boot-starter/pom.xml +++ b/spring-boot-starters/wx-java-qidian-spring-boot-starter/pom.xml @@ -3,7 +3,7 @@ wx-java-spring-boot-starters com.github.binarywang - 4.7.2.B + 4.7.8.B 4.0.0 diff --git a/spring-boot-starters/wx-java-qidian-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/qidian/config/WxQidianStorageAutoConfiguration.java b/spring-boot-starters/wx-java-qidian-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/qidian/config/WxQidianStorageAutoConfiguration.java index 80809fefce..01ba91b565 100644 --- a/spring-boot-starters/wx-java-qidian-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/qidian/config/WxQidianStorageAutoConfiguration.java +++ b/spring-boot-starters/wx-java-qidian-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/qidian/config/WxQidianStorageAutoConfiguration.java @@ -20,6 +20,7 @@ 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; @@ -80,7 +81,7 @@ private WxQidianConfigStorage defaultConfigStorage() { } private WxQidianConfigStorage jedisConfigStorage() { - Pool jedisPool; + Pool jedisPool; if (StringUtils.isNotEmpty(redisHost) || StringUtils.isNotEmpty(redisHost2)) { jedisPool = getJedisPool(); } else { @@ -136,7 +137,7 @@ private void setWxMpInfo(WxQidianDefaultConfigImpl config) { } } - private Pool getJedisPool() { + private Pool getJedisPool() { WxQidianProperties.ConfigStorage storage = wxQidianProperties.getConfigStorage(); RedisProperties redis = storage.getRedis(); diff --git a/weixin-graal/pom.xml b/weixin-graal/pom.xml index 338a22a564..1255aaa93e 100644 --- a/weixin-graal/pom.xml +++ b/weixin-graal/pom.xml @@ -6,7 +6,7 @@ com.github.binarywang wx-java - 4.7.2.B + 4.7.8.B weixin-graal diff --git a/weixin-java-channel/pom.xml b/weixin-java-channel/pom.xml index 8d34754cb3..2b6893d2b8 100644 --- a/weixin-java-channel/pom.xml +++ b/weixin-java-channel/pom.xml @@ -6,7 +6,7 @@ com.github.binarywang wx-java - 4.7.2.B + 4.7.8.B weixin-java-channel @@ -14,7 +14,7 @@ 微信视频号/微信小店 Java SDK - 2.18.1 + 2.18.4 @@ -29,6 +29,11 @@ jodd-http provided
+ + org.apache.httpcomponents.client5 + httpclient5 + provided + com.fasterxml.jackson.core diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/BaseWxChannelService.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/BaseWxChannelService.java index f9f4cbed68..07278da7ef 100644 --- a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/BaseWxChannelService.java +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/BaseWxChannelService.java @@ -131,5 +131,5 @@ public interface BaseWxChannelService extends WxService { * * @return . request http */ - RequestHttp getRequestHttp(); + RequestHttp getRequestHttp(); } diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxChannelAfterSaleService.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxChannelAfterSaleService.java index 418feab7ac..dedbf5e4f2 100644 --- a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxChannelAfterSaleService.java +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxChannelAfterSaleService.java @@ -3,6 +3,7 @@ import java.util.List; import me.chanjar.weixin.channel.bean.after.AfterSaleInfoResponse; +import me.chanjar.weixin.channel.bean.after.AfterSaleListParam; import me.chanjar.weixin.channel.bean.after.AfterSaleListResponse; import me.chanjar.weixin.channel.bean.after.AfterSaleReasonResponse; import me.chanjar.weixin.channel.bean.after.AfterSaleRejectReasonResponse; @@ -26,10 +27,22 @@ public interface WxChannelAfterSaleService { * @return 售后单列表 * * @throws WxErrorException 异常 + * @deprecated 使用 {@link WxChannelAfterSaleService#listIds(AfterSaleListParam)} */ + @Deprecated AfterSaleListResponse listIds(Long beginCreateTime, Long endCreateTime, String nextKey) throws WxErrorException; + /** + * 获取售后单列表 + * + * @param param 参数 + * @return 售后单列表 + * + * @throws WxErrorException 异常 + */ + AfterSaleListResponse listIds(AfterSaleListParam param) throws WxErrorException; + /** * 获取售后单详情 * diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxChannelOrderService.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxChannelOrderService.java index d426a39805..7be0382bac 100644 --- a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxChannelOrderService.java +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxChannelOrderService.java @@ -34,6 +34,17 @@ public interface WxChannelOrderService { */ OrderInfoResponse getOrder(String orderId) throws WxErrorException; + /** + * 获取订单详情 + * + * @param orderId 订单id + * @param encodeSensitiveInfo 是否编码敏感信息 + * @return 订单详情 + * + * @throws WxErrorException 异常 + */ + OrderInfoResponse getOrder(String orderId, Boolean encodeSensitiveInfo) throws WxErrorException; + /** * 获取订单列表 * @@ -128,7 +139,7 @@ WxChannelBaseResponse updatePrice(String orderId, Integer expressFee, List implements WxChannelService private int maxRetryTimes = 5; @Override - public RequestHttp getRequestHttp() { + public RequestHttp getRequestHttp() { return this; } @@ -75,7 +75,7 @@ public boolean checkSignature(String timestamp, String nonce, String signature) try { return SHA1.gen(this.getConfig().getToken(), timestamp, nonce).equals(signature); } catch (Exception e) { - log.error("Checking signature failed, and the reason is :" + e.getMessage()); + log.error("Checking signature failed, and the reason is :{}", e.getMessage()); return false; } } @@ -276,7 +276,7 @@ protected T executeInternal(RequestExecutor executor, String uri, E * @throws WxErrorException 异常 */ protected String extractAccessToken(String resultContent) throws WxErrorException { - log.debug("access-token response: " + resultContent); + log.debug("access-token response: {}", resultContent); WxChannelConfig config = this.getConfig(); WxError error = WxError.fromJson(resultContent, WxType.Channel); if (error.getErrorCode() != 0) { diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxAssistantServiceImpl.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxAssistantServiceImpl.java index 20572c5ef0..55be5abcca 100644 --- a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxAssistantServiceImpl.java +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxAssistantServiceImpl.java @@ -28,7 +28,7 @@ public class WxAssistantServiceImpl implements WxAssistantService { /** 微信商店服务 */ - private final BaseWxChannelServiceImpl shopService; + private final BaseWxChannelServiceImpl shopService; @Override public WxChannelBaseResponse addWindowProduct(AddWindowProductRequest req) throws WxErrorException { String resJson = shopService.post(ADD_WINDOW_PRODUCT_URL, "{}"); diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelAddressServiceImpl.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelAddressServiceImpl.java index 53b9eb4d7a..20cf128559 100644 --- a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelAddressServiceImpl.java +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelAddressServiceImpl.java @@ -29,9 +29,9 @@ public class WxChannelAddressServiceImpl implements WxChannelAddressService { /** 微信商店服务 */ - private final BaseWxChannelServiceImpl shopService; + private final BaseWxChannelServiceImpl shopService; - public WxChannelAddressServiceImpl(BaseWxChannelServiceImpl shopService) { + public WxChannelAddressServiceImpl(BaseWxChannelServiceImpl shopService) { this.shopService = shopService; } diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelAfterSaleServiceImpl.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelAfterSaleServiceImpl.java index a4be86f28d..4e314d52fa 100644 --- a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelAfterSaleServiceImpl.java +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelAfterSaleServiceImpl.java @@ -23,16 +23,22 @@ public class WxChannelAfterSaleServiceImpl implements WxChannelAfterSaleService { /** 微信商店服务 */ - private final BaseWxChannelServiceImpl shopService; + private final BaseWxChannelServiceImpl shopService; - public WxChannelAfterSaleServiceImpl(BaseWxChannelServiceImpl shopService) { + public WxChannelAfterSaleServiceImpl(BaseWxChannelServiceImpl shopService) { this.shopService = shopService; } @Override public AfterSaleListResponse listIds(Long beginCreateTime, Long endCreateTime, String nextKey) throws WxErrorException { - AfterSaleListParam param = new AfterSaleListParam(beginCreateTime, endCreateTime, nextKey); + AfterSaleListParam param = new AfterSaleListParam(beginCreateTime, endCreateTime, null, null, nextKey); + String resJson = shopService.post(AFTER_SALE_LIST_URL, param); + return ResponseUtils.decode(resJson, AfterSaleListResponse.class); + } + + @Override + public AfterSaleListResponse listIds(AfterSaleListParam param) throws WxErrorException { String resJson = shopService.post(AFTER_SALE_LIST_URL, param); return ResponseUtils.decode(resJson, AfterSaleListResponse.class); } diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelBasicServiceImpl.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelBasicServiceImpl.java index cac5e9e513..6eb699da23 100644 --- a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelBasicServiceImpl.java +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelBasicServiceImpl.java @@ -31,9 +31,9 @@ public class WxChannelBasicServiceImpl implements WxChannelBasicService { /** 微信商店服务 */ - private final BaseWxChannelServiceImpl shopService; + private final BaseWxChannelServiceImpl shopService; - public WxChannelBasicServiceImpl(BaseWxChannelServiceImpl shopService) { + public WxChannelBasicServiceImpl(BaseWxChannelServiceImpl shopService) { this.shopService = shopService; } @@ -56,7 +56,7 @@ public ChannelImageInfo uploadImg(int respType, String imgUrl) throws WxErrorExc public ChannelImageInfo uploadImg(int respType, File file, int height, int width) throws WxErrorException { String url = IMG_UPLOAD_URL + "?upload_type=0&resp_type=" + respType + "&height=" + height + "&width=" + width; RequestExecutor executor = ChannelFileUploadRequestExecutor.create(shopService); - String resJson = (String) shopService.execute(executor, url, file); + String resJson = shopService.execute(executor, url, file); UploadImageResponse response = ResponseUtils.decode(resJson, UploadImageResponse.class); return response.getImgInfo(); } @@ -64,19 +64,19 @@ public ChannelImageInfo uploadImg(int respType, File file, int height, int width @Override public QualificationFileResponse uploadQualificationFile(File file) throws WxErrorException { RequestExecutor executor = ChannelFileUploadRequestExecutor.create(shopService); - String resJson = (String) shopService.execute(executor, UPLOAD_QUALIFICATION_FILE, file); + String resJson = shopService.execute(executor, UPLOAD_QUALIFICATION_FILE, file); return ResponseUtils.decode(resJson, QualificationFileResponse.class); } @Override public ChannelImageResponse getImg(String mediaId) throws WxErrorException { String appId = shopService.getConfig().getAppid(); - ChannelImageResponse rs = null; + ChannelImageResponse rs; try { String url = GET_IMG_URL + "?media_id=" + mediaId; RequestExecutor executor = ChannelMediaDownloadRequestExecutor.create(shopService, Files.createTempDirectory("wxjava-channel-" + appId).toFile()); - rs = (ChannelImageResponse) shopService.execute(executor, url, null); + rs = shopService.execute(executor, url, null); } catch (IOException e) { throw new WxErrorException(WxError.builder().errorMsg(e.getMessage()).build(), e); } diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelBrandServiceImpl.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelBrandServiceImpl.java index 19aadcc06e..c6c476b116 100644 --- a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelBrandServiceImpl.java +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelBrandServiceImpl.java @@ -33,9 +33,9 @@ public class WxChannelBrandServiceImpl implements WxChannelBrandService { /** 微信商店服务 */ - private final BaseWxChannelServiceImpl shopService; + private final BaseWxChannelServiceImpl shopService; - public WxChannelBrandServiceImpl(BaseWxChannelServiceImpl shopService) { + public WxChannelBrandServiceImpl(BaseWxChannelServiceImpl shopService) { this.shopService = shopService; } 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 e5940e9879..23cd839848 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 @@ -37,9 +37,9 @@ public class WxChannelCategoryServiceImpl implements WxChannelCategoryService { /** 微信商店服务 */ - private final BaseWxChannelServiceImpl shopService; + private final BaseWxChannelServiceImpl shopService; - public WxChannelCategoryServiceImpl(BaseWxChannelServiceImpl shopService) { + public WxChannelCategoryServiceImpl(BaseWxChannelServiceImpl shopService) { this.shopService = shopService; } @@ -56,7 +56,7 @@ public List listAvailableCategory(String parentId) throws WxErrorE try { pid = Long.parseLong(parentId); } catch (Throwable e) { - log.error("parentId必须为数字, " + parentId, e); + log.error("parentId必须为数字, {}", parentId, e); return Collections.emptyList(); } String reqJson = "{\"f_cat_id\": " + pid + "}"; @@ -80,7 +80,7 @@ public CategoryDetailResult getCategoryDetail(String id) throws WxErrorException try { catId = Long.parseLong(id); } catch (Throwable e) { - log.error("id必须为数字, " + id, e); + log.error("id必须为数字, {}", id, e); return ResponseUtils.internalError(CategoryDetailResult.class); } String reqJson = "{\"cat_id\": " + catId + "}"; diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelCompassFinderServiceImpl.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelCompassFinderServiceImpl.java index acaad0c0c1..c80345aef2 100644 --- a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelCompassFinderServiceImpl.java +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelCompassFinderServiceImpl.java @@ -20,9 +20,9 @@ public class WxChannelCompassFinderServiceImpl implements WxChannelCompassFinder /** * 微信商店服务 */ - private final BaseWxChannelServiceImpl shopService; + private final BaseWxChannelServiceImpl shopService; - public WxChannelCompassFinderServiceImpl(BaseWxChannelServiceImpl shopService) {this.shopService = shopService;} + public WxChannelCompassFinderServiceImpl(BaseWxChannelServiceImpl shopService) {this.shopService = shopService;} @Override public OverallResponse getOverall(String ds) throws WxErrorException { diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelCompassShopServiceImpl.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelCompassShopServiceImpl.java index 36b5a23950..3a593a691f 100644 --- a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelCompassShopServiceImpl.java +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelCompassShopServiceImpl.java @@ -41,9 +41,9 @@ public class WxChannelCompassShopServiceImpl implements WxChannelCompassShopServ /** * 微信商店服务 */ - private final BaseWxChannelServiceImpl shopService; + private final BaseWxChannelServiceImpl shopService; - public WxChannelCompassShopServiceImpl(BaseWxChannelServiceImpl shopService) {this.shopService = shopService;} + public WxChannelCompassShopServiceImpl(BaseWxChannelServiceImpl shopService) {this.shopService = shopService;} @Override public ShopOverallResponse getShopOverall(String ds) throws WxErrorException { diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelCouponServiceImpl.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelCouponServiceImpl.java index 174626f4a9..22abf25fb0 100644 --- a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelCouponServiceImpl.java +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelCouponServiceImpl.java @@ -35,9 +35,9 @@ public class WxChannelCouponServiceImpl implements WxChannelCouponService { /** 微信商店服务 */ - private final BaseWxChannelServiceImpl shopService; + private final BaseWxChannelServiceImpl shopService; - public WxChannelCouponServiceImpl(BaseWxChannelServiceImpl shopService) { + public WxChannelCouponServiceImpl(BaseWxChannelServiceImpl shopService) { this.shopService = shopService; } diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelFreightTemplateServiceImpl.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelFreightTemplateServiceImpl.java index 8fbfbd09c3..b8f00a4f84 100644 --- a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelFreightTemplateServiceImpl.java +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelFreightTemplateServiceImpl.java @@ -24,9 +24,9 @@ @Slf4j public class WxChannelFreightTemplateServiceImpl implements WxChannelFreightTemplateService { /** 微信商店服务 */ - private final BaseWxChannelServiceImpl shopService; + private final BaseWxChannelServiceImpl shopService; - public WxChannelFreightTemplateServiceImpl(BaseWxChannelServiceImpl shopService) { + public WxChannelFreightTemplateServiceImpl(BaseWxChannelServiceImpl shopService) { this.shopService = shopService; } diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelFundServiceImpl.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelFundServiceImpl.java index 050a19f44d..7cf30905ec 100644 --- a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelFundServiceImpl.java +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelFundServiceImpl.java @@ -54,9 +54,9 @@ public class WxChannelFundServiceImpl implements WxChannelFundService { /** 微信商店服务 */ - private final BaseWxChannelServiceImpl shopService; + private final BaseWxChannelServiceImpl shopService; - public WxChannelFundServiceImpl(BaseWxChannelServiceImpl shopService) { + public WxChannelFundServiceImpl(BaseWxChannelServiceImpl shopService) { this.shopService = shopService; } diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelLiveDashboardServiceImpl.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelLiveDashboardServiceImpl.java index 7c9c876e9b..7eace4377b 100644 --- a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelLiveDashboardServiceImpl.java +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelLiveDashboardServiceImpl.java @@ -27,10 +27,10 @@ public class WxChannelLiveDashboardServiceImpl implements WxChannelLiveDashboard /** * 微信商店服务 */ - private final BaseWxChannelServiceImpl shopService; + private final BaseWxChannelServiceImpl shopService; private final ObjectMapper objectMapper = new ObjectMapper(); - public WxChannelLiveDashboardServiceImpl(BaseWxChannelServiceImpl shopService) {this.shopService = shopService;} + public WxChannelLiveDashboardServiceImpl(BaseWxChannelServiceImpl shopService) {this.shopService = shopService;} @Override public LiveListResponse getLiveList(Long ds) throws WxErrorException { diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelOrderServiceImpl.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelOrderServiceImpl.java index e98294d189..fd26268333 100644 --- a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelOrderServiceImpl.java +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelOrderServiceImpl.java @@ -1,24 +1,37 @@ package me.chanjar.weixin.channel.api.impl; import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Delivery.DELIVERY_SEND_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Delivery.GET_DELIVERY_COMPANY_NEW_URL; import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Delivery.GET_DELIVERY_COMPANY_URL; -import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Order.*; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Order.ACCEPT_ADDRESS_MODIFY_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Order.DECODE_SENSITIVE_INFO_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Order.ORDER_GET_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Order.ORDER_LIST_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Order.ORDER_SEARCH_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Order.REJECT_ADDRESS_MODIFY_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Order.UPDATE_ADDRESS_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Order.UPDATE_EXPRESS_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Order.UPDATE_PRICE_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Order.UPDATE_REMARK_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Order.UPLOAD_FRESH_INSPECT_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Order.VIRTUAL_TEL_NUMBER_URL; import java.util.List; import lombok.extern.slf4j.Slf4j; import me.chanjar.weixin.channel.api.WxChannelOrderService; import me.chanjar.weixin.channel.bean.base.AddressInfo; import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; -import me.chanjar.weixin.channel.bean.delivery.PackageAuditInfo; import me.chanjar.weixin.channel.bean.delivery.DeliveryCompanyResponse; import me.chanjar.weixin.channel.bean.delivery.DeliveryInfo; import me.chanjar.weixin.channel.bean.delivery.DeliverySendParam; import me.chanjar.weixin.channel.bean.delivery.FreshInspectParam; +import me.chanjar.weixin.channel.bean.delivery.PackageAuditInfo; import me.chanjar.weixin.channel.bean.order.ChangeOrderInfo; import me.chanjar.weixin.channel.bean.order.DecodeSensitiveInfoResponse; import me.chanjar.weixin.channel.bean.order.DeliveryUpdateParam; import me.chanjar.weixin.channel.bean.order.OrderAddressParam; import me.chanjar.weixin.channel.bean.order.OrderIdParam; +import me.chanjar.weixin.channel.bean.order.OrderInfoParam; import me.chanjar.weixin.channel.bean.order.OrderInfoResponse; import me.chanjar.weixin.channel.bean.order.OrderListParam; import me.chanjar.weixin.channel.bean.order.OrderListResponse; @@ -39,15 +52,22 @@ public class WxChannelOrderServiceImpl implements WxChannelOrderService { /** 微信商店服务 */ - private final BaseWxChannelServiceImpl shopService; + private final BaseWxChannelServiceImpl shopService; - public WxChannelOrderServiceImpl(BaseWxChannelServiceImpl shopService) { + public WxChannelOrderServiceImpl(BaseWxChannelServiceImpl shopService) { this.shopService = shopService; } @Override public OrderInfoResponse getOrder(String orderId) throws WxErrorException { - OrderIdParam param = new OrderIdParam(orderId); + OrderInfoParam param = new OrderInfoParam(orderId, null); + String resJson = shopService.post(ORDER_GET_URL, param); + return ResponseUtils.decode(resJson, OrderInfoResponse.class); + } + + @Override + public OrderInfoResponse getOrder(String orderId, Boolean encodeSensitiveInfo) throws WxErrorException { + OrderInfoParam param = new OrderInfoParam(orderId, encodeSensitiveInfo); String resJson = shopService.post(ORDER_GET_URL, param); return ResponseUtils.decode(resJson, OrderInfoResponse.class); } @@ -119,6 +139,16 @@ public DeliveryCompanyResponse listDeliveryCompany() throws WxErrorException { return ResponseUtils.decode(resJson, DeliveryCompanyResponse.class); } + @Override + public DeliveryCompanyResponse listDeliveryCompany(Boolean ewaybillOnly) throws WxErrorException { + String reqJson = "{}"; + if (ewaybillOnly != null) { + reqJson = "{\"ewaybill_only\":" + ewaybillOnly + "}"; + } + String resJson = shopService.post(GET_DELIVERY_COMPANY_NEW_URL, reqJson); + return ResponseUtils.decode(resJson, DeliveryCompanyResponse.class); + } + @Override public WxChannelBaseResponse deliveryOrder(String orderId, List deliveryList) throws WxErrorException { diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelProductServiceImpl.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelProductServiceImpl.java index bb131d2eaa..08c9638f0c 100644 --- a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelProductServiceImpl.java +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelProductServiceImpl.java @@ -56,9 +56,9 @@ public class WxChannelProductServiceImpl implements WxChannelProductService { /** 微信商店服务 */ - private final BaseWxChannelServiceImpl shopService; + private final BaseWxChannelServiceImpl shopService; - public WxChannelProductServiceImpl(BaseWxChannelServiceImpl shopService) { + public WxChannelProductServiceImpl(BaseWxChannelServiceImpl shopService) { this.shopService = shopService; } diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelServiceHttpClientImpl.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelServiceHttpClientImpl.java index d4b5afde0c..6f380f80fb 100644 --- a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelServiceHttpClientImpl.java +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelServiceHttpClientImpl.java @@ -1,28 +1,28 @@ package me.chanjar.weixin.channel.api.impl; -import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.GET_ACCESS_TOKEN_URL; -import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.GET_STABLE_ACCESS_TOKEN_URL; - -import java.io.IOException; import lombok.extern.slf4j.Slf4j; import me.chanjar.weixin.channel.bean.token.StableTokenParam; import me.chanjar.weixin.channel.config.WxChannelConfig; import me.chanjar.weixin.channel.util.JsonUtils; -import me.chanjar.weixin.common.util.http.HttpType; +import me.chanjar.weixin.common.util.http.HttpClientType; +import me.chanjar.weixin.common.util.http.apache.ApacheBasicResponseHandler; import me.chanjar.weixin.common.util.http.apache.ApacheHttpClientBuilder; import me.chanjar.weixin.common.util.http.apache.DefaultApacheHttpClientBuilder; import org.apache.commons.lang3.StringUtils; import org.apache.http.HttpHost; import org.apache.http.client.HttpClient; import org.apache.http.client.config.RequestConfig; -import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpPost; import org.apache.http.entity.ContentType; import org.apache.http.entity.StringEntity; -import org.apache.http.impl.client.BasicResponseHandler; import org.apache.http.impl.client.CloseableHttpClient; +import java.io.IOException; + +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.GET_ACCESS_TOKEN_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.GET_STABLE_ACCESS_TOKEN_URL; + /** * @author Zeyes */ @@ -63,8 +63,8 @@ public HttpHost getRequestHttpProxy() { } @Override - public HttpType getRequestType() { - return HttpType.APACHE_HTTP; + public HttpClientType getRequestType() { + return HttpClientType.APACHE_HTTP; } @Override @@ -76,27 +76,12 @@ protected String doGetAccessTokenRequest() throws IOException { url = String.format(url, config.getAppid(), config.getSecret()); - HttpGet httpGet = null; - CloseableHttpResponse response = null; - try { - httpGet = new HttpGet(url); - if (this.getRequestHttpProxy() != null) { - RequestConfig requestConfig = RequestConfig.custom().setProxy(this.getRequestHttpProxy()).build(); - httpGet.setConfig(requestConfig); - } - response = getRequestHttpClient().execute(httpGet); - return new BasicResponseHandler().handleResponse(response); - } finally { - if (httpGet != null) { - httpGet.releaseConnection(); - } - if (response != null) { - try { - response.close(); - } catch (IOException ignored) { - } - } + HttpGet httpGet = new HttpGet(url); + if (this.getRequestHttpProxy() != null) { + RequestConfig requestConfig = RequestConfig.custom().setProxy(this.getRequestHttpProxy()).build(); + httpGet.setConfig(requestConfig); } + return getRequestHttpClient().execute(httpGet, ApacheBasicResponseHandler.INSTANCE); } /** @@ -125,10 +110,6 @@ protected String doGetStableAccessTokenRequest(boolean forceRefresh) throws IOEx assert requestJson != null; httpPost.setEntity(new StringEntity(requestJson, ContentType.APPLICATION_JSON)); - try (CloseableHttpResponse response = getRequestHttpClient().execute(httpPost)) { - return new BasicResponseHandler().handleResponse(response); - } finally { - httpPost.releaseConnection(); - } + return getRequestHttpClient().execute(httpPost, ApacheBasicResponseHandler.INSTANCE); } } 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 new file mode 100644 index 0000000000..6cf2d38503 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelServiceHttpComponentsImpl.java @@ -0,0 +1,113 @@ +package me.chanjar.weixin.channel.api.impl; + +import lombok.extern.slf4j.Slf4j; +import me.chanjar.weixin.channel.bean.token.StableTokenParam; +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; +import org.apache.commons.lang3.StringUtils; +import org.apache.hc.client5.http.classic.HttpClient; +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; + +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.GET_ACCESS_TOKEN_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.GET_STABLE_ACCESS_TOKEN_URL; + +/** + * @author altusea + */ +@Slf4j +public class WxChannelServiceHttpComponentsImpl extends BaseWxChannelServiceImpl { + + private CloseableHttpClient httpClient; + private HttpHost httpProxy; + + @Override + public void initHttp() { + WxChannelConfig config = this.getConfig(); + HttpComponentsClientBuilder apacheHttpClientBuilder = DefaultHttpComponentsClientBuilder.get(); + + apacheHttpClientBuilder.httpProxyHost(config.getHttpProxyHost()) + .httpProxyPort(config.getHttpProxyPort()) + .httpProxyUsername(config.getHttpProxyUsername()) + .httpProxyPassword(config.getHttpProxyPassword().toCharArray()); + + if (config.getHttpProxyHost() != null && config.getHttpProxyPort() > 0) { + this.httpProxy = new HttpHost(config.getHttpProxyHost(), config.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 { + WxChannelConfig config = this.getConfig(); + String url = StringUtils.isNotEmpty(config.getAccessTokenUrl()) ? config.getAccessTokenUrl() : + StringUtils.isNotEmpty(config.getApiHostUrl()) ? + GET_ACCESS_TOKEN_URL.replace("https://api.weixin.qq.com", config.getApiHostUrl()) : GET_ACCESS_TOKEN_URL; + + url = String.format(url, config.getAppid(), config.getSecret()); + + HttpGet httpGet = new HttpGet(url); + if (this.getRequestHttpProxy() != null) { + RequestConfig requestConfig = RequestConfig.custom().setProxy(this.getRequestHttpProxy()).build(); + httpGet.setConfig(requestConfig); + } + return getRequestHttpClient().execute(httpGet, BasicResponseHandler.INSTANCE); + } + + /** + * 获取稳定版接口调用凭据 + * + * @param forceRefresh false 为普通模式, true为强制刷新模式 + * @return 返回json的字符串 + * @throws IOException the io exception + */ + @Override + protected String doGetStableAccessTokenRequest(boolean forceRefresh) throws IOException { + WxChannelConfig config = this.getConfig(); + String url = GET_STABLE_ACCESS_TOKEN_URL; + + HttpPost httpPost = new HttpPost(url); + if (this.getRequestHttpProxy() != null) { + RequestConfig requestConfig = RequestConfig.custom().setProxy(this.getRequestHttpProxy()).build(); + httpPost.setConfig(requestConfig); + } + StableTokenParam requestParam = new StableTokenParam(); + requestParam.setAppId(config.getAppid()); + requestParam.setSecret(config.getSecret()); + requestParam.setGrantType("client_credential"); + requestParam.setForceRefresh(forceRefresh); + String requestJson = JsonUtils.encode(requestParam); + assert requestJson != null; + + httpPost.setEntity(new StringEntity(requestJson, ContentType.APPLICATION_JSON)); + return getRequestHttpClient().execute(httpPost, BasicResponseHandler.INSTANCE); + } +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelServiceOkHttpImpl.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelServiceOkHttpImpl.java index 518aa968e7..6d109be70d 100644 --- a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelServiceOkHttpImpl.java +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelServiceOkHttpImpl.java @@ -9,7 +9,7 @@ import me.chanjar.weixin.channel.bean.token.StableTokenParam; import me.chanjar.weixin.channel.config.WxChannelConfig; import me.chanjar.weixin.channel.util.JsonUtils; -import me.chanjar.weixin.common.util.http.HttpType; +import me.chanjar.weixin.common.util.http.HttpClientType; import me.chanjar.weixin.common.util.http.okhttp.DefaultOkHttpClientBuilder; import me.chanjar.weixin.common.util.http.okhttp.OkHttpProxyInfo; import okhttp3.Authenticator; @@ -65,8 +65,8 @@ public OkHttpProxyInfo getRequestHttpProxy() { } @Override - public HttpType getRequestType() { - return HttpType.OK_HTTP; + public HttpClientType getRequestType() { + return HttpClientType.OK_HTTP; } @Override diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelSharerServiceImpl.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelSharerServiceImpl.java index 676b310288..3e27b124c7 100644 --- a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelSharerServiceImpl.java +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelSharerServiceImpl.java @@ -33,9 +33,9 @@ public class WxChannelSharerServiceImpl implements WxChannelSharerService { /** 微信商店服务 */ - private final BaseWxChannelServiceImpl shopService; + private final BaseWxChannelServiceImpl shopService; - public WxChannelSharerServiceImpl(BaseWxChannelServiceImpl shopService) { + public WxChannelSharerServiceImpl(BaseWxChannelServiceImpl shopService) { this.shopService = shopService; } diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelVipServiceImpl.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelVipServiceImpl.java index c06e7ff7a4..4644989d60 100644 --- a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelVipServiceImpl.java +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelVipServiceImpl.java @@ -18,9 +18,9 @@ @Slf4j public class WxChannelVipServiceImpl implements WxChannelVipService { - private BaseWxChannelServiceImpl shopService; + private final BaseWxChannelServiceImpl shopService; - public WxChannelVipServiceImpl(BaseWxChannelServiceImpl shopService) { + public WxChannelVipServiceImpl(BaseWxChannelServiceImpl shopService) { this.shopService = shopService; } diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelWarehouseServiceImpl.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelWarehouseServiceImpl.java index b9609e5c6b..6805f26a4f 100644 --- a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelWarehouseServiceImpl.java +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelWarehouseServiceImpl.java @@ -40,9 +40,9 @@ public class WxChannelWarehouseServiceImpl implements WxChannelWarehouseService { /** 微信商店服务 */ - private final BaseWxChannelServiceImpl shopService; + private final BaseWxChannelServiceImpl shopService; - public WxChannelWarehouseServiceImpl(BaseWxChannelServiceImpl shopService) { + public WxChannelWarehouseServiceImpl(BaseWxChannelServiceImpl shopService) { this.shopService = shopService; } diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxFinderLiveServiceImpl.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxFinderLiveServiceImpl.java index aecd1cccac..51623609cf 100644 --- a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxFinderLiveServiceImpl.java +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxFinderLiveServiceImpl.java @@ -26,7 +26,7 @@ public class WxFinderLiveServiceImpl implements WxFinderLiveService { /** 微信商店服务 */ - private final BaseWxChannelServiceImpl shopService; + private final BaseWxChannelServiceImpl shopService; @Override public FinderAttrResponse getFinderAttrByAppid() throws WxErrorException { String resJson = shopService.post(GET_FINDER_ATTR_BY_APPID, "{}"); diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxLeadComponentServiceImpl.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxLeadComponentServiceImpl.java index b99cfe9f47..eb1bcee28c 100644 --- a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxLeadComponentServiceImpl.java +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxLeadComponentServiceImpl.java @@ -38,7 +38,7 @@ public class WxLeadComponentServiceImpl implements WxLeadComponentService { /** 微信商店服务 */ - private final BaseWxChannelServiceImpl shopService; + private final BaseWxChannelServiceImpl shopService; private final ObjectMapper objectMapper = new ObjectMapper(); @Override public LeadInfoResponse getLeadsInfoByComponentId(GetLeadInfoByComponentRequest req) throws WxErrorException { diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxLeagueProductServiceImpl.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxLeagueProductServiceImpl.java index 29620874e2..fc8d2fbadc 100644 --- a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxLeagueProductServiceImpl.java +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxLeagueProductServiceImpl.java @@ -32,9 +32,9 @@ public class WxLeagueProductServiceImpl implements WxLeagueProductService { /** 微信商店服务 */ - private final BaseWxChannelServiceImpl shopService; + private final BaseWxChannelServiceImpl shopService; - public WxLeagueProductServiceImpl(BaseWxChannelServiceImpl shopService) { + public WxLeagueProductServiceImpl(BaseWxChannelServiceImplshopService) { this.shopService = shopService; } diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxLeaguePromoterServiceImpl.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxLeaguePromoterServiceImpl.java index a6bfddfbef..c81df29533 100644 --- a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxLeaguePromoterServiceImpl.java +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxLeaguePromoterServiceImpl.java @@ -24,9 +24,9 @@ public class WxLeaguePromoterServiceImpl implements WxLeaguePromoterService { /** 微信商店服务 */ - private final BaseWxChannelServiceImpl shopService; + private final BaseWxChannelServiceImpl shopService; - public WxLeaguePromoterServiceImpl(BaseWxChannelServiceImpl shopService) { + public WxLeaguePromoterServiceImpl(BaseWxChannelServiceImpl shopService) { this.shopService = shopService; } @@ -58,6 +58,34 @@ public PromoterInfoResponse getPromoterInfo(String finderId) throws WxErrorExcep return ResponseUtils.decode(resJson, PromoterInfoResponse.class); } + @Override + public WxChannelBaseResponse addPromoterV2(String promoterId) throws WxErrorException { + String reqJson = "{\"promoter_id\":\"" + promoterId + "\"}"; + String resJson = shopService.post(ADD_PROMOTER_URL, reqJson); + return ResponseUtils.decode(resJson, WxChannelBaseResponse.class); + } + + @Override + public WxChannelBaseResponse updatePromoterV2(String promoterId, int type) throws WxErrorException { + String reqJson = "{\"promoter_id\":\"" + promoterId + "\",\"type\":" + type + "}"; + String resJson = shopService.post(EDIT_PROMOTER_URL, reqJson); + return ResponseUtils.decode(resJson, WxChannelBaseResponse.class); + } + + @Override + public WxChannelBaseResponse deletePromoterV2(String promoterId) throws WxErrorException { + String reqJson = "{\"promoter_id\":\"" + promoterId + "\"}"; + String resJson = shopService.post(DELETE_PROMOTER_URL, reqJson); + return ResponseUtils.decode(resJson, WxChannelBaseResponse.class); + } + + @Override + public PromoterInfoResponse getPromoterInfoV2(String promoterId) throws WxErrorException { + String reqJson = "{\"promoter_id\":\"" + promoterId + "\"}"; + String resJson = shopService.post(GET_PROMOTER_URL, reqJson); + return ResponseUtils.decode(resJson, PromoterInfoResponse.class); + } + @Override public PromoterListResponse listPromoter(Integer pageIndex, Integer pageSize, Integer status) throws WxErrorException { diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxLeagueSupplierServiceImpl.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxLeagueSupplierServiceImpl.java index d69296bd0f..2b280a2f6d 100644 --- a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxLeagueSupplierServiceImpl.java +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxLeagueSupplierServiceImpl.java @@ -38,9 +38,9 @@ public class WxLeagueSupplierServiceImpl implements WxLeagueSupplierService { /** 微信商店服务 */ - private final BaseWxChannelServiceImpl shopService; + private final BaseWxChannelServiceImpl shopService; - public WxLeagueSupplierServiceImpl(BaseWxChannelServiceImpl shopService) { + public WxLeagueSupplierServiceImpl(BaseWxChannelServiceImpl shopService) { this.shopService = shopService; } diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxLeagueWindowServiceImpl.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxLeagueWindowServiceImpl.java index a59fc6efa5..a0c21ab4ef 100644 --- a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxLeagueWindowServiceImpl.java +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxLeagueWindowServiceImpl.java @@ -29,9 +29,9 @@ public class WxLeagueWindowServiceImpl implements WxLeagueWindowService { /** 微信商店服务 */ - private final BaseWxChannelServiceImpl shopService; + private final BaseWxChannelServiceImpl shopService; - public WxLeagueWindowServiceImpl(BaseWxChannelServiceImpl shopService) { + public WxLeagueWindowServiceImpl(BaseWxChannelServiceImpl shopService) { this.shopService = shopService; } diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxStoreCooperationServiceImpl.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxStoreCooperationServiceImpl.java index f82e35fa2f..56dc78e09e 100644 --- a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxStoreCooperationServiceImpl.java +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxStoreCooperationServiceImpl.java @@ -25,9 +25,9 @@ public class WxStoreCooperationServiceImpl implements WxStoreCooperationService { /** 微信小店服务 */ - private final BaseWxChannelServiceImpl storeService; + private final BaseWxChannelServiceImpl storeService; - public WxStoreCooperationServiceImpl(BaseWxChannelServiceImpl storeService) { + public WxStoreCooperationServiceImpl(BaseWxChannelServiceImpl storeService) { this.storeService = storeService; } diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxStoreHomePageServiceImpl.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxStoreHomePageServiceImpl.java index b5f3038e98..e3e9f06deb 100644 --- a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxStoreHomePageServiceImpl.java +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxStoreHomePageServiceImpl.java @@ -37,9 +37,9 @@ public class WxStoreHomePageServiceImpl implements WxStoreHomePageService { /** 微信小店服务 */ - private final BaseWxChannelServiceImpl storeService; + private final BaseWxChannelServiceImpl storeService; - public WxStoreHomePageServiceImpl(BaseWxChannelServiceImpl storeService) { + public WxStoreHomePageServiceImpl(BaseWxChannelServiceImpl storeService) { this.storeService = storeService; } diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/after/AfterSaleExchangeDeliveryInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/after/AfterSaleExchangeDeliveryInfo.java new file mode 100644 index 0000000000..277d9d4d89 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/after/AfterSaleExchangeDeliveryInfo.java @@ -0,0 +1,35 @@ +package me.chanjar.weixin.channel.bean.after; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.Data; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.bean.base.AddressInfo; + +/** + * 换货类型的发货物流信息 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +public class AfterSaleExchangeDeliveryInfo implements Serializable { + + private static final long serialVersionUID = 3039216368034112038L; + + /** 快递单号 */ + @JsonProperty("waybill_id") + private String waybillId; + + /** 物流公司id */ + @JsonProperty("delivery_id") + private String deliveryId; + + /** 物流公司名称 */ + @JsonProperty("delivery_name") + private String deliveryName; + + /** 地址信息 */ + @JsonProperty("address_info") + private AddressInfo addressInfo; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/after/AfterSaleExchangeProductInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/after/AfterSaleExchangeProductInfo.java new file mode 100644 index 0000000000..1e862791ea --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/after/AfterSaleExchangeProductInfo.java @@ -0,0 +1,34 @@ +package me.chanjar.weixin.channel.bean.after; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 换货商品信息 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +public class AfterSaleExchangeProductInfo implements Serializable { + + private static final long serialVersionUID = -1341436607011117854L; + + /** 商品spuid */ + @JsonProperty("product_id") + private String productId; + + /** 旧商品skuid */ + @JsonProperty("old_sku_id") + private String oldSkuId; + + /** 新商品skuid */ + @JsonProperty("new_sku_id") + private String newSkuId; + + /** 数量 */ + @JsonProperty("product_cnt") + private String productCnt; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/after/AfterSaleInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/after/AfterSaleInfo.java index 3a9d390c95..d465766d75 100644 --- a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/after/AfterSaleInfo.java +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/after/AfterSaleInfo.java @@ -86,4 +86,16 @@ public class AfterSaleInfo implements Serializable { /** 仅在待商家审核退款退货申请或收货期间返回,表示操作剩余时间(秒数)*/ @JsonProperty("deadline") private Long deadline; + + /** 售后换货商品信息 */ + @JsonProperty("exchange_product_info") + private AfterSaleExchangeProductInfo exchangeProductInfo; + + /** 售后换货物流信息 */ + @JsonProperty("exchange_delivery_info") + private AfterSaleExchangeDeliveryInfo exchangeDeliveryInfo; + + /** 售后换货虚拟号码信息 */ + @JsonProperty("virtual_tel_num_info") + private AfterSaleVirtualNumberInfo virtualTelNumInfo; } diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/after/AfterSaleListParam.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/after/AfterSaleListParam.java index 78cc394085..a477a2c581 100644 --- a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/after/AfterSaleListParam.java +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/after/AfterSaleListParam.java @@ -28,6 +28,14 @@ public class AfterSaleListParam implements Serializable { @JsonProperty("end_create_time") private Long endCreateTime; + /** 售后单更新起始时间 */ + @JsonProperty("begin_update_time") + private Long beginUpdateTime; + + /** 售后单更新结束时间,end_update_time减去begin_update_time不得大于24小时 */ + @JsonProperty("end_update_time") + private Long endUpdateTime; + /** 翻页参数,从第二页开始传,来源于上一页的返回值 */ @JsonProperty("next_key") private String nextKey; diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/after/AfterSaleVirtualNumberInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/after/AfterSaleVirtualNumberInfo.java new file mode 100644 index 0000000000..4366fa5ce9 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/after/AfterSaleVirtualNumberInfo.java @@ -0,0 +1,26 @@ +package me.chanjar.weixin.channel.bean.after; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 虚拟号码信息 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +public class AfterSaleVirtualNumberInfo implements Serializable { + private static final long serialVersionUID = -5756618937333859985L; + + /** 虚拟号码 */ + @JsonProperty("virtual_tel_number") + private String virtualTelNumber; + + /** 虚拟号码过期时间 */ + @JsonProperty("virtual_tel_expire_time") + private Long virtualTelExpireTime; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/league/promoter/PromoterListResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/league/promoter/PromoterListResponse.java index c193550369..f7c7298c03 100644 --- a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/league/promoter/PromoterListResponse.java +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/league/promoter/PromoterListResponse.java @@ -18,11 +18,19 @@ public class PromoterListResponse extends WxChannelBaseResponse { private static final long serialVersionUID = 1411870432999885996L; - /** 达人finder_id列表 */ + /** 达人finder_id列表,待废除后续以promoter_ids为准 */ @JsonProperty("finder_ids") private List finderIds; /** 达人总数 */ @JsonProperty("total_num") private Integer totalNum; + + /** 后面是否还有(true: 还有内容; false: 已结束)*/ + @JsonProperty("continue_flag") + private Boolean continueFlag; + + /** 达人带货id列表 */ + @JsonProperty("promoter_ids") + private List promoterIds; } diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/OrderCustomInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/OrderCustomInfo.java new file mode 100644 index 0000000000..88981c6ccc --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/OrderCustomInfo.java @@ -0,0 +1,33 @@ +package me.chanjar.weixin.channel.bean.order; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 商品定制信息 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +public class OrderCustomInfo implements Serializable { + private static final long serialVersionUID = 6681266835402157651L; + + /** 定制图片,custom_type=2时返回 */ + @JsonProperty("custom_img_url") + private String customImgUrl; + + /** 定制文字,custom_type=1时返回 */ + @JsonProperty("custom_word") + private String customWord; + + /** 定制类型,枚举值请参考CustomType枚举 */ + @JsonProperty("custom_type") + private Integer customType; + + /** 定制预览图片,开启了定制预览时返回 */ + @JsonProperty("custom_preview_img_url") + private String customPreviewImgUrl; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/OrderDetailInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/OrderDetailInfo.java index 282f2f99f6..4d96023be1 100644 --- a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/OrderDetailInfo.java +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/OrderDetailInfo.java @@ -61,7 +61,18 @@ public class OrderDetailInfo implements Serializable { private OrderAgentInfo agentInfo; /** 订单来源信息 */ - @JsonProperty("source_info") - private OrderSourceInfo sourceInfo; + @JsonProperty("source_infos") + private List sourceInfos; + /** 订单退款信息 */ + @JsonProperty("refund_info") + private OrderSourceInfo refundInfo; + + /** 订单代写商品信息 */ + @JsonProperty("greeting_card_info") + private OrderGreetingCardInfo greetingCardInfo; + + /** 商品定制信息 */ + @JsonProperty("custom_info") + private OrderCustomInfo customInfo; } diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/OrderGreetingCardInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/OrderGreetingCardInfo.java new file mode 100644 index 0000000000..6b0c37033f --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/OrderGreetingCardInfo.java @@ -0,0 +1,29 @@ +package me.chanjar.weixin.channel.bean.order; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 订单商品贺卡信息 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +public class OrderGreetingCardInfo implements Serializable { + private static final long serialVersionUID = -6391443179945240242L; + + /** 贺卡落款,用户选填 */ + @JsonProperty("giver_name") + private String giverName; + + /** 贺卡称谓,用户选填 */ + @JsonProperty("receiver_name") + private String receiverName; + + /** 贺卡内容,用户必填 */ + @JsonProperty("greeting_message") + private String greetingMessage; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/OrderInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/OrderInfo.java index 894b36f7af..00222d8487 100644 --- a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/OrderInfo.java +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/OrderInfo.java @@ -39,6 +39,26 @@ public class OrderInfo implements Serializable { @JsonProperty("aftersale_detail") protected AfterSaleDetail afterSaleDetail; + /** 是否为礼物订单 */ + @JsonProperty("is_present") + private Boolean present; + + /** 礼物订单ID */ + @JsonProperty("present_order_id_str") + private String presentOrderId; + + /** 礼物订单留言 */ + @JsonProperty("present_note") + private String presentNote; + + /** 礼物订单赠送者openid */ + @JsonProperty("present_giver_openid") + private String presentGiverOpenid; + + /** 礼物订单赠送者unionid */ + @JsonProperty("present_giver_unionid") + private String presentGiverUnionid; + /** 创建时间 秒级时间戳 */ @JsonProperty("create_time") protected Integer createTime; @@ -46,5 +66,4 @@ public class OrderInfo implements Serializable { /** 更新时间 秒级时间戳 */ @JsonProperty("update_time") protected Integer updateTime; - } diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/OrderInfoParam.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/OrderInfoParam.java new file mode 100644 index 0000000000..e7a8c9a2b8 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/OrderInfoParam.java @@ -0,0 +1,31 @@ +package me.chanjar.weixin.channel.bean.order; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * 获取订单详情参数 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@JsonInclude(JsonInclude.Include.NON_NULL) +public class OrderInfoParam implements Serializable { + + private static final long serialVersionUID = 42L; + + /** 订单ID */ + @JsonProperty("order_id") + private String orderId; + + /** + * 用于商家提前测试订单脱敏效果,如果传true,即对订单进行脱敏,后期会默认对所有订单脱敏 + */ + @JsonProperty("encode_sensitive_info") + private Boolean encodeSensitiveInfo; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/OrderRefundInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/OrderRefundInfo.java new file mode 100644 index 0000000000..9e3ae522f8 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/OrderRefundInfo.java @@ -0,0 +1,21 @@ +package me.chanjar.weixin.channel.bean.order; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 订单退款信息 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +public class OrderRefundInfo implements Serializable { + private static final long serialVersionUID = -7257910073388645919L; + + /** 退还运费金额,礼物订单(is_present=true)可能存在 */ + @JsonProperty("refund_freight") + private Integer refundFreight; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/OrderSearchParam.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/OrderSearchParam.java index 0a9483e0d5..2f56747d19 100644 --- a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/OrderSearchParam.java +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/OrderSearchParam.java @@ -25,6 +25,6 @@ public class OrderSearchParam extends StreamPageParam { private Integer onAfterSaleOrderExist; /** 订单状态 {@link me.chanjar.weixin.channel.enums.WxOrderStatus} */ - @JsonProperty("on_aftersale_order_exist") + @JsonProperty("status") private Integer status; } diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/OrderSourceInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/OrderSourceInfo.java index fbdd663a5a..8d5e5aaef0 100644 --- a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/OrderSourceInfo.java +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/OrderSourceInfo.java @@ -23,26 +23,44 @@ public class OrderSourceInfo implements Serializable { private String skuId; /** - * 带货账户类型,1:视频号,2:公众号,3:小程序 + * 带货账号类型,1:视频号,2:公众号,3:小程序,4:企业微信,5:带货达人,6:服务号,1000:带货机构 */ @JsonProperty("account_type") private Integer accountType; /** - * 带货账户id,如果带货账户类型是视频号,此id为视频号id; 如果带货类型为 公众号/小程序, 此id 为对应 公众号/小程序 的appid + * 带货账号id,取决于带货账号类型(分别为视频号id、公众号appid、小程序appid、企业微信id、带货达人appid、服务号appid、带货机构id) */ @JsonProperty("account_id") private String accountId; /** - * 销售渠道, 0:关联账号,1:合作账号,100:联盟达人带货 + * 账号关联类型,0:关联账号,1:合作账号,2:授权号,100:达人带货,101:带货机构推广 */ @JsonProperty("sale_channel") private Integer saleChannel; /** - * 带货账户昵称 + * 带货账号昵称 */ @JsonProperty("account_nickname") private String accountNickname; + + /** + * 带货内容类型,1:企微成员转发 + */ + @JsonProperty("content_type") + private String contentType; + + /** + * 带货内容id,取决于带货内容类型(企微成员user_id) + */ + @JsonProperty("content_id") + private String contentId; + + /** + * 自营推客推广的带货机构id + */ + @JsonProperty("promoter_head_supplier_id") + private String promoterHeadSupplierId; } 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 e88f95e64b..b7d3add72a 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 @@ -247,8 +247,9 @@ public interface Complaint { /** 物流相关接口 */ public interface Delivery { - /** 获取快递公司列表 */ + String GET_DELIVERY_COMPANY_NEW_URL = "https://api.weixin.qq.com/channels/ec/order/deliverycompanylist/new/get"; + /** 获取快递公司列表(旧) */ String GET_DELIVERY_COMPANY_URL = "https://api.weixin.qq.com/channels/ec/order/deliverycompanylist/get"; /** 订单发货 */ String DELIVERY_SEND_URL = "https://api.weixin.qq.com/channels/ec/order/delivery/send"; diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/executor/ApacheHttpChannelFileUploadRequestExecutor.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/executor/ApacheHttpChannelFileUploadRequestExecutor.java new file mode 100644 index 0000000000..5ccb6f5cb1 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/executor/ApacheHttpChannelFileUploadRequestExecutor.java @@ -0,0 +1,47 @@ +package me.chanjar.weixin.channel.executor; + +import me.chanjar.weixin.common.enums.WxType; +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.common.util.http.RequestHttp; +import me.chanjar.weixin.common.util.http.ResponseHandler; +import me.chanjar.weixin.common.util.http.apache.Utf8ResponseHandler; +import org.apache.http.HttpEntity; +import org.apache.http.HttpHost; +import org.apache.http.client.config.RequestConfig; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.entity.mime.HttpMultipartMode; +import org.apache.http.entity.mime.MultipartEntityBuilder; +import org.apache.http.impl.client.CloseableHttpClient; + +import java.io.File; +import java.io.IOException; + +public class ApacheHttpChannelFileUploadRequestExecutor extends ChannelFileUploadRequestExecutor { + public ApacheHttpChannelFileUploadRequestExecutor(RequestHttp requestHttp) { + super(requestHttp); + } + + @Override + public String execute(String uri, File file, WxType wxType) throws WxErrorException, IOException { + HttpPost httpPost = new HttpPost(uri); + if (requestHttp.getRequestHttpProxy() != null) { + RequestConfig config = RequestConfig.custom().setProxy(requestHttp.getRequestHttpProxy()).build(); + httpPost.setConfig(config); + } + if (file != null) { + HttpEntity entity = MultipartEntityBuilder + .create() + .addBinaryBody("media", file) + .setMode(HttpMultipartMode.RFC6532) + .build(); + httpPost.setEntity(entity); + } + return requestHttp.getRequestHttpClient().execute(httpPost, Utf8ResponseHandler.INSTANCE); + } + + @Override + public void execute(String uri, File data, ResponseHandler handler, WxType wxType) + throws WxErrorException, IOException { + handler.handle(this.execute(uri, data, wxType)); + } +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/executor/ApacheHttpChannelMediaDownloadRequestExecutor.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/executor/ApacheHttpChannelMediaDownloadRequestExecutor.java new file mode 100644 index 0000000000..b9b44b60e2 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/executor/ApacheHttpChannelMediaDownloadRequestExecutor.java @@ -0,0 +1,90 @@ +package me.chanjar.weixin.channel.executor; + +import me.chanjar.weixin.channel.bean.image.ChannelImageResponse; +import me.chanjar.weixin.channel.util.JsonUtils; +import me.chanjar.weixin.common.enums.WxType; +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.common.util.http.RequestHttp; +import me.chanjar.weixin.common.util.http.ResponseHandler; +import me.chanjar.weixin.common.util.http.apache.InputStreamResponseHandler; +import me.chanjar.weixin.common.util.http.apache.Utf8ResponseHandler; +import org.apache.commons.io.FilenameUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.http.Header; +import org.apache.http.HttpHost; +import org.apache.http.client.config.RequestConfig; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.entity.ContentType; +import org.apache.http.impl.client.CloseableHttpClient; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; + +public class ApacheHttpChannelMediaDownloadRequestExecutor extends ChannelMediaDownloadRequestExecutor { + + public ApacheHttpChannelMediaDownloadRequestExecutor(RequestHttp requestHttp, File tmpDirFile) { + super(requestHttp, tmpDirFile); + } + + @Override + public ChannelImageResponse execute(String uri, String data, WxType wxType) throws WxErrorException, IOException { + if (data != null) { + if (uri.indexOf('?') == -1) { + uri += '?'; + } + uri += uri.endsWith("?") ? data : '&' + data; + } + + HttpGet httpGet = new HttpGet(uri); + if (requestHttp.getRequestHttpProxy() != null) { + RequestConfig config = RequestConfig.custom().setProxy(requestHttp.getRequestHttpProxy()).build(); + httpGet.setConfig(config); + } + + try (CloseableHttpResponse response = requestHttp.getRequestHttpClient().execute(httpGet); + InputStream inputStream = InputStreamResponseHandler.INSTANCE.handleResponse(response)) { + Header[] contentTypeHeader = response.getHeaders("Content-Type"); + String contentType = null; + if (contentTypeHeader != null && contentTypeHeader.length > 0) { + contentType = contentTypeHeader[0].getValue(); + if (contentType.startsWith(ContentType.APPLICATION_JSON.getMimeType())) { + // application/json; encoding=utf-8 下载媒体文件出错 + String responseContent = Utf8ResponseHandler.INSTANCE.handleResponse(response); + return JsonUtils.decode(responseContent, ChannelImageResponse.class); + } + } + + String fileName = this.getFileName(response); + if (StringUtils.isBlank(fileName)) { + fileName = String.valueOf(System.currentTimeMillis()); + } + + String baseName = FilenameUtils.getBaseName(fileName); + if (StringUtils.isBlank(fileName) || baseName.length() < 3) { + baseName = String.valueOf(System.currentTimeMillis()); + } + String extension = FilenameUtils.getExtension(fileName); + if (StringUtils.isBlank(extension)) { + extension = "unknown"; + } + File file = createTmpFile(inputStream, baseName, extension, tmpDirFile); + return new ChannelImageResponse(file, contentType); + } + } + + private String getFileName(CloseableHttpResponse response) throws WxErrorException { + Header[] contentDispositionHeader = response.getHeaders("Content-disposition"); + if (contentDispositionHeader == null || contentDispositionHeader.length == 0) { + return createDefaultFileName(); + } + return this.extractFileNameFromContentString(contentDispositionHeader[0].getValue()); + } + + @Override + public void execute(String uri, String data, ResponseHandler handler, WxType wxType) + throws WxErrorException, IOException { + handler.handle(this.execute(uri, data, wxType)); + } +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/executor/ChannelFileUploadRequestExecutor.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/executor/ChannelFileUploadRequestExecutor.java index 576f1c286a..78a6735192 100644 --- a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/executor/ChannelFileUploadRequestExecutor.java +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/executor/ChannelFileUploadRequestExecutor.java @@ -1,66 +1,35 @@ package me.chanjar.weixin.channel.executor; - -import java.io.File; -import java.io.IOException; -import me.chanjar.weixin.common.enums.WxType; -import me.chanjar.weixin.common.error.WxErrorException; import me.chanjar.weixin.common.util.http.RequestExecutor; import me.chanjar.weixin.common.util.http.RequestHttp; -import me.chanjar.weixin.common.util.http.ResponseHandler; -import me.chanjar.weixin.common.util.http.apache.Utf8ResponseHandler; -import org.apache.http.HttpEntity; -import org.apache.http.HttpHost; -import org.apache.http.client.config.RequestConfig; -import org.apache.http.client.methods.CloseableHttpResponse; -import org.apache.http.client.methods.HttpPost; -import org.apache.http.entity.mime.HttpMultipartMode; -import org.apache.http.entity.mime.MultipartEntityBuilder; -import org.apache.http.impl.client.CloseableHttpClient; + +import java.io.File; /** * 视频号小店 图片上传接口 请求的参数是File, 返回的结果是String * * @author Zeyes */ -public class ChannelFileUploadRequestExecutor implements RequestExecutor { +public abstract class ChannelFileUploadRequestExecutor implements RequestExecutor { - protected RequestHttp requestHttp; + protected RequestHttp requestHttp; - public ChannelFileUploadRequestExecutor(RequestHttp requestHttp) { + public ChannelFileUploadRequestExecutor(RequestHttp requestHttp) { this.requestHttp = requestHttp; } - @Override - public String execute(String uri, File file, WxType wxType) throws WxErrorException, IOException { - HttpPost httpPost = new HttpPost(uri); - if (requestHttp.getRequestHttpProxy() != null) { - RequestConfig config = RequestConfig.custom().setProxy(requestHttp.getRequestHttpProxy()).build(); - httpPost.setConfig(config); - } - if (file != null) { - HttpEntity entity = MultipartEntityBuilder - .create() - .addBinaryBody("media", file) - .setMode(HttpMultipartMode.RFC6532) - .build(); - httpPost.setEntity(entity); + @SuppressWarnings("unchecked") + public static RequestExecutor create(RequestHttp requestHttp) { + switch (requestHttp.getRequestType()) { + case APACHE_HTTP: + return new ApacheHttpChannelFileUploadRequestExecutor( + (RequestHttp) requestHttp); + case HTTP_COMPONENTS: + return new HttpComponentsChannelFileUploadRequestExecutor( + (RequestHttp) requestHttp); + default: + throw new IllegalArgumentException("不支持的http执行器类型:" + requestHttp.getRequestType()); } - try (CloseableHttpResponse response = requestHttp.getRequestHttpClient().execute(httpPost)) { - return Utf8ResponseHandler.INSTANCE.handleResponse(response); - } finally { - httpPost.releaseConnection(); - } - } - - @Override - public void execute(String uri, File data, ResponseHandler handler, WxType wxType) - throws WxErrorException, IOException { - handler.handle(this.execute(uri, data, wxType)); - } - - public static RequestExecutor create(RequestHttp requestHttp) { - return new ChannelFileUploadRequestExecutor(requestHttp); } } diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/executor/ChannelMediaDownloadRequestExecutor.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/executor/ChannelMediaDownloadRequestExecutor.java index 1b81dc6d15..dd4bf0ba89 100644 --- a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/executor/ChannelMediaDownloadRequestExecutor.java +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/executor/ChannelMediaDownloadRequestExecutor.java @@ -1,6 +1,9 @@ package me.chanjar.weixin.channel.executor; -import static org.apache.commons.io.FileUtils.openOutputStream; +import me.chanjar.weixin.channel.bean.image.ChannelImageResponse; +import me.chanjar.weixin.common.util.http.RequestExecutor; +import me.chanjar.weixin.common.util.http.RequestHttp; +import org.apache.commons.io.IOUtils; import java.io.File; import java.io.IOException; @@ -9,102 +12,38 @@ import java.util.UUID; import java.util.regex.Matcher; import java.util.regex.Pattern; -import lombok.extern.slf4j.Slf4j; -import me.chanjar.weixin.channel.bean.image.ChannelImageResponse; -import me.chanjar.weixin.channel.util.JsonUtils; -import me.chanjar.weixin.common.enums.WxType; -import me.chanjar.weixin.common.error.WxErrorException; -import me.chanjar.weixin.common.util.http.RequestExecutor; -import me.chanjar.weixin.common.util.http.RequestHttp; -import me.chanjar.weixin.common.util.http.ResponseHandler; -import me.chanjar.weixin.common.util.http.apache.InputStreamResponseHandler; -import me.chanjar.weixin.common.util.http.apache.Utf8ResponseHandler; -import org.apache.commons.io.FilenameUtils; -import org.apache.commons.io.IOUtils; -import org.apache.commons.lang3.StringUtils; -import org.apache.http.Header; -import org.apache.http.HttpHost; -import org.apache.http.client.config.RequestConfig; -import org.apache.http.client.methods.CloseableHttpResponse; -import org.apache.http.client.methods.HttpGet; -import org.apache.http.entity.ContentType; -import org.apache.http.impl.client.CloseableHttpClient; + +import static org.apache.commons.io.FileUtils.openOutputStream; /** * 下载媒体文件请求执行器 * * @author Zeyes */ -@Slf4j -public class ChannelMediaDownloadRequestExecutor implements RequestExecutor { +public abstract class ChannelMediaDownloadRequestExecutor implements RequestExecutor { - protected RequestHttp requestHttp; + protected RequestHttp requestHttp; protected File tmpDirFile; private static final Pattern PATTERN = Pattern.compile(".*filename=\"(.*)\""); - public ChannelMediaDownloadRequestExecutor(RequestHttp requestHttp, File tmpDirFile) { + public ChannelMediaDownloadRequestExecutor(RequestHttp requestHttp, File tmpDirFile) { this.requestHttp = requestHttp; this.tmpDirFile = tmpDirFile; } - @Override - public ChannelImageResponse execute(String uri, String data, WxType wxType) throws WxErrorException, IOException { - if (data != null) { - if (uri.indexOf('?') == -1) { - uri += '?'; - } - uri += uri.endsWith("?") ? data : '&' + data; - } - - HttpGet httpGet = new HttpGet(uri); - if (requestHttp.getRequestHttpProxy() != null) { - RequestConfig config = RequestConfig.custom().setProxy(requestHttp.getRequestHttpProxy()).build(); - httpGet.setConfig(config); + @SuppressWarnings("unchecked") + public static RequestExecutor create(RequestHttp requestHttp, File tmpDirFile) { + switch (requestHttp.getRequestType()) { + case APACHE_HTTP: + return new ApacheHttpChannelMediaDownloadRequestExecutor( + (RequestHttp) requestHttp, tmpDirFile); + case HTTP_COMPONENTS: + return new HttpComponentsChannelMediaDownloadRequestExecutor( + (RequestHttp) requestHttp, tmpDirFile); + default: + throw new IllegalArgumentException("不支持的http执行器类型:" + requestHttp.getRequestType()); } - - try (CloseableHttpResponse response = requestHttp.getRequestHttpClient().execute(httpGet); - InputStream inputStream = InputStreamResponseHandler.INSTANCE.handleResponse(response)) { - Header[] contentTypeHeader = response.getHeaders("Content-Type"); - String contentType = null; - if (contentTypeHeader != null && contentTypeHeader.length > 0) { - contentType = contentTypeHeader[0].getValue(); - if (contentType.startsWith(ContentType.APPLICATION_JSON.getMimeType())) { - // application/json; encoding=utf-8 下载媒体文件出错 - String responseContent = Utf8ResponseHandler.INSTANCE.handleResponse(response); - return JsonUtils.decode(responseContent, ChannelImageResponse.class); - } - } - - String fileName = this.getFileName(response); - if (StringUtils.isBlank(fileName)) { - fileName = String.valueOf(System.currentTimeMillis()); - } - - String baseName = FilenameUtils.getBaseName(fileName); - if (StringUtils.isBlank(fileName) || baseName.length() < 3) { - baseName = String.valueOf(System.currentTimeMillis()); - } - String extension = FilenameUtils.getExtension(fileName); - if (StringUtils.isBlank(extension)) { - extension = "unknown"; - } - File file = createTmpFile(inputStream, baseName, extension, tmpDirFile); - ChannelImageResponse result = new ChannelImageResponse(file, contentType); - return result; - } finally { - httpGet.releaseConnection(); - } - } - - @Override - public void execute(String uri, String data, ResponseHandler handler, WxType wxType) - throws WxErrorException, IOException { - handler.handle(this.execute(uri, data, wxType)); - } - - public static RequestExecutor create(RequestHttp requestHttp, File tmpDirFile) { - return new ChannelMediaDownloadRequestExecutor(requestHttp, tmpDirFile); } /** @@ -125,20 +64,12 @@ public static File createTmpFile(InputStream inputStream, String name, String ex return resultFile; } - private String getFileName(CloseableHttpResponse response) throws WxErrorException { - Header[] contentDispositionHeader = response.getHeaders("Content-disposition"); - if (contentDispositionHeader == null || contentDispositionHeader.length == 0) { - return createDefaultFileName(); - } - return this.extractFileNameFromContentString(contentDispositionHeader[0].getValue()); - } - - private String createDefaultFileName() { + protected String createDefaultFileName() { return UUID.randomUUID().toString(); } - private String extractFileNameFromContentString(String content) throws WxErrorException { - if (content == null || content.length() == 0) { + protected String extractFileNameFromContentString(String content) { + if (content == null || content.isEmpty()) { return createDefaultFileName(); } Matcher m = PATTERN.matcher(content); diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/executor/HttpComponentsChannelFileUploadRequestExecutor.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/executor/HttpComponentsChannelFileUploadRequestExecutor.java new file mode 100644 index 0000000000..3b1e7076a9 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/executor/HttpComponentsChannelFileUploadRequestExecutor.java @@ -0,0 +1,47 @@ +package me.chanjar.weixin.channel.executor; + +import me.chanjar.weixin.common.enums.WxType; +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.common.util.http.RequestHttp; +import me.chanjar.weixin.common.util.http.ResponseHandler; +import me.chanjar.weixin.common.util.http.hc.Utf8ResponseHandler; +import org.apache.hc.client5.http.classic.methods.HttpPost; +import org.apache.hc.client5.http.config.RequestConfig; +import org.apache.hc.client5.http.entity.mime.HttpMultipartMode; +import org.apache.hc.client5.http.entity.mime.MultipartEntityBuilder; +import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; +import org.apache.hc.core5.http.HttpEntity; +import org.apache.hc.core5.http.HttpHost; + +import java.io.File; +import java.io.IOException; + +public class HttpComponentsChannelFileUploadRequestExecutor extends ChannelFileUploadRequestExecutor { + public HttpComponentsChannelFileUploadRequestExecutor(RequestHttp requestHttp) { + super(requestHttp); + } + + @Override + public String execute(String uri, File file, WxType wxType) throws WxErrorException, IOException { + HttpPost httpPost = new HttpPost(uri); + if (requestHttp.getRequestHttpProxy() != null) { + RequestConfig config = RequestConfig.custom().setProxy(requestHttp.getRequestHttpProxy()).build(); + httpPost.setConfig(config); + } + if (file != null) { + HttpEntity entity = MultipartEntityBuilder + .create() + .addBinaryBody("media", file) + .setMode(HttpMultipartMode.EXTENDED) + .build(); + httpPost.setEntity(entity); + } + return requestHttp.getRequestHttpClient().execute(httpPost, Utf8ResponseHandler.INSTANCE); + } + + @Override + public void execute(String uri, File data, ResponseHandler handler, WxType wxType) + throws WxErrorException, IOException { + handler.handle(this.execute(uri, data, wxType)); + } +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/executor/HttpComponentsChannelMediaDownloadRequestExecutor.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/executor/HttpComponentsChannelMediaDownloadRequestExecutor.java new file mode 100644 index 0000000000..95a13f6c86 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/executor/HttpComponentsChannelMediaDownloadRequestExecutor.java @@ -0,0 +1,94 @@ +package me.chanjar.weixin.channel.executor; + +import me.chanjar.weixin.channel.bean.image.ChannelImageResponse; +import me.chanjar.weixin.channel.util.JsonUtils; +import me.chanjar.weixin.common.enums.WxType; +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.common.util.http.RequestHttp; +import me.chanjar.weixin.common.util.http.ResponseHandler; +import me.chanjar.weixin.common.util.http.hc.InputStreamResponseHandler; +import me.chanjar.weixin.common.util.http.hc.Utf8ResponseHandler; +import org.apache.commons.io.FilenameUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.hc.client5.http.ClientProtocolException; +import org.apache.hc.client5.http.classic.methods.HttpGet; +import org.apache.hc.client5.http.config.RequestConfig; +import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; +import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse; +import org.apache.hc.core5.http.ContentType; +import org.apache.hc.core5.http.Header; +import org.apache.hc.core5.http.HttpException; +import org.apache.hc.core5.http.HttpHost; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; + +public class HttpComponentsChannelMediaDownloadRequestExecutor extends ChannelMediaDownloadRequestExecutor { + + public HttpComponentsChannelMediaDownloadRequestExecutor(RequestHttp requestHttp, File tmpDirFile) { + super(requestHttp, tmpDirFile); + } + + @Override + public ChannelImageResponse execute(String uri, String data, WxType wxType) throws WxErrorException, IOException { + if (data != null) { + if (uri.indexOf('?') == -1) { + uri += '?'; + } + uri += uri.endsWith("?") ? data : '&' + data; + } + + HttpGet httpGet = new HttpGet(uri); + if (requestHttp.getRequestHttpProxy() != null) { + RequestConfig config = RequestConfig.custom().setProxy(requestHttp.getRequestHttpProxy()).build(); + httpGet.setConfig(config); + } + + try (CloseableHttpResponse response = requestHttp.getRequestHttpClient().execute(httpGet); + InputStream inputStream = InputStreamResponseHandler.INSTANCE.handleResponse(response)) { + Header[] contentTypeHeader = response.getHeaders("Content-Type"); + String contentType = null; + if (contentTypeHeader != null && contentTypeHeader.length > 0) { + contentType = contentTypeHeader[0].getValue(); + if (contentType.startsWith(ContentType.APPLICATION_JSON.getMimeType())) { + // application/json; encoding=utf-8 下载媒体文件出错 + String responseContent = Utf8ResponseHandler.INSTANCE.handleResponse(response); + return JsonUtils.decode(responseContent, ChannelImageResponse.class); + } + } + + String fileName = this.getFileName(response); + if (StringUtils.isBlank(fileName)) { + fileName = String.valueOf(System.currentTimeMillis()); + } + + String baseName = FilenameUtils.getBaseName(fileName); + if (StringUtils.isBlank(fileName) || baseName.length() < 3) { + baseName = String.valueOf(System.currentTimeMillis()); + } + String extension = FilenameUtils.getExtension(fileName); + if (StringUtils.isBlank(extension)) { + extension = "unknown"; + } + File file = createTmpFile(inputStream, baseName, extension, tmpDirFile); + return new ChannelImageResponse(file, contentType); + } catch (HttpException httpException) { + throw new ClientProtocolException(httpException.getMessage(), httpException); + } + } + + private String getFileName(CloseableHttpResponse response) throws WxErrorException { + Header[] contentDispositionHeader = response.getHeaders("Content-disposition"); + if (contentDispositionHeader == null || contentDispositionHeader.length == 0) { + return createDefaultFileName(); + } + return this.extractFileNameFromContentString(contentDispositionHeader[0].getValue()); + } + + @Override + public void execute(String uri, String data, ResponseHandler handler, WxType wxType) + throws WxErrorException, IOException { + handler.handle(this.execute(uri, data, wxType)); + } +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/message/WxChannelMessageRouter.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/message/WxChannelMessageRouter.java index c35f75ac0b..3236e18303 100644 --- a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/message/WxChannelMessageRouter.java +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/message/WxChannelMessageRouter.java @@ -137,7 +137,7 @@ public Object route(final WxChannelMessage message, final String content, final } } - if (matchRules.size() == 0) { + if (matchRules.isEmpty()) { return null; } final List> futures = new ArrayList<>(); @@ -157,7 +157,7 @@ public Object route(final WxChannelMessage message, final String content, final } } - if (futures.size() > 0) { + if (!futures.isEmpty()) { this.executorService.submit(() -> { for (Future future : futures) { try { diff --git a/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/api/impl/WxChannelOrderServiceImplTest.java b/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/api/impl/WxChannelOrderServiceImplTest.java index 78bd13e7d6..2c70c7bde8 100644 --- a/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/api/impl/WxChannelOrderServiceImplTest.java +++ b/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/api/impl/WxChannelOrderServiceImplTest.java @@ -20,6 +20,7 @@ import me.chanjar.weixin.channel.bean.order.OrderInfoResponse; import me.chanjar.weixin.channel.bean.order.OrderListParam; import me.chanjar.weixin.channel.bean.order.OrderListResponse; +import me.chanjar.weixin.channel.bean.order.OrderSearchCondition; import me.chanjar.weixin.channel.bean.order.OrderSearchParam; import me.chanjar.weixin.channel.bean.order.VirtualTelNumberResponse; import me.chanjar.weixin.channel.test.ApiTestModule; @@ -45,6 +46,16 @@ public void testGetOrder() throws WxErrorException { assertTrue(response.isSuccess()); } + @Test + public void testGetOrder2() throws WxErrorException { + WxChannelOrderService orderService = channelService.getOrderService(); + String orderId = ""; + boolean encodeSensitiveInfo = true; + OrderInfoResponse response = orderService.getOrder(orderId, encodeSensitiveInfo); + assertNotNull(response); + assertTrue(response.isSuccess()); + } + @Test public void testGetOrders() throws WxErrorException { WxChannelOrderService orderService = channelService.getOrderService(); @@ -58,6 +69,10 @@ public void testGetOrders() throws WxErrorException { public void testSearchOrder() throws WxErrorException { WxChannelOrderService orderService = channelService.getOrderService(); OrderSearchParam param = new OrderSearchParam(); + param.setPageSize(100); + OrderSearchCondition searchCondition = new OrderSearchCondition(); + searchCondition.setTitle(""); + param.setSearchCondition(searchCondition); OrderListResponse response = orderService.searchOrder(param); assertNotNull(response); assertTrue(response.isSuccess()); @@ -135,7 +150,7 @@ public void testCloseOrder() { @Test public void testListDeliveryCompany() throws WxErrorException { WxChannelOrderService orderService = channelService.getOrderService(); - DeliveryCompanyResponse response = orderService.listDeliveryCompany(); + DeliveryCompanyResponse response = orderService.listDeliveryCompany(false); assertNotNull(response); assertTrue(response.isSuccess()); } diff --git a/weixin-java-common/pom.xml b/weixin-java-common/pom.xml index e89234d17a..d176ee7576 100644 --- a/weixin-java-common/pom.xml +++ b/weixin-java-common/pom.xml @@ -6,7 +6,7 @@ com.github.binarywang wx-java - 4.7.2.B + 4.7.8.B weixin-java-common @@ -24,6 +24,11 @@ okhttp provided + + org.apache.httpcomponents.client5 + httpclient5 + provided + org.slf4j 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 60aeb1c427..20da30f586 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 @@ -302,6 +302,7 @@ public static class EventType { public static final String VIEW = "VIEW"; public static final String MASS_SEND_JOB_FINISH = "MASSSENDJOBFINISH"; + public static final String SYS_APPROVAL_CHANGE = "sys_approval_change"; /** * 扫码推事件的事件推送 */ @@ -431,7 +432,7 @@ public static class EventType { */ public static final String WEAPP_AUDIT_FAIL = "weapp_audit_fail"; - + /** * 小程序审核事件:审核延后 */ @@ -622,4 +623,19 @@ public static class AppIdType { */ public static final String MINI_TYPE = "mini"; } + + /** + * 新建文章类型 + */ + @UtilityClass + public static class ArticleType { + /** + * 图文消息 + */ + public static final String NEWS = "news"; + /** + * 图片消息 + */ + public static final String NEWS_PIC = "newspic"; + } } diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/result/WxMinishopImageUploadCustomizeResult.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/result/WxMinishopImageUploadCustomizeResult.java index cd700be7c1..5427d5cada 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/result/WxMinishopImageUploadCustomizeResult.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/result/WxMinishopImageUploadCustomizeResult.java @@ -16,7 +16,7 @@ public class WxMinishopImageUploadCustomizeResult implements Serializable { private WxMinishopPicFileCustomizeResult imgInfo; public static WxMinishopImageUploadCustomizeResult fromJson(String json) { - JsonObject jsonObject = new JsonParser().parse(json).getAsJsonObject(); + JsonObject jsonObject = JsonParser.parseString(json).getAsJsonObject(); WxMinishopImageUploadCustomizeResult result = new WxMinishopImageUploadCustomizeResult(); result.setErrcode(jsonObject.get(WxConsts.ERR_CODE).getAsNumber().toString()); if (result.getErrcode().equals("0")) { diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/result/WxMinishopImageUploadResult.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/result/WxMinishopImageUploadResult.java index 324232d0ee..9c2cbaf3ba 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/result/WxMinishopImageUploadResult.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/result/WxMinishopImageUploadResult.java @@ -21,7 +21,7 @@ public class WxMinishopImageUploadResult implements Serializable { public static WxMinishopImageUploadResult fromJson(String json) { - JsonObject jsonObject = new JsonParser().parse(json).getAsJsonObject(); + JsonObject jsonObject = JsonParser.parseString(json).getAsJsonObject(); WxMinishopImageUploadResult result = new WxMinishopImageUploadResult(); result.setErrcode(jsonObject.get(WxConsts.ERR_CODE).getAsNumber().toString()); if (result.getErrcode().equals("0")) { diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/executor/CommonUploadRequestExecutor.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/executor/CommonUploadRequestExecutor.java index 2c9a4d7526..a93cbe1e99 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/executor/CommonUploadRequestExecutor.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/executor/CommonUploadRequestExecutor.java @@ -1,11 +1,15 @@ package me.chanjar.weixin.common.executor; +import jodd.http.HttpConnectionProvider; +import jodd.http.ProxyInfo; import me.chanjar.weixin.common.bean.CommonUploadParam; import me.chanjar.weixin.common.enums.WxType; import me.chanjar.weixin.common.error.WxErrorException; import me.chanjar.weixin.common.util.http.RequestExecutor; import me.chanjar.weixin.common.util.http.RequestHttp; import me.chanjar.weixin.common.util.http.ResponseHandler; +import me.chanjar.weixin.common.util.http.okhttp.OkHttpProxyInfo; +import okhttp3.OkHttpClient; import java.io.IOException; @@ -34,15 +38,19 @@ public void execute(String uri, CommonUploadParam data, ResponseHandler * @param requestHttp 请求信息 * @return 执行器 */ - @SuppressWarnings({"rawtypes", "unchecked"}) - public static RequestExecutor create(RequestHttp requestHttp) { + @SuppressWarnings("unchecked") + public static RequestExecutor create(RequestHttp requestHttp) { switch (requestHttp.getRequestType()) { case APACHE_HTTP: - return new CommonUploadRequestExecutorApacheImpl(requestHttp); + return new CommonUploadRequestExecutorApacheImpl( + (RequestHttp) requestHttp); case JODD_HTTP: - return new CommonUploadRequestExecutorJoddHttpImpl(requestHttp); + return new CommonUploadRequestExecutorJoddHttpImpl((RequestHttp) requestHttp); case OK_HTTP: - return new CommonUploadRequestExecutorOkHttpImpl(requestHttp); + return new CommonUploadRequestExecutorOkHttpImpl((RequestHttp) requestHttp); + case HTTP_COMPONENTS: + return new CommonUploadRequestExecutorHttpComponentsImpl( + (RequestHttp) requestHttp); default: throw new IllegalArgumentException("不支持的http执行器类型:" + requestHttp.getRequestType()); } diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/executor/CommonUploadRequestExecutorApacheImpl.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/executor/CommonUploadRequestExecutorApacheImpl.java index 6a3c05dd21..7f19241cdb 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/executor/CommonUploadRequestExecutorApacheImpl.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/executor/CommonUploadRequestExecutorApacheImpl.java @@ -8,10 +8,10 @@ import me.chanjar.weixin.common.error.WxErrorException; import me.chanjar.weixin.common.util.http.RequestHttp; import me.chanjar.weixin.common.util.http.apache.Utf8ResponseHandler; +import org.apache.commons.lang3.StringUtils; import org.apache.http.HttpEntity; import org.apache.http.HttpHost; import org.apache.http.client.config.RequestConfig; -import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpPost; import org.apache.http.entity.ContentType; import org.apache.http.entity.mime.HttpMultipartMode; @@ -28,8 +28,7 @@ * @author 广州跨界 * created on 2024/01/11 */ -public class CommonUploadRequestExecutorApacheImpl - extends CommonUploadRequestExecutor { +public class CommonUploadRequestExecutorApacheImpl extends CommonUploadRequestExecutor { public CommonUploadRequestExecutorApacheImpl(RequestHttp requestHttp) { super(requestHttp); @@ -52,19 +51,15 @@ public String execute(String uri, CommonUploadParam param, WxType wxType) throws .build(); httpPost.setEntity(entity); } - try (CloseableHttpResponse response = requestHttp.getRequestHttpClient().execute(httpPost)) { - String responseContent = Utf8ResponseHandler.INSTANCE.handleResponse(response); - if (responseContent == null || responseContent.isEmpty()) { - throw new WxErrorException(String.format("上传失败,服务器响应空 url:%s param:%s", uri, param)); - } - WxError error = WxError.fromJson(responseContent, wxType); - if (error.getErrorCode() != 0) { - throw new WxErrorException(error); - } - return responseContent; - } finally { - httpPost.releaseConnection(); + String responseContent = requestHttp.getRequestHttpClient().execute(httpPost, Utf8ResponseHandler.INSTANCE); + if (StringUtils.isEmpty(responseContent)) { + throw new WxErrorException(String.format("上传失败,服务器响应空 url:%s param:%s", uri, param)); } + WxError error = WxError.fromJson(responseContent, wxType); + if (error.getErrorCode() != 0) { + throw new WxErrorException(error); + } + return responseContent; } /** diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/executor/CommonUploadRequestExecutorHttpComponentsImpl.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/executor/CommonUploadRequestExecutorHttpComponentsImpl.java new file mode 100644 index 0000000000..f79eaa49b8 --- /dev/null +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/executor/CommonUploadRequestExecutorHttpComponentsImpl.java @@ -0,0 +1,75 @@ +package me.chanjar.weixin.common.executor; + +import lombok.Getter; +import me.chanjar.weixin.common.bean.CommonUploadData; +import me.chanjar.weixin.common.bean.CommonUploadParam; +import me.chanjar.weixin.common.enums.WxType; +import me.chanjar.weixin.common.error.WxError; +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.common.util.http.RequestHttp; +import me.chanjar.weixin.common.util.http.hc.Utf8ResponseHandler; +import org.apache.commons.lang3.StringUtils; +import org.apache.hc.client5.http.classic.methods.HttpPost; +import org.apache.hc.client5.http.config.RequestConfig; +import org.apache.hc.client5.http.entity.mime.HttpMultipartMode; +import org.apache.hc.client5.http.entity.mime.InputStreamBody; +import org.apache.hc.client5.http.entity.mime.MultipartEntityBuilder; +import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; +import org.apache.hc.core5.http.ContentType; +import org.apache.hc.core5.http.HttpEntity; +import org.apache.hc.core5.http.HttpHost; + +import java.io.IOException; +import java.io.InputStream; + +/** + * Apache HttpComponents 通用文件上传器 + */ +public class CommonUploadRequestExecutorHttpComponentsImpl extends CommonUploadRequestExecutor { + + public CommonUploadRequestExecutorHttpComponentsImpl(RequestHttp requestHttp) { + super(requestHttp); + } + + @Override + public String execute(String uri, CommonUploadParam param, WxType wxType) throws WxErrorException, IOException { + HttpPost httpPost = new HttpPost(uri); + if (requestHttp.getRequestHttpProxy() != null) { + RequestConfig config = RequestConfig.custom().setProxy(requestHttp.getRequestHttpProxy()).build(); + httpPost.setConfig(config); + } + if (param != null) { + CommonUploadData data = param.getData(); + InnerStreamBody part = new InnerStreamBody(data.getInputStream(), ContentType.DEFAULT_BINARY, data.getFileName(), data.getLength()); + HttpEntity entity = MultipartEntityBuilder + .create() + .addPart(param.getName(), part) + .setMode(HttpMultipartMode.EXTENDED) + .build(); + httpPost.setEntity(entity); + } + String responseContent = requestHttp.getRequestHttpClient().execute(httpPost, Utf8ResponseHandler.INSTANCE); + if (StringUtils.isEmpty(responseContent)) { + throw new WxErrorException(String.format("上传失败,服务器响应空 url:%s param:%s", uri, param)); + } + WxError error = WxError.fromJson(responseContent, wxType); + if (error.getErrorCode() != 0) { + throw new WxErrorException(error); + } + return responseContent; + } + + /** + * 内部流 请求体 + */ + @Getter + public static class InnerStreamBody extends InputStreamBody { + + private final long contentLength; + + public InnerStreamBody(final InputStream in, final ContentType contentType, final String filename, long contentLength) { + super(in, contentType, filename); + this.contentLength = contentLength; + } + } +} diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/requestexecuter/ocr/OcrDiscernApacheHttpRequestExecutor.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/requestexecuter/ocr/OcrDiscernApacheHttpRequestExecutor.java index 22cdab3f92..03bec013dd 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/requestexecuter/ocr/OcrDiscernApacheHttpRequestExecutor.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/requestexecuter/ocr/OcrDiscernApacheHttpRequestExecutor.java @@ -8,7 +8,6 @@ import org.apache.http.HttpEntity; import org.apache.http.HttpHost; import org.apache.http.client.config.RequestConfig; -import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpPost; import org.apache.http.entity.mime.HttpMultipartMode; import org.apache.http.entity.mime.MultipartEntityBuilder; @@ -24,7 +23,7 @@ * created on 2019/6/27 14:06 */ public class OcrDiscernApacheHttpRequestExecutor extends OcrDiscernRequestExecutor { - public OcrDiscernApacheHttpRequestExecutor(RequestHttp requestHttp) { + public OcrDiscernApacheHttpRequestExecutor(RequestHttp requestHttp) { super(requestHttp); } @@ -43,15 +42,11 @@ public String execute(String uri, File file, WxType wxType) throws WxErrorExcept .build(); httpPost.setEntity(entity); } - try (CloseableHttpResponse response = requestHttp.getRequestHttpClient().execute(httpPost)) { - String responseContent = Utf8ResponseHandler.INSTANCE.handleResponse(response); - WxError error = WxError.fromJson(responseContent, wxType); - if (error.getErrorCode() != 0) { - throw new WxErrorException(error); - } - return responseContent; - } finally { - httpPost.releaseConnection(); + String responseContent = requestHttp.getRequestHttpClient().execute(httpPost, Utf8ResponseHandler.INSTANCE); + WxError error = WxError.fromJson(responseContent, wxType); + if (error.getErrorCode() != 0) { + throw new WxErrorException(error); } + return responseContent; } } diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/requestexecuter/ocr/OcrDiscernHttpComponentsRequestExecutor.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/requestexecuter/ocr/OcrDiscernHttpComponentsRequestExecutor.java new file mode 100644 index 0000000000..2d02c965a8 --- /dev/null +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/requestexecuter/ocr/OcrDiscernHttpComponentsRequestExecutor.java @@ -0,0 +1,46 @@ +package me.chanjar.weixin.common.requestexecuter.ocr; + +import me.chanjar.weixin.common.enums.WxType; +import me.chanjar.weixin.common.error.WxError; +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.common.util.http.RequestHttp; +import me.chanjar.weixin.common.util.http.hc.Utf8ResponseHandler; +import org.apache.hc.client5.http.classic.methods.HttpPost; +import org.apache.hc.client5.http.config.RequestConfig; +import org.apache.hc.client5.http.entity.mime.HttpMultipartMode; +import org.apache.hc.client5.http.entity.mime.MultipartEntityBuilder; +import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; +import org.apache.hc.core5.http.HttpEntity; +import org.apache.hc.core5.http.HttpHost; + +import java.io.File; +import java.io.IOException; + +public class OcrDiscernHttpComponentsRequestExecutor extends OcrDiscernRequestExecutor { + public OcrDiscernHttpComponentsRequestExecutor(RequestHttp requestHttp) { + super(requestHttp); + } + + @Override + public String execute(String uri, File file, WxType wxType) throws WxErrorException, IOException { + HttpPost httpPost = new HttpPost(uri); + if (requestHttp.getRequestHttpProxy() != null) { + RequestConfig config = RequestConfig.custom().setProxy(requestHttp.getRequestHttpProxy()).build(); + httpPost.setConfig(config); + } + if (file != null) { + HttpEntity entity = MultipartEntityBuilder + .create() + .addBinaryBody("file", file) + .setMode(HttpMultipartMode.EXTENDED) + .build(); + httpPost.setEntity(entity); + } + String responseContent = requestHttp.getRequestHttpClient().execute(httpPost, Utf8ResponseHandler.INSTANCE); + WxError error = WxError.fromJson(responseContent, wxType); + if (error.getErrorCode() != 0) { + throw new WxErrorException(error); + } + return responseContent; + } +} diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/requestexecuter/ocr/OcrDiscernRequestExecutor.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/requestexecuter/ocr/OcrDiscernRequestExecutor.java index 870f77d2ed..542ab4a378 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/requestexecuter/ocr/OcrDiscernRequestExecutor.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/requestexecuter/ocr/OcrDiscernRequestExecutor.java @@ -5,6 +5,8 @@ import me.chanjar.weixin.common.util.http.RequestExecutor; import me.chanjar.weixin.common.util.http.RequestHttp; import me.chanjar.weixin.common.util.http.ResponseHandler; +import org.apache.http.HttpHost; +import org.apache.http.impl.client.CloseableHttpClient; import java.io.File; import java.io.IOException; @@ -18,7 +20,7 @@ public abstract class OcrDiscernRequestExecutor implements RequestExecutor { protected RequestHttp requestHttp; - public OcrDiscernRequestExecutor(RequestHttp requestHttp) { + public OcrDiscernRequestExecutor(RequestHttp requestHttp) { this.requestHttp = requestHttp; } @@ -27,12 +29,17 @@ public void execute(String uri, File data, ResponseHandler handler, WxTy handler.handle(this.execute(uri, data, wxType)); } - public static RequestExecutor create(RequestHttp requestHttp) { + @SuppressWarnings("unchecked") + public static RequestExecutor create(RequestHttp requestHttp) { switch (requestHttp.getRequestType()) { case APACHE_HTTP: - return new OcrDiscernApacheHttpRequestExecutor(requestHttp); + return new OcrDiscernApacheHttpRequestExecutor( + (RequestHttp) requestHttp); + case HTTP_COMPONENTS: + return new OcrDiscernHttpComponentsRequestExecutor( + (RequestHttp) requestHttp); default: - return null; + throw new IllegalArgumentException("不支持的http执行器类型:" + requestHttp.getRequestType()); } } } diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/BeanUtils.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/BeanUtils.java index 73b6cff368..d3f8d00406 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/BeanUtils.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/BeanUtils.java @@ -2,7 +2,6 @@ import com.google.common.collect.Lists; import me.chanjar.weixin.common.annotation.Required; -import me.chanjar.weixin.common.error.WxError; import me.chanjar.weixin.common.error.WxErrorException; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/DataUtils.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/DataUtils.java index 983d9a668f..b8fb42e0e9 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/DataUtils.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/DataUtils.java @@ -1,5 +1,6 @@ package me.chanjar.weixin.common.util; +import org.apache.commons.lang3.RegExUtils; import org.apache.commons.lang3.StringUtils; /** @@ -17,7 +18,7 @@ public class DataUtils { public static E handleDataWithSecret(E data) { E dataForLog = data; if(data instanceof String && StringUtils.contains((String)data, "&secret=")){ - dataForLog = (E) StringUtils.replaceAll((String)data,"&secret=\\w+&","&secret=******&"); + dataForLog = (E) RegExUtils.replaceAll((String)data,"&secret=\\w+&","&secret=******&"); } return dataForLog; } diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/XmlUtils.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/XmlUtils.java index facf564e32..67faf319f4 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/XmlUtils.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/XmlUtils.java @@ -65,7 +65,7 @@ private static Object element2MapOrString(Element element) { final List names = names(nodes); // 判断节点下有无非文本节点(非Text和CDATA),如无,直接取Text文本内容 - if (names.size() < 1) { + if (names.isEmpty()) { return element.getText(); } 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 f02d087b7d..0a40d0e93c 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 @@ -1,6 +1,5 @@ package me.chanjar.weixin.common.util.crypto; -import com.google.common.base.CharMatcher; import lombok.AllArgsConstructor; import lombok.Data; import me.chanjar.weixin.common.error.WxRuntimeException; diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/BaseMediaDownloadRequestExecutor.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/BaseMediaDownloadRequestExecutor.java index ed5ec17bc9..8304742524 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/BaseMediaDownloadRequestExecutor.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/BaseMediaDownloadRequestExecutor.java @@ -1,13 +1,18 @@ package me.chanjar.weixin.common.util.http; -import java.io.File; -import java.io.IOException; - +import jodd.http.HttpConnectionProvider; +import jodd.http.ProxyInfo; import me.chanjar.weixin.common.enums.WxType; import me.chanjar.weixin.common.error.WxErrorException; import me.chanjar.weixin.common.util.http.apache.ApacheMediaDownloadRequestExecutor; +import me.chanjar.weixin.common.util.http.hc.HttpComponentsMediaDownloadRequestExecutor; import me.chanjar.weixin.common.util.http.jodd.JoddHttpMediaDownloadRequestExecutor; import me.chanjar.weixin.common.util.http.okhttp.OkHttpMediaDownloadRequestExecutor; +import me.chanjar.weixin.common.util.http.okhttp.OkHttpProxyInfo; +import okhttp3.OkHttpClient; + +import java.io.File; +import java.io.IOException; /** * 下载媒体文件请求执行器. @@ -30,16 +35,21 @@ public void execute(String uri, String data, ResponseHandler handler, WxTy handler.handle(this.execute(uri, data, wxType)); } - public static RequestExecutor create(RequestHttp requestHttp, File tmpDirFile) { + @SuppressWarnings("unchecked") + public static RequestExecutor create(RequestHttp requestHttp, File tmpDirFile) { switch (requestHttp.getRequestType()) { case APACHE_HTTP: - return new ApacheMediaDownloadRequestExecutor(requestHttp, tmpDirFile); + return new ApacheMediaDownloadRequestExecutor( + (RequestHttp) requestHttp, tmpDirFile); case JODD_HTTP: - return new JoddHttpMediaDownloadRequestExecutor(requestHttp, tmpDirFile); + return new JoddHttpMediaDownloadRequestExecutor((RequestHttp) requestHttp, tmpDirFile); case OK_HTTP: - return new OkHttpMediaDownloadRequestExecutor(requestHttp, tmpDirFile); + return new OkHttpMediaDownloadRequestExecutor((RequestHttp) requestHttp, tmpDirFile); + case HTTP_COMPONENTS: + return new HttpComponentsMediaDownloadRequestExecutor( + (RequestHttp) requestHttp, tmpDirFile); default: - return null; + throw new IllegalArgumentException("不支持的http执行器类型:" + requestHttp.getRequestType()); } } diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/HttpType.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/HttpClientType.java similarity index 59% rename from weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/HttpType.java rename to weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/HttpClientType.java index eff5907f7a..a4e22be9b4 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/HttpType.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/HttpClientType.java @@ -3,17 +3,21 @@ /** * Created by ecoolper on 2017/4/28. */ -public enum HttpType { +public enum HttpClientType { /** * jodd-http. */ JODD_HTTP, /** - * apache httpclient. + * apache httpclient 4.x. */ APACHE_HTTP, /** * okhttp. */ - OK_HTTP + OK_HTTP, + /** + * apache httpclient 5.x. + */ + HTTP_COMPONENTS } diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/HttpResponseProxy.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/HttpResponseProxy.java index 11b1209460..e45294b503 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/HttpResponseProxy.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/HttpResponseProxy.java @@ -1,10 +1,10 @@ package me.chanjar.weixin.common.util.http; -import jodd.http.HttpResponse; import me.chanjar.weixin.common.error.WxErrorException; -import okhttp3.Response; -import org.apache.http.Header; -import org.apache.http.client.methods.CloseableHttpResponse; +import me.chanjar.weixin.common.util.http.apache.ApacheHttpResponseProxy; +import me.chanjar.weixin.common.util.http.hc.HttpComponentsResponseProxy; +import me.chanjar.weixin.common.util.http.jodd.JoddHttpResponseProxy; +import me.chanjar.weixin.common.util.http.okhttp.OkHttpResponseProxy; import java.io.UnsupportedEncodingException; import java.net.URLDecoder; @@ -14,68 +14,33 @@ /** *
- * 三种http框架的response代理类,方便提取公共方法
+ * http 框架的 response 代理类,方便提取公共方法
  * Created by Binary Wang on 2017-8-3.
  * 
* * @author Binary Wang */ -public class HttpResponseProxy { +public interface HttpResponseProxy { - private CloseableHttpResponse apacheHttpResponse; - private HttpResponse joddHttpResponse; - private Response okHttpResponse; - - public HttpResponseProxy(CloseableHttpResponse apacheHttpResponse) { - this.apacheHttpResponse = apacheHttpResponse; - } - - public HttpResponseProxy(HttpResponse joddHttpResponse) { - this.joddHttpResponse = joddHttpResponse; + static ApacheHttpResponseProxy from(org.apache.http.client.methods.CloseableHttpResponse response) { + return new ApacheHttpResponseProxy(response); } - public HttpResponseProxy(Response okHttpResponse) { - this.okHttpResponse = okHttpResponse; - } - - public String getFileName() throws WxErrorException { - //由于对象只能由一个构造方法实现,因此三个response对象必定且只有一个不为空 - if (this.apacheHttpResponse != null) { - return this.getFileName(this.apacheHttpResponse); - } - - if (this.joddHttpResponse != null) { - return this.getFileName(this.joddHttpResponse); - } - - if (this.okHttpResponse != null) { - return this.getFileName(this.okHttpResponse); - } - - //cannot happen - return null; + static HttpComponentsResponseProxy from(org.apache.hc.client5.http.impl.classic.CloseableHttpResponse response) { + return new HttpComponentsResponseProxy(response); } - private String getFileName(CloseableHttpResponse response) throws WxErrorException { - Header[] contentDispositionHeader = response.getHeaders("Content-disposition"); - if (contentDispositionHeader == null || contentDispositionHeader.length == 0) { - throw new WxErrorException("无法获取到文件名,Content-disposition为空"); - } - - return extractFileNameFromContentString(contentDispositionHeader[0].getValue()); + static JoddHttpResponseProxy from(jodd.http.HttpResponse response) { + return new JoddHttpResponseProxy(response); } - private String getFileName(HttpResponse response) throws WxErrorException { - String content = response.header("Content-disposition"); - return extractFileNameFromContentString(content); + static OkHttpResponseProxy from(okhttp3.Response response) { + return new OkHttpResponseProxy(response); } - private String getFileName(Response response) throws WxErrorException { - String content = response.header("Content-disposition"); - return extractFileNameFromContentString(content); - } + String getFileName() throws WxErrorException; - public static String extractFileNameFromContentString(String content) throws WxErrorException { + static String extractFileNameFromContentString(String content) throws WxErrorException { if (content == null || content.isEmpty()) { throw new WxErrorException("无法获取到文件名,content为空"); } diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/MediaInputStreamUploadRequestExecutor.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/MediaInputStreamUploadRequestExecutor.java index de4be21709..22c426ca54 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/MediaInputStreamUploadRequestExecutor.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/MediaInputStreamUploadRequestExecutor.java @@ -1,11 +1,16 @@ package me.chanjar.weixin.common.util.http; +import jodd.http.HttpConnectionProvider; +import jodd.http.ProxyInfo; import me.chanjar.weixin.common.bean.result.WxMediaUploadResult; import me.chanjar.weixin.common.enums.WxType; import me.chanjar.weixin.common.error.WxErrorException; import me.chanjar.weixin.common.util.http.apache.ApacheMediaInputStreamUploadRequestExecutor; +import me.chanjar.weixin.common.util.http.hc.HttpComponentsMediaInputStreamUploadRequestExecutor; import me.chanjar.weixin.common.util.http.jodd.JoddHttpMediaInputStreamUploadRequestExecutor; import me.chanjar.weixin.common.util.http.okhttp.OkHttpMediaInputStreamUploadRequestExecutor; +import me.chanjar.weixin.common.util.http.okhttp.OkHttpProxyInfo; +import okhttp3.OkHttpClient; import java.io.IOException; @@ -18,7 +23,7 @@ public abstract class MediaInputStreamUploadRequestExecutor implements RequestExecutor { protected RequestHttp requestHttp; - public MediaInputStreamUploadRequestExecutor(RequestHttp requestHttp) { + public MediaInputStreamUploadRequestExecutor(RequestHttp requestHttp) { this.requestHttp = requestHttp; } @@ -27,16 +32,21 @@ public void execute(String uri, InputStreamData data, ResponseHandler create(RequestHttp requestHttp) { + @SuppressWarnings("unchecked") + public static RequestExecutor create(RequestHttp requestHttp) { switch (requestHttp.getRequestType()) { case APACHE_HTTP: - return new ApacheMediaInputStreamUploadRequestExecutor(requestHttp); + return new ApacheMediaInputStreamUploadRequestExecutor( + (RequestHttp) requestHttp); case JODD_HTTP: - return new JoddHttpMediaInputStreamUploadRequestExecutor(requestHttp); + return new JoddHttpMediaInputStreamUploadRequestExecutor((RequestHttp) requestHttp); case OK_HTTP: - return new OkHttpMediaInputStreamUploadRequestExecutor(requestHttp); + return new OkHttpMediaInputStreamUploadRequestExecutor((RequestHttp) requestHttp); + case HTTP_COMPONENTS: + return new HttpComponentsMediaInputStreamUploadRequestExecutor( + (RequestHttp) requestHttp); default: - return null; + throw new IllegalArgumentException("不支持的http执行器类型:" + requestHttp.getRequestType()); } } diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/MediaUploadRequestExecutor.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/MediaUploadRequestExecutor.java index 83d0c099b3..2d16e714e9 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/MediaUploadRequestExecutor.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/MediaUploadRequestExecutor.java @@ -1,13 +1,18 @@ package me.chanjar.weixin.common.util.http; +import jodd.http.HttpConnectionProvider; +import jodd.http.ProxyInfo; import me.chanjar.weixin.common.bean.CommonUploadParam; import me.chanjar.weixin.common.bean.result.WxMediaUploadResult; import me.chanjar.weixin.common.enums.WxType; import me.chanjar.weixin.common.error.WxErrorException; import me.chanjar.weixin.common.service.WxService; import me.chanjar.weixin.common.util.http.apache.ApacheMediaUploadRequestExecutor; +import me.chanjar.weixin.common.util.http.hc.HttpComponentsMediaUploadRequestExecutor; import me.chanjar.weixin.common.util.http.jodd.JoddHttpMediaUploadRequestExecutor; import me.chanjar.weixin.common.util.http.okhttp.OkHttpMediaUploadRequestExecutor; +import me.chanjar.weixin.common.util.http.okhttp.OkHttpProxyInfo; +import okhttp3.OkHttpClient; import java.io.File; import java.io.IOException; @@ -25,7 +30,7 @@ public abstract class MediaUploadRequestExecutor implements RequestExecutor { protected RequestHttp requestHttp; - public MediaUploadRequestExecutor(RequestHttp requestHttp) { + public MediaUploadRequestExecutor(RequestHttp requestHttp) { this.requestHttp = requestHttp; } @@ -34,16 +39,21 @@ public void execute(String uri, File data, ResponseHandler handler.handle(this.execute(uri, data, wxType)); } - public static RequestExecutor create(RequestHttp requestHttp) { + @SuppressWarnings("unchecked") + public static RequestExecutor create(RequestHttp requestHttp) { switch (requestHttp.getRequestType()) { case APACHE_HTTP: - return new ApacheMediaUploadRequestExecutor(requestHttp); + return new ApacheMediaUploadRequestExecutor( + (RequestHttp) requestHttp); case JODD_HTTP: - return new JoddHttpMediaUploadRequestExecutor(requestHttp); + return new JoddHttpMediaUploadRequestExecutor((RequestHttp) requestHttp); case OK_HTTP: - return new OkHttpMediaUploadRequestExecutor(requestHttp); + return new OkHttpMediaUploadRequestExecutor((RequestHttp) requestHttp); + case HTTP_COMPONENTS: + return new HttpComponentsMediaUploadRequestExecutor( + (RequestHttp) requestHttp); default: - return null; + throw new IllegalArgumentException("不支持的http执行器类型:" + requestHttp.getRequestType()); } } diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/MinishopUploadRequestCustomizeExecutor.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/MinishopUploadRequestCustomizeExecutor.java index e94b2d8d6a..0e8684a1db 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/MinishopUploadRequestCustomizeExecutor.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/MinishopUploadRequestCustomizeExecutor.java @@ -1,11 +1,16 @@ package me.chanjar.weixin.common.util.http; +import jodd.http.HttpConnectionProvider; +import jodd.http.ProxyInfo; import me.chanjar.weixin.common.bean.result.WxMinishopImageUploadCustomizeResult; import me.chanjar.weixin.common.enums.WxType; import me.chanjar.weixin.common.error.WxErrorException; import me.chanjar.weixin.common.util.http.apache.ApacheMinishopMediaUploadRequestCustomizeExecutor; +import me.chanjar.weixin.common.util.http.hc.HttpComponentsMinishopMediaUploadRequestCustomizeExecutor; import me.chanjar.weixin.common.util.http.jodd.JoddHttpMinishopMediaUploadRequestCustomizeExecutor; import me.chanjar.weixin.common.util.http.okhttp.OkHttpMinishopMediaUploadRequestCustomizeExecutor; +import me.chanjar.weixin.common.util.http.okhttp.OkHttpProxyInfo; +import okhttp3.OkHttpClient; import java.io.File; import java.io.IOException; @@ -16,13 +21,12 @@ public abstract class MinishopUploadRequestCustomizeExecutor implements Re protected String uploadType; protected String imgUrl; - public MinishopUploadRequestCustomizeExecutor(RequestHttp requestHttp, String respType, String imgUrl) { + public MinishopUploadRequestCustomizeExecutor(RequestHttp requestHttp, String respType, String imgUrl) { this.requestHttp = requestHttp; this.respType = respType; if (imgUrl == null || imgUrl.isEmpty()) { this.uploadType = "0"; - } - else { + } else { this.uploadType = "1"; this.imgUrl = imgUrl; } @@ -33,16 +37,21 @@ public void execute(String uri, File data, ResponseHandler create(RequestHttp requestHttp, String respType, String imgUrl) { + @SuppressWarnings("unchecked") + public static RequestExecutor create(RequestHttp requestHttp, String respType, String imgUrl) { switch (requestHttp.getRequestType()) { case APACHE_HTTP: - return new ApacheMinishopMediaUploadRequestCustomizeExecutor(requestHttp, respType, imgUrl); + return new ApacheMinishopMediaUploadRequestCustomizeExecutor( + (RequestHttp) requestHttp, respType, imgUrl); case JODD_HTTP: - return new JoddHttpMinishopMediaUploadRequestCustomizeExecutor(requestHttp, respType, imgUrl); + return new JoddHttpMinishopMediaUploadRequestCustomizeExecutor((RequestHttp) requestHttp, respType, imgUrl); case OK_HTTP: - return new OkHttpMinishopMediaUploadRequestCustomizeExecutor(requestHttp, respType, imgUrl); + return new OkHttpMinishopMediaUploadRequestCustomizeExecutor((RequestHttp) requestHttp, respType, imgUrl); + case HTTP_COMPONENTS: + return new HttpComponentsMinishopMediaUploadRequestCustomizeExecutor( + (RequestHttp) requestHttp, respType, imgUrl); default: - return null; + throw new IllegalArgumentException("不支持的http执行器类型:" + requestHttp.getRequestType()); } } } diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/MinishopUploadRequestExecutor.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/MinishopUploadRequestExecutor.java index ee4608edf3..e6018a7791 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/MinishopUploadRequestExecutor.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/MinishopUploadRequestExecutor.java @@ -1,11 +1,16 @@ package me.chanjar.weixin.common.util.http; +import jodd.http.HttpConnectionProvider; +import jodd.http.ProxyInfo; import me.chanjar.weixin.common.bean.result.WxMinishopImageUploadResult; import me.chanjar.weixin.common.enums.WxType; import me.chanjar.weixin.common.error.WxErrorException; import me.chanjar.weixin.common.util.http.apache.ApacheMinishopMediaUploadRequestExecutor; +import me.chanjar.weixin.common.util.http.hc.HttpComponentsMinishopMediaUploadRequestExecutor; import me.chanjar.weixin.common.util.http.jodd.JoddHttpMinishopMediaUploadRequestExecutor; import me.chanjar.weixin.common.util.http.okhttp.OkHttpMinishopMediaUploadRequestExecutor; +import me.chanjar.weixin.common.util.http.okhttp.OkHttpProxyInfo; +import okhttp3.OkHttpClient; import java.io.File; import java.io.IOException; @@ -13,7 +18,7 @@ public abstract class MinishopUploadRequestExecutor implements RequestExecutor { protected RequestHttp requestHttp; - public MinishopUploadRequestExecutor(RequestHttp requestHttp) { + public MinishopUploadRequestExecutor(RequestHttp requestHttp) { this.requestHttp = requestHttp; } @@ -22,16 +27,21 @@ public void execute(String uri, File data, ResponseHandler create(RequestHttp requestHttp) { + @SuppressWarnings("unchecked") + public static RequestExecutor create(RequestHttp requestHttp) { switch (requestHttp.getRequestType()) { case APACHE_HTTP: - return new ApacheMinishopMediaUploadRequestExecutor(requestHttp); + return new ApacheMinishopMediaUploadRequestExecutor( + (RequestHttp) requestHttp); case JODD_HTTP: - return new JoddHttpMinishopMediaUploadRequestExecutor(requestHttp); + return new JoddHttpMinishopMediaUploadRequestExecutor((RequestHttp) requestHttp); case OK_HTTP: - return new OkHttpMinishopMediaUploadRequestExecutor(requestHttp); + return new OkHttpMinishopMediaUploadRequestExecutor((RequestHttp) requestHttp); + case HTTP_COMPONENTS: + return new HttpComponentsMinishopMediaUploadRequestExecutor( + (RequestHttp) requestHttp); default: - return null; + throw new IllegalArgumentException("不支持的http执行器类型:" + requestHttp.getRequestType()); } } } diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/RequestHttp.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/RequestHttp.java index b7bc850f8f..36be78b8ae 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/RequestHttp.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/RequestHttp.java @@ -26,6 +26,6 @@ public interface RequestHttp { * * @return HttpType */ - HttpType getRequestType(); + HttpClientType getRequestType(); } diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/SimpleGetRequestExecutor.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/SimpleGetRequestExecutor.java index 266fd226e7..a880a9323c 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/SimpleGetRequestExecutor.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/SimpleGetRequestExecutor.java @@ -1,11 +1,16 @@ package me.chanjar.weixin.common.util.http; +import jodd.http.HttpConnectionProvider; +import jodd.http.ProxyInfo; import me.chanjar.weixin.common.enums.WxType; import me.chanjar.weixin.common.error.WxError; import me.chanjar.weixin.common.error.WxErrorException; import me.chanjar.weixin.common.util.http.apache.ApacheSimpleGetRequestExecutor; +import me.chanjar.weixin.common.util.http.hc.HttpComponentsSimpleGetRequestExecutor; import me.chanjar.weixin.common.util.http.jodd.JoddHttpSimpleGetRequestExecutor; +import me.chanjar.weixin.common.util.http.okhttp.OkHttpProxyInfo; import me.chanjar.weixin.common.util.http.okhttp.OkHttpSimpleGetRequestExecutor; +import okhttp3.OkHttpClient; import java.io.IOException; @@ -27,16 +32,21 @@ public void execute(String uri, String data, ResponseHandler handler, Wx handler.handle(this.execute(uri, data, wxType)); } + @SuppressWarnings("unchecked") public static RequestExecutor create(RequestHttp requestHttp) { switch (requestHttp.getRequestType()) { case APACHE_HTTP: - return new ApacheSimpleGetRequestExecutor(requestHttp); + return new ApacheSimpleGetRequestExecutor( + (RequestHttp) requestHttp); case JODD_HTTP: - return new JoddHttpSimpleGetRequestExecutor(requestHttp); + return new JoddHttpSimpleGetRequestExecutor((RequestHttp) requestHttp); case OK_HTTP: - return new OkHttpSimpleGetRequestExecutor(requestHttp); + return new OkHttpSimpleGetRequestExecutor((RequestHttp) requestHttp); + case HTTP_COMPONENTS: + return new HttpComponentsSimpleGetRequestExecutor( + (RequestHttp) requestHttp); default: - throw new IllegalArgumentException("非法请求参数"); + throw new IllegalArgumentException("不支持的http执行器类型:" + requestHttp.getRequestType()); } } diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/SimplePostRequestExecutor.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/SimplePostRequestExecutor.java index 0366b156af..2cc086cd0f 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/SimplePostRequestExecutor.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/SimplePostRequestExecutor.java @@ -1,11 +1,16 @@ package me.chanjar.weixin.common.util.http; +import jodd.http.HttpConnectionProvider; +import jodd.http.ProxyInfo; import me.chanjar.weixin.common.enums.WxType; import me.chanjar.weixin.common.error.WxError; import me.chanjar.weixin.common.error.WxErrorException; import me.chanjar.weixin.common.util.http.apache.ApacheSimplePostRequestExecutor; +import me.chanjar.weixin.common.util.http.hc.HttpComponentsSimplePostRequestExecutor; import me.chanjar.weixin.common.util.http.jodd.JoddHttpSimplePostRequestExecutor; +import me.chanjar.weixin.common.util.http.okhttp.OkHttpProxyInfo; import me.chanjar.weixin.common.util.http.okhttp.OkHttpSimplePostRequestExecutor; +import okhttp3.OkHttpClient; import org.jetbrains.annotations.NotNull; import java.io.IOException; @@ -18,7 +23,7 @@ public abstract class SimplePostRequestExecutor implements RequestExecutor { protected RequestHttp requestHttp; - public SimplePostRequestExecutor(RequestHttp requestHttp) { + public SimplePostRequestExecutor(RequestHttp requestHttp) { this.requestHttp = requestHttp; } @@ -28,16 +33,21 @@ public void execute(String uri, String data, ResponseHandler handler, Wx handler.handle(this.execute(uri, data, wxType)); } - public static RequestExecutor create(RequestHttp requestHttp) { + @SuppressWarnings("unchecked") + public static RequestExecutor create(RequestHttp requestHttp) { switch (requestHttp.getRequestType()) { case APACHE_HTTP: - return new ApacheSimplePostRequestExecutor(requestHttp); + return new ApacheSimplePostRequestExecutor( + (RequestHttp) requestHttp); case JODD_HTTP: - return new JoddHttpSimplePostRequestExecutor(requestHttp); + return new JoddHttpSimplePostRequestExecutor((RequestHttp) requestHttp); case OK_HTTP: - return new OkHttpSimplePostRequestExecutor(requestHttp); + return new OkHttpSimplePostRequestExecutor((RequestHttp) requestHttp); + case HTTP_COMPONENTS: + return new HttpComponentsSimplePostRequestExecutor( + (RequestHttp) requestHttp); default: - throw new IllegalArgumentException("非法请求参数"); + throw new IllegalArgumentException("不支持的http执行器类型:" + requestHttp.getRequestType()); } } diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/ApacheBasicResponseHandler.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/ApacheBasicResponseHandler.java new file mode 100644 index 0000000000..a91fc383ca --- /dev/null +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/ApacheBasicResponseHandler.java @@ -0,0 +1,9 @@ +package me.chanjar.weixin.common.util.http.apache; + +import org.apache.http.impl.client.BasicResponseHandler; + +public class ApacheBasicResponseHandler extends BasicResponseHandler { + + public static final ApacheBasicResponseHandler INSTANCE = new ApacheBasicResponseHandler(); + +} diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/ApacheHttpClientBuilder.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/ApacheHttpClientBuilder.java index 0d5073de14..de34ca5bd1 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 @@ -53,4 +53,10 @@ public interface ApacheHttpClientBuilder { * ssl连接socket工厂. */ ApacheHttpClientBuilder sslConnectionSocketFactory(SSLConnectionSocketFactory sslConnectionSocketFactory); + + /** + * 支持的TLS协议版本. + * Supported TLS protocol versions. + */ + ApacheHttpClientBuilder supportedProtocols(String[] supportedProtocols); } diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/ApacheHttpDnsClientBuilder.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/ApacheHttpDnsClientBuilder.java index 6a136600e5..b3ebf350be 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/ApacheHttpDnsClientBuilder.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/ApacheHttpDnsClientBuilder.java @@ -117,6 +117,13 @@ public ApacheHttpClientBuilder sslConnectionSocketFactory(SSLConnectionSocketFac return this; } + @Override + public ApacheHttpClientBuilder supportedProtocols(String[] supportedProtocols) { + // This implementation doesn't use the supportedProtocols parameter as it relies on the provided SSLConnectionSocketFactory + // Users should configure the SSLConnectionSocketFactory with desired protocols before setting it + return this; + } + /** * 获取链接的超时时间设置,默认3000ms *

diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/ApacheHttpResponseProxy.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/ApacheHttpResponseProxy.java new file mode 100644 index 0000000000..06439d3879 --- /dev/null +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/ApacheHttpResponseProxy.java @@ -0,0 +1,25 @@ +package me.chanjar.weixin.common.util.http.apache; + +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.common.util.http.HttpResponseProxy; +import org.apache.http.Header; +import org.apache.http.client.methods.CloseableHttpResponse; + +public class ApacheHttpResponseProxy implements HttpResponseProxy { + + private final CloseableHttpResponse httpResponse; + + public ApacheHttpResponseProxy(CloseableHttpResponse closeableHttpResponse) { + this.httpResponse = closeableHttpResponse; + } + + @Override + public String getFileName() throws WxErrorException { + Header[] contentDispositionHeader = this.httpResponse.getHeaders("Content-disposition"); + if (contentDispositionHeader == null || contentDispositionHeader.length == 0) { + throw new WxErrorException("无法获取到文件名,Content-disposition为空"); + } + + return HttpResponseProxy.extractFileNameFromContentString(contentDispositionHeader[0].getValue()); + } +} diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/ApacheMediaDownloadRequestExecutor.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/ApacheMediaDownloadRequestExecutor.java index e2f4611439..554dc8df7b 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/ApacheMediaDownloadRequestExecutor.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/ApacheMediaDownloadRequestExecutor.java @@ -28,7 +28,7 @@ * created on 2017/5/5 */ public class ApacheMediaDownloadRequestExecutor extends BaseMediaDownloadRequestExecutor { - public ApacheMediaDownloadRequestExecutor(RequestHttp requestHttp, File tmpDirFile) { + public ApacheMediaDownloadRequestExecutor(RequestHttp requestHttp, File tmpDirFile) { super(requestHttp, tmpDirFile); } @@ -58,7 +58,7 @@ public File execute(String uri, String queryParam, WxType wxType) throws WxError } } - String fileName = new HttpResponseProxy(response).getFileName(); + String fileName = HttpResponseProxy.from(response).getFileName(); if (StringUtils.isBlank(fileName)) { fileName = String.valueOf(System.currentTimeMillis()); } @@ -68,11 +68,7 @@ public File execute(String uri, String queryParam, WxType wxType) throws WxError baseName = String.valueOf(System.currentTimeMillis()); } - return FileUtils.createTmpFile(inputStream, baseName, FilenameUtils.getExtension(fileName), - super.tmpDirFile); - - } finally { - httpGet.releaseConnection(); + return FileUtils.createTmpFile(inputStream, baseName, FilenameUtils.getExtension(fileName), super.tmpDirFile); } } diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/ApacheMediaInputStreamUploadRequestExecutor.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/ApacheMediaInputStreamUploadRequestExecutor.java index ef09812cb2..43a5d604b0 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/ApacheMediaInputStreamUploadRequestExecutor.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/ApacheMediaInputStreamUploadRequestExecutor.java @@ -10,7 +10,6 @@ import org.apache.http.HttpEntity; import org.apache.http.HttpHost; import org.apache.http.client.config.RequestConfig; -import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpPost; import org.apache.http.entity.ContentType; import org.apache.http.entity.mime.HttpMultipartMode; @@ -26,7 +25,7 @@ * created on 2022/02/15 */ public class ApacheMediaInputStreamUploadRequestExecutor extends MediaInputStreamUploadRequestExecutor { - public ApacheMediaInputStreamUploadRequestExecutor(RequestHttp requestHttp) { + public ApacheMediaInputStreamUploadRequestExecutor(RequestHttp requestHttp) { super(requestHttp); } @@ -45,15 +44,11 @@ public WxMediaUploadResult execute(String uri, InputStreamData data, WxType wxTy .build(); httpPost.setEntity(entity); } - try (CloseableHttpResponse response = requestHttp.getRequestHttpClient().execute(httpPost)) { - String responseContent = Utf8ResponseHandler.INSTANCE.handleResponse(response); - WxError error = WxError.fromJson(responseContent, wxType); - if (error.getErrorCode() != 0) { - throw new WxErrorException(error); - } - return WxMediaUploadResult.fromJson(responseContent); - } finally { - httpPost.releaseConnection(); + String responseContent = requestHttp.getRequestHttpClient().execute(httpPost, Utf8ResponseHandler.INSTANCE); + WxError error = WxError.fromJson(responseContent, wxType); + if (error.getErrorCode() != 0) { + throw new WxErrorException(error); } + return WxMediaUploadResult.fromJson(responseContent); } } diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/ApacheMediaUploadRequestExecutor.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/ApacheMediaUploadRequestExecutor.java index ca33b8641f..5d3eae174f 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/ApacheMediaUploadRequestExecutor.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/ApacheMediaUploadRequestExecutor.java @@ -1,7 +1,7 @@ package me.chanjar.weixin.common.util.http.apache; -import me.chanjar.weixin.common.enums.WxType; import me.chanjar.weixin.common.bean.result.WxMediaUploadResult; +import me.chanjar.weixin.common.enums.WxType; import me.chanjar.weixin.common.error.WxError; import me.chanjar.weixin.common.error.WxErrorException; import me.chanjar.weixin.common.util.http.MediaUploadRequestExecutor; @@ -9,7 +9,6 @@ import org.apache.http.HttpEntity; import org.apache.http.HttpHost; import org.apache.http.client.config.RequestConfig; -import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpPost; import org.apache.http.entity.mime.HttpMultipartMode; import org.apache.http.entity.mime.MultipartEntityBuilder; @@ -22,7 +21,7 @@ * Created by ecoolper on 2017/5/5. */ public class ApacheMediaUploadRequestExecutor extends MediaUploadRequestExecutor { - public ApacheMediaUploadRequestExecutor(RequestHttp requestHttp) { + public ApacheMediaUploadRequestExecutor(RequestHttp requestHttp) { super(requestHttp); } @@ -41,15 +40,11 @@ public WxMediaUploadResult execute(String uri, File file, WxType wxType) throws .build(); httpPost.setEntity(entity); } - try (CloseableHttpResponse response = requestHttp.getRequestHttpClient().execute(httpPost)) { - String responseContent = Utf8ResponseHandler.INSTANCE.handleResponse(response); - WxError error = WxError.fromJson(responseContent, wxType); - if (error.getErrorCode() != 0) { - throw new WxErrorException(error); - } - return WxMediaUploadResult.fromJson(responseContent); - } finally { - httpPost.releaseConnection(); + String responseContent = requestHttp.getRequestHttpClient().execute(httpPost, Utf8ResponseHandler.INSTANCE); + WxError error = WxError.fromJson(responseContent, wxType); + if (error.getErrorCode() != 0) { + throw new WxErrorException(error); } + return WxMediaUploadResult.fromJson(responseContent); } } diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/ApacheMinishopMediaUploadRequestCustomizeExecutor.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/ApacheMinishopMediaUploadRequestCustomizeExecutor.java index 9af02af5d0..48fafc3401 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/ApacheMinishopMediaUploadRequestCustomizeExecutor.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/ApacheMinishopMediaUploadRequestCustomizeExecutor.java @@ -10,7 +10,6 @@ import org.apache.http.HttpEntity; import org.apache.http.HttpHost; import org.apache.http.client.config.RequestConfig; -import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpPost; import org.apache.http.entity.mime.HttpMultipartMode; import org.apache.http.entity.mime.MultipartEntityBuilder; @@ -24,7 +23,7 @@ */ @Slf4j public class ApacheMinishopMediaUploadRequestCustomizeExecutor extends MinishopUploadRequestCustomizeExecutor { - public ApacheMinishopMediaUploadRequestCustomizeExecutor(RequestHttp requestHttp, String respType, String imgUrl) { + public ApacheMinishopMediaUploadRequestCustomizeExecutor(RequestHttp requestHttp, String respType, String imgUrl) { super(requestHttp, respType, imgUrl); } @@ -58,16 +57,12 @@ public WxMinishopImageUploadCustomizeResult execute(String uri, File file, WxTyp .build(); httpPost.setEntity(entity); } - try (CloseableHttpResponse response = requestHttp.getRequestHttpClient().execute(httpPost)) { - String responseContent = Utf8ResponseHandler.INSTANCE.handleResponse(response); - WxError error = WxError.fromJson(responseContent, wxType); - if (error.getErrorCode() != 0) { - throw new WxErrorException(error); - } - log.info("responseContent: " + responseContent); - return WxMinishopImageUploadCustomizeResult.fromJson(responseContent); - } finally { - httpPost.releaseConnection(); + String responseContent = requestHttp.getRequestHttpClient().execute(httpPost, Utf8ResponseHandler.INSTANCE); + WxError error = WxError.fromJson(responseContent, wxType); + if (error.getErrorCode() != 0) { + throw new WxErrorException(error); } + log.info("responseContent: {}", responseContent); + return WxMinishopImageUploadCustomizeResult.fromJson(responseContent); } } diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/ApacheMinishopMediaUploadRequestExecutor.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/ApacheMinishopMediaUploadRequestExecutor.java index 7adc6a2cfa..f76d4e8642 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/ApacheMinishopMediaUploadRequestExecutor.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/ApacheMinishopMediaUploadRequestExecutor.java @@ -5,13 +5,11 @@ import me.chanjar.weixin.common.enums.WxType; import me.chanjar.weixin.common.error.WxError; import me.chanjar.weixin.common.error.WxErrorException; -import me.chanjar.weixin.common.util.http.MediaUploadRequestExecutor; import me.chanjar.weixin.common.util.http.MinishopUploadRequestExecutor; import me.chanjar.weixin.common.util.http.RequestHttp; import org.apache.http.HttpEntity; import org.apache.http.HttpHost; import org.apache.http.client.config.RequestConfig; -import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpPost; import org.apache.http.entity.mime.HttpMultipartMode; import org.apache.http.entity.mime.MultipartEntityBuilder; @@ -25,7 +23,7 @@ */ @Slf4j public class ApacheMinishopMediaUploadRequestExecutor extends MinishopUploadRequestExecutor { - public ApacheMinishopMediaUploadRequestExecutor(RequestHttp requestHttp) { + public ApacheMinishopMediaUploadRequestExecutor(RequestHttp requestHttp) { super(requestHttp); } @@ -44,16 +42,12 @@ public WxMinishopImageUploadResult execute(String uri, File file, WxType wxType) .build(); httpPost.setEntity(entity); } - try (CloseableHttpResponse response = requestHttp.getRequestHttpClient().execute(httpPost)) { - String responseContent = Utf8ResponseHandler.INSTANCE.handleResponse(response); - WxError error = WxError.fromJson(responseContent, wxType); - if (error.getErrorCode() != 0) { - throw new WxErrorException(error); - } - log.info("responseContent: " + responseContent); - return WxMinishopImageUploadResult.fromJson(responseContent); - } finally { - httpPost.releaseConnection(); + String responseContent = requestHttp.getRequestHttpClient().execute(httpPost, Utf8ResponseHandler.INSTANCE); + WxError error = WxError.fromJson(responseContent, wxType); + if (error.getErrorCode() != 0) { + throw new WxErrorException(error); } + log.info("responseContent: {}", responseContent); + return WxMinishopImageUploadResult.fromJson(responseContent); } } diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/ApacheSimpleGetRequestExecutor.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/ApacheSimpleGetRequestExecutor.java index be0784b076..08ccf1b252 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/ApacheSimpleGetRequestExecutor.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/ApacheSimpleGetRequestExecutor.java @@ -6,7 +6,6 @@ import me.chanjar.weixin.common.util.http.SimpleGetRequestExecutor; import org.apache.http.HttpHost; import org.apache.http.client.config.RequestConfig; -import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.impl.client.CloseableHttpClient; @@ -19,7 +18,7 @@ * created on 2017/5/4 */ public class ApacheSimpleGetRequestExecutor extends SimpleGetRequestExecutor { - public ApacheSimpleGetRequestExecutor(RequestHttp requestHttp) { + public ApacheSimpleGetRequestExecutor(RequestHttp requestHttp) { super(requestHttp); } @@ -37,12 +36,8 @@ public String execute(String uri, String queryParam, WxType wxType) throws WxErr httpGet.setConfig(config); } - try (CloseableHttpResponse response = requestHttp.getRequestHttpClient().execute(httpGet)) { - String responseContent = Utf8ResponseHandler.INSTANCE.handleResponse(response); - return handleResponse(wxType, responseContent); - } finally { - httpGet.releaseConnection(); - } + String responseContent = requestHttp.getRequestHttpClient().execute(httpGet, Utf8ResponseHandler.INSTANCE); + return handleResponse(wxType, responseContent); } } diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/ApacheSimplePostRequestExecutor.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/ApacheSimplePostRequestExecutor.java index 52c8caaf3d..65af81690d 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/ApacheSimplePostRequestExecutor.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/ApacheSimplePostRequestExecutor.java @@ -4,15 +4,15 @@ import me.chanjar.weixin.common.error.WxErrorException; import me.chanjar.weixin.common.util.http.RequestHttp; import me.chanjar.weixin.common.util.http.SimplePostRequestExecutor; -import org.apache.http.Consts; import org.apache.http.HttpHost; import org.apache.http.client.config.RequestConfig; -import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpPost; +import org.apache.http.entity.ContentType; import org.apache.http.entity.StringEntity; import org.apache.http.impl.client.CloseableHttpClient; import java.io.IOException; +import java.nio.charset.StandardCharsets; /** * . @@ -21,7 +21,7 @@ * created on 2017/5/4 */ public class ApacheSimplePostRequestExecutor extends SimplePostRequestExecutor { - public ApacheSimplePostRequestExecutor(RequestHttp requestHttp) { + public ApacheSimplePostRequestExecutor(RequestHttp requestHttp) { super(requestHttp); } @@ -34,17 +34,12 @@ public String execute(String uri, String postEntity, WxType wxType) throws WxErr } if (postEntity != null) { - StringEntity entity = new StringEntity(postEntity, Consts.UTF_8); - entity.setContentType("application/json; charset=utf-8"); + StringEntity entity = new StringEntity(postEntity, ContentType.APPLICATION_JSON.withCharset(StandardCharsets.UTF_8)); httpPost.setEntity(entity); } - try (CloseableHttpResponse response = requestHttp.getRequestHttpClient().execute(httpPost)) { - String responseContent = Utf8ResponseHandler.INSTANCE.handleResponse(response); - return this.handleResponse(wxType, responseContent); - } finally { - httpPost.releaseConnection(); - } + String responseContent = requestHttp.getRequestHttpClient().execute(httpPost, Utf8ResponseHandler.INSTANCE); + return this.handleResponse(wxType, responseContent); } } diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/ByteArrayResponseHandler.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/ByteArrayResponseHandler.java new file mode 100644 index 0000000000..776b7e32f1 --- /dev/null +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/ByteArrayResponseHandler.java @@ -0,0 +1,17 @@ +package me.chanjar.weixin.common.util.http.apache; + +import org.apache.http.HttpEntity; +import org.apache.http.impl.client.AbstractResponseHandler; +import org.apache.http.util.EntityUtils; + +import java.io.IOException; + +public class ByteArrayResponseHandler extends AbstractResponseHandler { + + public static final ByteArrayResponseHandler INSTANCE = new ByteArrayResponseHandler(); + + @Override + public byte[] handleEntity(HttpEntity entity) throws IOException { + return EntityUtils.toByteArray(entity); + } +} diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/DefaultApacheHttpClientBuilder.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/DefaultApacheHttpClientBuilder.java index 4c06f5168e..ef7120b768 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/DefaultApacheHttpClientBuilder.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/DefaultApacheHttpClientBuilder.java @@ -25,17 +25,13 @@ import org.apache.http.impl.client.HttpClientBuilder; import org.apache.http.impl.client.HttpClients; import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; -import org.apache.http.protocol.HttpContext; import org.apache.http.ssl.SSLContexts; import javax.annotation.concurrent.NotThreadSafe; import javax.net.ssl.SSLContext; -import java.io.IOException; import java.security.KeyManagementException; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; -import java.security.cert.CertificateException; -import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.List; import java.util.concurrent.TimeUnit; @@ -59,7 +55,7 @@ public class DefaultApacheHttpClientBuilder implements ApacheHttpClientBuilder { * 设置为负数是使用系统默认设置(非3000ms的默认值,而是httpClient的默认设置). *

*/ - private int connectionRequestTimeout = -1; + private int connectionRequestTimeout = 3000; /** * 建立链接的超时时间,默认为5000ms.由于是在链接池获取链接,此设置应该并不起什么作用 @@ -97,6 +93,12 @@ public class DefaultApacheHttpClientBuilder implements ApacheHttpClientBuilder { */ private String userAgent; + /** + * 支持的TLS协议版本,默认支持现代TLS版本 + * Supported TLS protocol versions, defaults to modern TLS versions + */ + private String[] supportedProtocols = {"TLSv1.2", "TLSv1.3", "TLSv1.1", "TLSv1"}; + /** * 自定义请求拦截器 */ @@ -183,6 +185,12 @@ public ApacheHttpClientBuilder sslConnectionSocketFactory(SSLConnectionSocketFac return this; } + @Override + public ApacheHttpClientBuilder supportedProtocols(String[] supportedProtocols) { + this.supportedProtocols = supportedProtocols; + return this; + } + public IdleConnectionMonitorThread getIdleConnectionMonitorThread() { return this.idleConnectionMonitorThread; } @@ -261,7 +269,7 @@ private SSLConnectionSocketFactory buildSSLConnectionSocketFactory() { return new SSLConnectionSocketFactory( sslcontext, - new String[]{"TLSv1"}, + this.supportedProtocols, null, SSLConnectionSocketFactory.getDefaultHostnameVerifier()); } catch (NoSuchAlgorithmException | KeyManagementException | KeyStoreException e) { diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/InputStreamResponseHandler.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/InputStreamResponseHandler.java index 5c72744cb0..1568362611 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/InputStreamResponseHandler.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/InputStreamResponseHandler.java @@ -1,33 +1,23 @@ package me.chanjar.weixin.common.util.http.apache; -import java.io.IOException; -import java.io.InputStream; - import org.apache.http.HttpEntity; -import org.apache.http.HttpResponse; -import org.apache.http.StatusLine; -import org.apache.http.client.HttpResponseException; import org.apache.http.client.ResponseHandler; -import org.apache.http.util.EntityUtils; +import org.apache.http.impl.client.AbstractResponseHandler; + +import java.io.IOException; +import java.io.InputStream; /** * 输入流响应处理器. * - * @author Daniel Qian + * @author altusea */ -public class InputStreamResponseHandler implements ResponseHandler { +public class InputStreamResponseHandler extends AbstractResponseHandler { + public static final ResponseHandler INSTANCE = new InputStreamResponseHandler(); - private static final int STATUS_CODE_300 = 300; @Override - public InputStream handleResponse(final HttpResponse response) throws IOException { - final StatusLine statusLine = response.getStatusLine(); - final HttpEntity entity = response.getEntity(); - if (statusLine.getStatusCode() >= STATUS_CODE_300) { - EntityUtils.consume(entity); - throw new HttpResponseException(statusLine.getStatusCode(), statusLine.getReasonPhrase()); - } - return entity == null ? null : entity.getContent(); + public InputStream handleEntity(HttpEntity entity) throws IOException { + return entity.getContent(); } - } diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/Utf8ResponseHandler.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/Utf8ResponseHandler.java index 035726d44f..40d96e3ca1 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/Utf8ResponseHandler.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/Utf8ResponseHandler.java @@ -1,33 +1,24 @@ package me.chanjar.weixin.common.util.http.apache; -import org.apache.http.Consts; import org.apache.http.HttpEntity; -import org.apache.http.HttpResponse; -import org.apache.http.StatusLine; -import org.apache.http.client.HttpResponseException; import org.apache.http.client.ResponseHandler; +import org.apache.http.impl.client.AbstractResponseHandler; import org.apache.http.util.EntityUtils; import java.io.IOException; +import java.nio.charset.StandardCharsets; /** - * copy from {@link org.apache.http.impl.client.BasicResponseHandler} + * Utf8ResponseHandler * - * @author Daniel Qian + * @author altusea */ -public class Utf8ResponseHandler implements ResponseHandler { +public class Utf8ResponseHandler extends AbstractResponseHandler { public static final ResponseHandler INSTANCE = new Utf8ResponseHandler(); @Override - public String handleResponse(final HttpResponse response) throws IOException { - final StatusLine statusLine = response.getStatusLine(); - final HttpEntity entity = response.getEntity(); - if (statusLine.getStatusCode() >= 300) { - EntityUtils.consume(entity); - throw new HttpResponseException(statusLine.getStatusCode(), statusLine.toString()); - } - return entity == null ? null : EntityUtils.toString(entity, Consts.UTF_8); + public String handleEntity(HttpEntity entity) throws IOException { + return EntityUtils.toString(entity, StandardCharsets.UTF_8); } - } diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/hc/BasicResponseHandler.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/hc/BasicResponseHandler.java new file mode 100644 index 0000000000..f69e14a240 --- /dev/null +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/hc/BasicResponseHandler.java @@ -0,0 +1,14 @@ +package me.chanjar.weixin.common.util.http.hc; + +import org.apache.hc.client5.http.impl.classic.BasicHttpClientResponseHandler; + +/** + * ApacheBasicResponseHandler + * + * @author altusea + */ +public class BasicResponseHandler extends BasicHttpClientResponseHandler { + + public static final BasicHttpClientResponseHandler INSTANCE = new BasicHttpClientResponseHandler(); + +} diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/hc/ByteArrayResponseHandler.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/hc/ByteArrayResponseHandler.java new file mode 100644 index 0000000000..e4a314f866 --- /dev/null +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/hc/ByteArrayResponseHandler.java @@ -0,0 +1,22 @@ +package me.chanjar.weixin.common.util.http.hc; + +import org.apache.hc.client5.http.impl.classic.AbstractHttpClientResponseHandler; +import org.apache.hc.core5.http.HttpEntity; +import org.apache.hc.core5.http.io.entity.EntityUtils; + +import java.io.IOException; + +/** + * ByteArrayResponseHandler + * + * @author altusea + */ +public class ByteArrayResponseHandler extends AbstractHttpClientResponseHandler { + + public static final ByteArrayResponseHandler INSTANCE = new ByteArrayResponseHandler(); + + @Override + public byte[] handleEntity(HttpEntity entity) throws IOException { + return EntityUtils.toByteArray(entity); + } +} diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/hc/DefaultHttpComponentsClientBuilder.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/hc/DefaultHttpComponentsClientBuilder.java new file mode 100644 index 0000000000..4915e31a16 --- /dev/null +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/hc/DefaultHttpComponentsClientBuilder.java @@ -0,0 +1,249 @@ +package me.chanjar.weixin.common.util.http.hc; + +import lombok.Data; +import lombok.extern.slf4j.Slf4j; +import me.chanjar.weixin.common.util.http.apache.ApacheHttpClientBuilder; +import me.chanjar.weixin.common.util.http.apache.DefaultApacheHttpClientBuilder; +import org.apache.commons.lang3.ObjectUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.hc.client5.http.ConnectionKeepAliveStrategy; +import org.apache.hc.client5.http.HttpRequestRetryStrategy; +import org.apache.hc.client5.http.auth.AuthScope; +import org.apache.hc.client5.http.auth.UsernamePasswordCredentials; +import org.apache.hc.client5.http.config.ConnectionConfig; +import org.apache.hc.client5.http.config.RequestConfig; +import org.apache.hc.client5.http.impl.auth.BasicCredentialsProvider; +import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; +import org.apache.hc.client5.http.impl.classic.HttpClientBuilder; +import org.apache.hc.client5.http.impl.classic.HttpClients; +import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager; +import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuilder; +import org.apache.hc.client5.http.ssl.DefaultClientTlsStrategy; +import org.apache.hc.client5.http.ssl.NoopHostnameVerifier; +import org.apache.hc.client5.http.ssl.TrustAllStrategy; +import org.apache.hc.core5.http.HttpHost; +import org.apache.hc.core5.http.HttpRequestInterceptor; +import org.apache.hc.core5.http.HttpResponseInterceptor; +import org.apache.hc.core5.http.io.SocketConfig; +import org.apache.hc.core5.ssl.SSLContexts; + +import javax.annotation.concurrent.NotThreadSafe; +import javax.net.ssl.SSLContext; +import java.security.KeyManagementException; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * DefaultApacheHttpClientBuilder + * + * @author altusea + */ +@Slf4j +@Data +@NotThreadSafe +public class DefaultHttpComponentsClientBuilder implements HttpComponentsClientBuilder { + + private final AtomicBoolean prepared = new AtomicBoolean(false); + + /** + * 获取链接的超时时间设置 + *

+ * 设置为零时不超时,一直等待. + * 设置为负数是使用系统默认设置(非3000ms的默认值,而是httpClient的默认设置). + *

+ */ + private int connectionRequestTimeout = 3000; + + /** + * 建立链接的超时时间,默认为5000ms.由于是在链接池获取链接,此设置应该并不起什么作用 + *

+ * 设置为零时不超时,一直等待. + * 设置为负数是使用系统默认设置(非上述的5000ms的默认值,而是httpclient的默认设置). + *

+ */ + private int connectionTimeout = 5000; + /** + * 默认NIO的socket超时设置,默认5000ms. + */ + private int soTimeout = 5000; + /** + * 空闲链接的超时时间,默认60000ms. + *

+ * 超时的链接将在下一次空闲链接检查是被销毁 + *

+ */ + private int idleConnTimeout = 60000; + /** + * 检查空间链接的间隔周期,默认60000ms. + */ + private int checkWaitTime = 60000; + /** + * 每路的最大链接数,默认10 + */ + private int maxConnPerHost = 10; + /** + * 最大总连接数,默认50 + */ + private int maxTotalConn = 50; + /** + * 自定义httpclient的User Agent + */ + private String userAgent; + + /** + * 自定义请求拦截器 + */ + private List requestInterceptors = new ArrayList<>(); + + /** + * 自定义响应拦截器 + */ + private List responseInterceptors = new ArrayList<>(); + + /** + * 自定义重试策略 + */ + private HttpRequestRetryStrategy httpRequestRetryStrategy; + + /** + * 自定义KeepAlive策略 + */ + private ConnectionKeepAliveStrategy connectionKeepAliveStrategy; + + private String httpProxyHost; + private int httpProxyPort; + private String httpProxyUsername; + private char[] httpProxyPassword; + /** + * 持有client对象,仅初始化一次,避免多service实例的时候造成重复初始化的问题 + */ + private CloseableHttpClient closeableHttpClient; + + private DefaultHttpComponentsClientBuilder() { + } + + public static DefaultHttpComponentsClientBuilder get() { + return SingletonHolder.INSTANCE; + } + + @Override + public HttpComponentsClientBuilder httpProxyHost(String httpProxyHost) { + this.httpProxyHost = httpProxyHost; + return this; + } + + @Override + public HttpComponentsClientBuilder httpProxyPort(int httpProxyPort) { + this.httpProxyPort = httpProxyPort; + return this; + } + + @Override + public HttpComponentsClientBuilder httpProxyUsername(String httpProxyUsername) { + this.httpProxyUsername = httpProxyUsername; + return this; + } + + @Override + public HttpComponentsClientBuilder httpProxyPassword(char[] httpProxyPassword) { + this.httpProxyPassword = httpProxyPassword; + return this; + } + + @Override + public HttpComponentsClientBuilder httpRequestRetryStrategy(HttpRequestRetryStrategy httpRequestRetryStrategy) { + this.httpRequestRetryStrategy = httpRequestRetryStrategy; + return this; + } + + @Override + public HttpComponentsClientBuilder keepAliveStrategy(ConnectionKeepAliveStrategy keepAliveStrategy) { + this.connectionKeepAliveStrategy = keepAliveStrategy; + return this; + } + + private synchronized void prepare() { + if (prepared.get()) { + return; + } + + SSLContext sslcontext; + try { + sslcontext = SSLContexts.custom() + .loadTrustMaterial(TrustAllStrategy.INSTANCE) // 忽略对服务器端证书的校验 + .build(); + } catch (NoSuchAlgorithmException | KeyManagementException | KeyStoreException e) { + log.error("构建 SSLContext 时发生异常!", e); + throw new RuntimeException(e); + } + + PoolingHttpClientConnectionManager connManager = PoolingHttpClientConnectionManagerBuilder.create() + .setTlsSocketStrategy(new DefaultClientTlsStrategy(sslcontext, NoopHostnameVerifier.INSTANCE)) + .setMaxConnTotal(this.maxTotalConn) + .setMaxConnPerRoute(this.maxConnPerHost) + .setDefaultSocketConfig(SocketConfig.custom() + .setSoTimeout(this.soTimeout, TimeUnit.MILLISECONDS) + .build()) + .setDefaultConnectionConfig(ConnectionConfig.custom() + .setConnectTimeout(this.connectionTimeout, TimeUnit.MILLISECONDS) + .build()) + .build(); + + HttpClientBuilder httpClientBuilder = HttpClients.custom() + .setConnectionManager(connManager) + .setConnectionManagerShared(true) + .setDefaultRequestConfig(RequestConfig.custom() + .setConnectionRequestTimeout(this.connectionRequestTimeout, TimeUnit.MILLISECONDS) + .build() + ); + + // 设置重试策略,没有则使用默认 + httpClientBuilder.setRetryStrategy(ObjectUtils.defaultIfNull(httpRequestRetryStrategy, NoopRetryStrategy.INSTANCE)); + + // 设置KeepAliveStrategy,没有使用默认 + if (connectionKeepAliveStrategy != null) { + httpClientBuilder.setKeepAliveStrategy(connectionKeepAliveStrategy); + } + + if (StringUtils.isNotBlank(this.httpProxyHost) && StringUtils.isNotBlank(this.httpProxyUsername)) { + // 使用代理服务器 需要用户认证的代理服务器 + BasicCredentialsProvider provider = new BasicCredentialsProvider(); + provider.setCredentials(new AuthScope(this.httpProxyHost, this.httpProxyPort), + new UsernamePasswordCredentials(this.httpProxyUsername, this.httpProxyPassword)); + httpClientBuilder.setDefaultCredentialsProvider(provider); + httpClientBuilder.setProxy(new HttpHost(this.httpProxyHost, this.httpProxyPort)); + } + + if (StringUtils.isNotBlank(this.userAgent)) { + httpClientBuilder.setUserAgent(this.userAgent); + } + + //添加自定义的请求拦截器 + requestInterceptors.forEach(httpClientBuilder::addRequestInterceptorFirst); + + //添加自定义的响应拦截器 + responseInterceptors.forEach(httpClientBuilder::addResponseInterceptorLast); + + this.closeableHttpClient = httpClientBuilder.build(); + prepared.set(true); + } + + @Override + public CloseableHttpClient build() { + if (!prepared.get()) { + prepare(); + } + return this.closeableHttpClient; + } + + /** + * DefaultApacheHttpClientBuilder 改为单例模式,并持有唯一的CloseableHttpClient(仅首次调用创建) + */ + private static class SingletonHolder { + private static final DefaultHttpComponentsClientBuilder INSTANCE = new DefaultHttpComponentsClientBuilder(); + } +} diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/hc/HttpComponentsClientBuilder.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/hc/HttpComponentsClientBuilder.java new file mode 100644 index 0000000000..66cd58e15f --- /dev/null +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/hc/HttpComponentsClientBuilder.java @@ -0,0 +1,51 @@ +package me.chanjar.weixin.common.util.http.hc; + +import me.chanjar.weixin.common.util.http.apache.ApacheHttpClientBuilder; +import org.apache.hc.client5.http.ConnectionKeepAliveStrategy; +import org.apache.hc.client5.http.HttpRequestRetryStrategy; +import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; + +/** + * httpclient build interface. + * + * @author altusea + */ +public interface HttpComponentsClientBuilder { + + /** + * 构建httpclient实例. + * + * @return new instance of CloseableHttpClient + */ + CloseableHttpClient build(); + + /** + * 代理服务器地址. + */ + HttpComponentsClientBuilder httpProxyHost(String httpProxyHost); + + /** + * 代理服务器端口. + */ + HttpComponentsClientBuilder httpProxyPort(int httpProxyPort); + + /** + * 代理服务器用户名. + */ + HttpComponentsClientBuilder httpProxyUsername(String httpProxyUsername); + + /** + * 代理服务器密码. + */ + HttpComponentsClientBuilder httpProxyPassword(char[] httpProxyPassword); + + /** + * 重试策略. + */ + HttpComponentsClientBuilder httpRequestRetryStrategy(HttpRequestRetryStrategy httpRequestRetryStrategy); + + /** + * 超时时间. + */ + HttpComponentsClientBuilder keepAliveStrategy(ConnectionKeepAliveStrategy keepAliveStrategy); +} diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/hc/HttpComponentsMediaDownloadRequestExecutor.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/hc/HttpComponentsMediaDownloadRequestExecutor.java new file mode 100644 index 0000000000..26fbed93f3 --- /dev/null +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/hc/HttpComponentsMediaDownloadRequestExecutor.java @@ -0,0 +1,79 @@ +package me.chanjar.weixin.common.util.http.hc; + +import me.chanjar.weixin.common.enums.WxType; +import me.chanjar.weixin.common.error.WxError; +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.common.util.fs.FileUtils; +import me.chanjar.weixin.common.util.http.BaseMediaDownloadRequestExecutor; +import me.chanjar.weixin.common.util.http.HttpResponseProxy; +import me.chanjar.weixin.common.util.http.RequestHttp; +import org.apache.commons.io.FilenameUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.hc.client5.http.ClientProtocolException; +import org.apache.hc.client5.http.classic.methods.HttpGet; +import org.apache.hc.client5.http.config.RequestConfig; +import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; +import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse; +import org.apache.hc.core5.http.ContentType; +import org.apache.hc.core5.http.Header; +import org.apache.hc.core5.http.HttpException; +import org.apache.hc.core5.http.HttpHost; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; + +/** + * ApacheMediaDownloadRequestExecutor + * + * @author altusea + */ +public class HttpComponentsMediaDownloadRequestExecutor extends BaseMediaDownloadRequestExecutor { + + public HttpComponentsMediaDownloadRequestExecutor(RequestHttp requestHttp, File tmpDirFile) { + super(requestHttp, tmpDirFile); + } + + @Override + public File execute(String uri, String queryParam, WxType wxType) throws WxErrorException, IOException { + if (queryParam != null) { + if (uri.indexOf('?') == -1) { + uri += '?'; + } + uri += uri.endsWith("?") ? queryParam : '&' + queryParam; + } + + HttpGet httpGet = new HttpGet(uri); + if (requestHttp.getRequestHttpProxy() != null) { + RequestConfig config = RequestConfig.custom().setProxy(requestHttp.getRequestHttpProxy()).build(); + httpGet.setConfig(config); + } + + try (CloseableHttpResponse response = requestHttp.getRequestHttpClient().execute(httpGet); + InputStream inputStream = InputStreamResponseHandler.INSTANCE.handleResponse(response)) { + Header[] contentTypeHeader = response.getHeaders("Content-Type"); + if (contentTypeHeader != null && contentTypeHeader.length > 0) { + if (contentTypeHeader[0].getValue().startsWith(ContentType.APPLICATION_JSON.getMimeType())) { + // application/json; encoding=utf-8 下载媒体文件出错 + String responseContent = Utf8ResponseHandler.INSTANCE.handleResponse(response); + throw new WxErrorException(WxError.fromJson(responseContent, wxType)); + } + } + + String fileName = HttpResponseProxy.from(response).getFileName(); + if (StringUtils.isBlank(fileName)) { + fileName = String.valueOf(System.currentTimeMillis()); + } + + String baseName = FilenameUtils.getBaseName(fileName); + if (StringUtils.isBlank(fileName) || baseName.length() < 3) { + baseName = String.valueOf(System.currentTimeMillis()); + } + + return FileUtils.createTmpFile(inputStream, baseName, FilenameUtils.getExtension(fileName), super.tmpDirFile); + } catch (HttpException httpException) { + throw new ClientProtocolException(httpException.getMessage(), httpException); + } + } + +} diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/hc/HttpComponentsMediaInputStreamUploadRequestExecutor.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/hc/HttpComponentsMediaInputStreamUploadRequestExecutor.java new file mode 100644 index 0000000000..4853b1572b --- /dev/null +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/hc/HttpComponentsMediaInputStreamUploadRequestExecutor.java @@ -0,0 +1,54 @@ +package me.chanjar.weixin.common.util.http.hc; + +import me.chanjar.weixin.common.bean.result.WxMediaUploadResult; +import me.chanjar.weixin.common.enums.WxType; +import me.chanjar.weixin.common.error.WxError; +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.common.util.http.InputStreamData; +import me.chanjar.weixin.common.util.http.MediaInputStreamUploadRequestExecutor; +import me.chanjar.weixin.common.util.http.RequestHttp; +import org.apache.hc.client5.http.classic.methods.HttpPost; +import org.apache.hc.client5.http.config.RequestConfig; +import org.apache.hc.client5.http.entity.mime.HttpMultipartMode; +import org.apache.hc.client5.http.entity.mime.MultipartEntityBuilder; +import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; +import org.apache.hc.core5.http.ContentType; +import org.apache.hc.core5.http.HttpEntity; +import org.apache.hc.core5.http.HttpHost; + +import java.io.IOException; + +/** + * 文件输入流上传. + * + * @author altusea + */ +public class HttpComponentsMediaInputStreamUploadRequestExecutor extends MediaInputStreamUploadRequestExecutor { + + public HttpComponentsMediaInputStreamUploadRequestExecutor(RequestHttp requestHttp) { + super(requestHttp); + } + + @Override + public WxMediaUploadResult execute(String uri, InputStreamData data, WxType wxType) throws WxErrorException, IOException { + HttpPost httpPost = new HttpPost(uri); + if (requestHttp.getRequestHttpProxy() != null) { + RequestConfig config = RequestConfig.custom().setProxy(requestHttp.getRequestHttpProxy()).build(); + httpPost.setConfig(config); + } + if (data != null) { + HttpEntity entity = MultipartEntityBuilder + .create() + .addBinaryBody("media", data.getInputStream(), ContentType.DEFAULT_BINARY, data.getFilename()) + .setMode(HttpMultipartMode.EXTENDED) + .build(); + httpPost.setEntity(entity); + } + String responseContent = requestHttp.getRequestHttpClient().execute(httpPost, Utf8ResponseHandler.INSTANCE); + WxError error = WxError.fromJson(responseContent, wxType); + if (error.getErrorCode() != 0) { + throw new WxErrorException(error); + } + return WxMediaUploadResult.fromJson(responseContent); + } +} diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/hc/HttpComponentsMediaUploadRequestExecutor.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/hc/HttpComponentsMediaUploadRequestExecutor.java new file mode 100644 index 0000000000..e65d855d52 --- /dev/null +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/hc/HttpComponentsMediaUploadRequestExecutor.java @@ -0,0 +1,53 @@ +package me.chanjar.weixin.common.util.http.hc; + +import me.chanjar.weixin.common.bean.result.WxMediaUploadResult; +import me.chanjar.weixin.common.enums.WxType; +import me.chanjar.weixin.common.error.WxError; +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.common.util.http.MediaUploadRequestExecutor; +import me.chanjar.weixin.common.util.http.RequestHttp; +import org.apache.hc.client5.http.classic.methods.HttpPost; +import org.apache.hc.client5.http.config.RequestConfig; +import org.apache.hc.client5.http.entity.mime.HttpMultipartMode; +import org.apache.hc.client5.http.entity.mime.MultipartEntityBuilder; +import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; +import org.apache.hc.core5.http.HttpEntity; +import org.apache.hc.core5.http.HttpHost; + +import java.io.File; +import java.io.IOException; + +/** + * ApacheMediaUploadRequestExecutor + * + * @author altusea + */ +public class HttpComponentsMediaUploadRequestExecutor extends MediaUploadRequestExecutor { + + public HttpComponentsMediaUploadRequestExecutor(RequestHttp requestHttp) { + super(requestHttp); + } + + @Override + public WxMediaUploadResult execute(String uri, File file, WxType wxType) throws WxErrorException, IOException { + HttpPost httpPost = new HttpPost(uri); + if (requestHttp.getRequestHttpProxy() != null) { + RequestConfig config = RequestConfig.custom().setProxy(requestHttp.getRequestHttpProxy()).build(); + httpPost.setConfig(config); + } + if (file != null) { + HttpEntity entity = MultipartEntityBuilder + .create() + .addBinaryBody("media", file) + .setMode(HttpMultipartMode.EXTENDED) + .build(); + httpPost.setEntity(entity); + } + String responseContent = requestHttp.getRequestHttpClient().execute(httpPost, Utf8ResponseHandler.INSTANCE); + WxError error = WxError.fromJson(responseContent, wxType); + if (error.getErrorCode() != 0) { + throw new WxErrorException(error); + } + return WxMediaUploadResult.fromJson(responseContent); + } +} diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/hc/HttpComponentsMinishopMediaUploadRequestCustomizeExecutor.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/hc/HttpComponentsMinishopMediaUploadRequestCustomizeExecutor.java new file mode 100644 index 0000000000..711f538309 --- /dev/null +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/hc/HttpComponentsMinishopMediaUploadRequestCustomizeExecutor.java @@ -0,0 +1,71 @@ +package me.chanjar.weixin.common.util.http.hc; + +import lombok.extern.slf4j.Slf4j; +import me.chanjar.weixin.common.bean.result.WxMinishopImageUploadCustomizeResult; +import me.chanjar.weixin.common.enums.WxType; +import me.chanjar.weixin.common.error.WxError; +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.common.util.http.MinishopUploadRequestCustomizeExecutor; +import me.chanjar.weixin.common.util.http.RequestHttp; +import org.apache.hc.client5.http.classic.methods.HttpPost; +import org.apache.hc.client5.http.config.RequestConfig; +import org.apache.hc.client5.http.entity.mime.HttpMultipartMode; +import org.apache.hc.client5.http.entity.mime.MultipartEntityBuilder; +import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; +import org.apache.hc.core5.http.HttpEntity; +import org.apache.hc.core5.http.HttpHost; + +import java.io.File; +import java.io.IOException; + +/** + * ApacheMinishopMediaUploadRequestCustomizeExecutor + * + * @author altusea + */ +@Slf4j +public class HttpComponentsMinishopMediaUploadRequestCustomizeExecutor extends MinishopUploadRequestCustomizeExecutor { + + public HttpComponentsMinishopMediaUploadRequestCustomizeExecutor(RequestHttp requestHttp, String respType, String imgUrl) { + super(requestHttp, respType, imgUrl); + } + + @Override + public WxMinishopImageUploadCustomizeResult execute(String uri, File file, WxType wxType) throws WxErrorException, IOException { + HttpPost httpPost = new HttpPost(uri); + if (requestHttp.getRequestHttpProxy() != null) { + RequestConfig config = RequestConfig.custom().setProxy(requestHttp.getRequestHttpProxy()).build(); + httpPost.setConfig(config); + } + if (this.uploadType.equals("0")) { + if (file == null) { + throw new WxErrorException("上传文件为空"); + } + HttpEntity entity = MultipartEntityBuilder + .create() + .addBinaryBody("media", file) + .addTextBody("resp_type", this.respType) + .addTextBody("upload_type", this.uploadType) + .setMode(HttpMultipartMode.EXTENDED) + .build(); + httpPost.setEntity(entity); + } + else { + HttpEntity entity = MultipartEntityBuilder + .create() + .addTextBody("resp_type", this.respType) + .addTextBody("upload_type", this.uploadType) + .addTextBody("img_url", this.imgUrl) + .setMode(org.apache.hc.client5.http.entity.mime.HttpMultipartMode.EXTENDED) + .build(); + httpPost.setEntity(entity); + } + String responseContent = requestHttp.getRequestHttpClient().execute(httpPost, Utf8ResponseHandler.INSTANCE); + WxError error = WxError.fromJson(responseContent, wxType); + if (error.getErrorCode() != 0) { + throw new WxErrorException(error); + } + log.info("responseContent: {}", responseContent); + return WxMinishopImageUploadCustomizeResult.fromJson(responseContent); + } +} diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/hc/HttpComponentsMinishopMediaUploadRequestExecutor.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/hc/HttpComponentsMinishopMediaUploadRequestExecutor.java new file mode 100644 index 0000000000..72c1f2765f --- /dev/null +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/hc/HttpComponentsMinishopMediaUploadRequestExecutor.java @@ -0,0 +1,56 @@ +package me.chanjar.weixin.common.util.http.hc; + +import lombok.extern.slf4j.Slf4j; +import me.chanjar.weixin.common.bean.result.WxMinishopImageUploadResult; +import me.chanjar.weixin.common.enums.WxType; +import me.chanjar.weixin.common.error.WxError; +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.common.util.http.MinishopUploadRequestExecutor; +import me.chanjar.weixin.common.util.http.RequestHttp; +import org.apache.hc.client5.http.classic.methods.HttpPost; +import org.apache.hc.client5.http.config.RequestConfig; +import org.apache.hc.client5.http.entity.mime.HttpMultipartMode; +import org.apache.hc.client5.http.entity.mime.MultipartEntityBuilder; +import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; +import org.apache.hc.core5.http.HttpEntity; +import org.apache.hc.core5.http.HttpHost; + +import java.io.File; +import java.io.IOException; + +/** + * ApacheMinishopMediaUploadRequestExecutor + * + * @author altusea + */ +@Slf4j +public class HttpComponentsMinishopMediaUploadRequestExecutor extends MinishopUploadRequestExecutor { + + public HttpComponentsMinishopMediaUploadRequestExecutor(RequestHttp requestHttp) { + super(requestHttp); + } + + @Override + public WxMinishopImageUploadResult execute(String uri, File file, WxType wxType) throws WxErrorException, IOException { + HttpPost httpPost = new HttpPost(uri); + if (requestHttp.getRequestHttpProxy() != null) { + RequestConfig config = RequestConfig.custom().setProxy(requestHttp.getRequestHttpProxy()).build(); + httpPost.setConfig(config); + } + if (file != null) { + HttpEntity entity = MultipartEntityBuilder + .create() + .addBinaryBody("media", file) + .setMode(HttpMultipartMode.EXTENDED) + .build(); + httpPost.setEntity(entity); + } + String responseContent = requestHttp.getRequestHttpClient().execute(httpPost, Utf8ResponseHandler.INSTANCE); + WxError error = WxError.fromJson(responseContent, wxType); + if (error.getErrorCode() != 0) { + throw new WxErrorException(error); + } + log.info("responseContent: {}", responseContent); + return WxMinishopImageUploadResult.fromJson(responseContent); + } +} diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/hc/HttpComponentsResponseProxy.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/hc/HttpComponentsResponseProxy.java new file mode 100644 index 0000000000..d55ff0735f --- /dev/null +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/hc/HttpComponentsResponseProxy.java @@ -0,0 +1,25 @@ +package me.chanjar.weixin.common.util.http.hc; + +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.common.util.http.HttpResponseProxy; +import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse; +import org.apache.hc.core5.http.Header; + +public class HttpComponentsResponseProxy implements HttpResponseProxy { + + private final CloseableHttpResponse response; + + public HttpComponentsResponseProxy(CloseableHttpResponse closeableHttpResponse) { + this.response = closeableHttpResponse; + } + + @Override + public String getFileName() throws WxErrorException { + Header[] contentDispositionHeader = this.response.getHeaders("Content-disposition"); + if (contentDispositionHeader == null || contentDispositionHeader.length == 0) { + throw new WxErrorException("无法获取到文件名,Content-disposition为空"); + } + + return HttpResponseProxy.extractFileNameFromContentString(contentDispositionHeader[0].getValue()); + } +} diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/hc/HttpComponentsSimpleGetRequestExecutor.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/hc/HttpComponentsSimpleGetRequestExecutor.java new file mode 100644 index 0000000000..0d212fe7e2 --- /dev/null +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/hc/HttpComponentsSimpleGetRequestExecutor.java @@ -0,0 +1,43 @@ +package me.chanjar.weixin.common.util.http.hc; + +import me.chanjar.weixin.common.enums.WxType; +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.common.util.http.RequestHttp; +import me.chanjar.weixin.common.util.http.SimpleGetRequestExecutor; +import org.apache.hc.client5.http.classic.methods.HttpGet; +import org.apache.hc.client5.http.config.RequestConfig; +import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; +import org.apache.hc.core5.http.HttpHost; + +import java.io.IOException; + +/** + * ApacheSimpleGetRequestExecutor + * + * @author altusea + */ +public class HttpComponentsSimpleGetRequestExecutor extends SimpleGetRequestExecutor { + + public HttpComponentsSimpleGetRequestExecutor(RequestHttp requestHttp) { + super(requestHttp); + } + + @Override + public String execute(String uri, String queryParam, WxType wxType) throws WxErrorException, IOException { + if (queryParam != null) { + if (uri.indexOf('?') == -1) { + uri += '?'; + } + uri += uri.endsWith("?") ? queryParam : '&' + queryParam; + } + HttpGet httpGet = new HttpGet(uri); + if (requestHttp.getRequestHttpProxy() != null) { + RequestConfig config = RequestConfig.custom().setProxy(requestHttp.getRequestHttpProxy()).build(); + httpGet.setConfig(config); + } + + String responseContent = requestHttp.getRequestHttpClient().execute(httpGet, Utf8ResponseHandler.INSTANCE); + return handleResponse(wxType, responseContent); + } + +} diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/hc/HttpComponentsSimplePostRequestExecutor.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/hc/HttpComponentsSimplePostRequestExecutor.java new file mode 100644 index 0000000000..45d2ca9f6e --- /dev/null +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/hc/HttpComponentsSimplePostRequestExecutor.java @@ -0,0 +1,45 @@ +package me.chanjar.weixin.common.util.http.hc; + +import me.chanjar.weixin.common.enums.WxType; +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.common.util.http.RequestHttp; +import me.chanjar.weixin.common.util.http.SimplePostRequestExecutor; +import org.apache.hc.client5.http.classic.methods.HttpPost; +import org.apache.hc.client5.http.config.RequestConfig; +import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; +import org.apache.hc.core5.http.ContentType; +import org.apache.hc.core5.http.HttpHost; +import org.apache.hc.core5.http.io.entity.StringEntity; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; + +/** + * ApacheSimplePostRequestExecutor + * + * @author altusea + */ +public class HttpComponentsSimplePostRequestExecutor extends SimplePostRequestExecutor { + + public HttpComponentsSimplePostRequestExecutor(RequestHttp requestHttp) { + super(requestHttp); + } + + @Override + public String execute(String uri, String postEntity, WxType wxType) throws WxErrorException, IOException { + HttpPost httpPost = new HttpPost(uri); + if (requestHttp.getRequestHttpProxy() != null) { + RequestConfig config = RequestConfig.custom().setProxy(requestHttp.getRequestHttpProxy()).build(); + httpPost.setConfig(config); + } + + if (postEntity != null) { + StringEntity entity = new StringEntity(postEntity, ContentType.APPLICATION_JSON.withCharset(StandardCharsets.UTF_8)); + httpPost.setEntity(entity); + } + + String responseContent = requestHttp.getRequestHttpClient().execute(httpPost, Utf8ResponseHandler.INSTANCE); + return this.handleResponse(wxType, responseContent); + } + +} diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/hc/InputStreamResponseHandler.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/hc/InputStreamResponseHandler.java new file mode 100644 index 0000000000..27308151f7 --- /dev/null +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/hc/InputStreamResponseHandler.java @@ -0,0 +1,23 @@ +package me.chanjar.weixin.common.util.http.hc; + +import org.apache.hc.client5.http.impl.classic.AbstractHttpClientResponseHandler; +import org.apache.hc.core5.http.HttpEntity; +import org.apache.hc.core5.http.io.HttpClientResponseHandler; + +import java.io.IOException; +import java.io.InputStream; + +/** + * InputStreamResponseHandler + * + * @author altusea + */ +public class InputStreamResponseHandler extends AbstractHttpClientResponseHandler { + + public static final HttpClientResponseHandler INSTANCE = new InputStreamResponseHandler(); + + @Override + public InputStream handleEntity(HttpEntity entity) throws IOException { + return entity.getContent(); + } +} diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/hc/NoopRetryStrategy.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/hc/NoopRetryStrategy.java new file mode 100644 index 0000000000..9b4e3bc384 --- /dev/null +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/hc/NoopRetryStrategy.java @@ -0,0 +1,34 @@ +package me.chanjar.weixin.common.util.http.hc; + +import org.apache.hc.client5.http.HttpRequestRetryStrategy; +import org.apache.hc.core5.http.HttpRequest; +import org.apache.hc.core5.http.HttpResponse; +import org.apache.hc.core5.http.protocol.HttpContext; +import org.apache.hc.core5.util.TimeValue; + +import java.io.IOException; + +/** + * NoopRetryStrategy + * + * @author altusea + */ +public class NoopRetryStrategy implements HttpRequestRetryStrategy { + + public static final HttpRequestRetryStrategy INSTANCE = new NoopRetryStrategy(); + + @Override + public boolean retryRequest(HttpRequest request, IOException exception, int execCount, HttpContext context) { + return false; + } + + @Override + public boolean retryRequest(HttpResponse response, int execCount, HttpContext context) { + return false; + } + + @Override + public TimeValue getRetryInterval(HttpResponse response, int execCount, HttpContext context) { + return TimeValue.ZERO_MILLISECONDS; + } +} diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/hc/Utf8ResponseHandler.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/hc/Utf8ResponseHandler.java new file mode 100644 index 0000000000..81699ef57b --- /dev/null +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/hc/Utf8ResponseHandler.java @@ -0,0 +1,30 @@ +package me.chanjar.weixin.common.util.http.hc; + +import org.apache.hc.client5.http.ClientProtocolException; +import org.apache.hc.client5.http.impl.classic.AbstractHttpClientResponseHandler; +import org.apache.hc.core5.http.HttpEntity; +import org.apache.hc.core5.http.ParseException; +import org.apache.hc.core5.http.io.HttpClientResponseHandler; +import org.apache.hc.core5.http.io.entity.EntityUtils; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; + +/** + * Utf8ResponseHandler + * + * @author altusea + */ +public class Utf8ResponseHandler extends AbstractHttpClientResponseHandler { + + public static final HttpClientResponseHandler INSTANCE = new Utf8ResponseHandler(); + + @Override + public String handleEntity(HttpEntity entity) throws IOException { + try { + return EntityUtils.toString(entity, StandardCharsets.UTF_8); + } catch (final ParseException ex) { + throw new ClientProtocolException(ex); + } + } +} diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/jodd/JoddHttpMediaDownloadRequestExecutor.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/jodd/JoddHttpMediaDownloadRequestExecutor.java index 920cf2d03b..bc2fbc17f3 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/jodd/JoddHttpMediaDownloadRequestExecutor.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/jodd/JoddHttpMediaDownloadRequestExecutor.java @@ -4,7 +4,6 @@ import jodd.http.HttpRequest; import jodd.http.HttpResponse; import jodd.http.ProxyInfo; -import jodd.util.StringPool; import me.chanjar.weixin.common.enums.WxType; import me.chanjar.weixin.common.error.WxError; import me.chanjar.weixin.common.error.WxErrorException; @@ -28,7 +27,7 @@ * created on 2017/5/5 */ public class JoddHttpMediaDownloadRequestExecutor extends BaseMediaDownloadRequestExecutor { - public JoddHttpMediaDownloadRequestExecutor(RequestHttp requestHttp, File tmpDirFile) { + public JoddHttpMediaDownloadRequestExecutor(RequestHttp requestHttp, File tmpDirFile) { super(requestHttp, tmpDirFile); } @@ -56,7 +55,7 @@ public File execute(String uri, String queryParam, WxType wxType) throws WxError throw new WxErrorException(WxError.fromJson(response.bodyText(), wxType)); } - String fileName = new HttpResponseProxy(response).getFileName(); + String fileName = HttpResponseProxy.from(response).getFileName(); if (StringUtils.isBlank(fileName)) { return null; } diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/jodd/JoddHttpMediaInputStreamUploadRequestExecutor.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/jodd/JoddHttpMediaInputStreamUploadRequestExecutor.java index 311b7c49c5..915db21c65 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/jodd/JoddHttpMediaInputStreamUploadRequestExecutor.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/jodd/JoddHttpMediaInputStreamUploadRequestExecutor.java @@ -25,7 +25,7 @@ * created on 2022/02/15 */ public class JoddHttpMediaInputStreamUploadRequestExecutor extends MediaInputStreamUploadRequestExecutor { - public JoddHttpMediaInputStreamUploadRequestExecutor(RequestHttp requestHttp) { + public JoddHttpMediaInputStreamUploadRequestExecutor(RequestHttp requestHttp) { super(requestHttp); } diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/jodd/JoddHttpMediaUploadRequestExecutor.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/jodd/JoddHttpMediaUploadRequestExecutor.java index 876caa29fb..1ed59a71da 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/jodd/JoddHttpMediaUploadRequestExecutor.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/jodd/JoddHttpMediaUploadRequestExecutor.java @@ -4,7 +4,6 @@ import jodd.http.HttpRequest; import jodd.http.HttpResponse; import jodd.http.ProxyInfo; -import jodd.util.StringPool; import me.chanjar.weixin.common.enums.WxType; import me.chanjar.weixin.common.bean.result.WxMediaUploadResult; import me.chanjar.weixin.common.error.WxError; @@ -23,7 +22,7 @@ * created on 2017/5/5 */ public class JoddHttpMediaUploadRequestExecutor extends MediaUploadRequestExecutor { - public JoddHttpMediaUploadRequestExecutor(RequestHttp requestHttp) { + public JoddHttpMediaUploadRequestExecutor(RequestHttp requestHttp) { super(requestHttp); } diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/jodd/JoddHttpMinishopMediaUploadRequestCustomizeExecutor.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/jodd/JoddHttpMinishopMediaUploadRequestCustomizeExecutor.java index 1d6f24fa2a..66074d8103 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/jodd/JoddHttpMinishopMediaUploadRequestCustomizeExecutor.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/jodd/JoddHttpMinishopMediaUploadRequestCustomizeExecutor.java @@ -22,7 +22,7 @@ */ @Slf4j public class JoddHttpMinishopMediaUploadRequestCustomizeExecutor extends MinishopUploadRequestCustomizeExecutor { - public JoddHttpMinishopMediaUploadRequestCustomizeExecutor(RequestHttp requestHttp, String respType, String imgUrl) { + public JoddHttpMinishopMediaUploadRequestCustomizeExecutor(RequestHttp requestHttp, String respType, String imgUrl) { super(requestHttp, respType, imgUrl); } @@ -51,7 +51,7 @@ public WxMinishopImageUploadCustomizeResult execute(String uri, File file, WxTyp if (error.getErrorCode() != 0) { throw new WxErrorException(error); } - log.info("responseContent: " + responseContent); + log.info("responseContent: {}", responseContent); return WxMinishopImageUploadCustomizeResult.fromJson(responseContent); } diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/jodd/JoddHttpMinishopMediaUploadRequestExecutor.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/jodd/JoddHttpMinishopMediaUploadRequestExecutor.java index 4cb9c50ee0..c7c35dd798 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/jodd/JoddHttpMinishopMediaUploadRequestExecutor.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/jodd/JoddHttpMinishopMediaUploadRequestExecutor.java @@ -5,12 +5,10 @@ import jodd.http.HttpResponse; import jodd.http.ProxyInfo; import lombok.extern.slf4j.Slf4j; -import me.chanjar.weixin.common.bean.result.WxMediaUploadResult; import me.chanjar.weixin.common.bean.result.WxMinishopImageUploadResult; import me.chanjar.weixin.common.enums.WxType; import me.chanjar.weixin.common.error.WxError; import me.chanjar.weixin.common.error.WxErrorException; -import me.chanjar.weixin.common.util.http.MediaUploadRequestExecutor; import me.chanjar.weixin.common.util.http.MinishopUploadRequestExecutor; import me.chanjar.weixin.common.util.http.RequestHttp; @@ -26,7 +24,7 @@ */ @Slf4j public class JoddHttpMinishopMediaUploadRequestExecutor extends MinishopUploadRequestExecutor { - public JoddHttpMinishopMediaUploadRequestExecutor(RequestHttp requestHttp) { + public JoddHttpMinishopMediaUploadRequestExecutor(RequestHttp requestHttp) { super(requestHttp); } @@ -46,7 +44,7 @@ public WxMinishopImageUploadResult execute(String uri, File file, WxType wxType) if (error.getErrorCode() != 0) { throw new WxErrorException(error); } - log.info("responseContent: " + responseContent); + log.info("responseContent: {}", responseContent); return WxMinishopImageUploadResult.fromJson(responseContent); } diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/jodd/JoddHttpResponseProxy.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/jodd/JoddHttpResponseProxy.java new file mode 100644 index 0000000000..1bda38a497 --- /dev/null +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/jodd/JoddHttpResponseProxy.java @@ -0,0 +1,20 @@ +package me.chanjar.weixin.common.util.http.jodd; + +import jodd.http.HttpResponse; +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.common.util.http.HttpResponseProxy; + +public class JoddHttpResponseProxy implements HttpResponseProxy { + + private final HttpResponse response; + + public JoddHttpResponseProxy(HttpResponse httpResponse) { + this.response = httpResponse; + } + + @Override + public String getFileName() throws WxErrorException { + String content = response.header("Content-disposition"); + return HttpResponseProxy.extractFileNameFromContentString(content); + } +} diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/jodd/JoddHttpSimpleGetRequestExecutor.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/jodd/JoddHttpSimpleGetRequestExecutor.java index 869ea8c04e..ed8288b04f 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/jodd/JoddHttpSimpleGetRequestExecutor.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/jodd/JoddHttpSimpleGetRequestExecutor.java @@ -4,7 +4,6 @@ import jodd.http.HttpRequest; import jodd.http.HttpResponse; import jodd.http.ProxyInfo; -import jodd.util.StringPool; import me.chanjar.weixin.common.enums.WxType; import me.chanjar.weixin.common.error.WxErrorException; import me.chanjar.weixin.common.util.http.RequestHttp; @@ -20,7 +19,7 @@ * created on 2017/5/4 */ public class JoddHttpSimpleGetRequestExecutor extends SimpleGetRequestExecutor { - public JoddHttpSimpleGetRequestExecutor(RequestHttp requestHttp) { + public JoddHttpSimpleGetRequestExecutor(RequestHttp requestHttp) { super(requestHttp); } diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/jodd/JoddHttpSimplePostRequestExecutor.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/jodd/JoddHttpSimplePostRequestExecutor.java index 654378271c..095493c75e 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/jodd/JoddHttpSimplePostRequestExecutor.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/jodd/JoddHttpSimplePostRequestExecutor.java @@ -19,7 +19,7 @@ * created on 2017/5/4 */ public class JoddHttpSimplePostRequestExecutor extends SimplePostRequestExecutor { - public JoddHttpSimplePostRequestExecutor(RequestHttp requestHttp) { + public JoddHttpSimplePostRequestExecutor(RequestHttp requestHttp) { super(requestHttp); } diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/okhttp/OkHttpMediaDownloadRequestExecutor.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/okhttp/OkHttpMediaDownloadRequestExecutor.java index dda52e2f7b..0610d3f51c 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/okhttp/OkHttpMediaDownloadRequestExecutor.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/okhttp/OkHttpMediaDownloadRequestExecutor.java @@ -25,7 +25,7 @@ */ @Slf4j public class OkHttpMediaDownloadRequestExecutor extends BaseMediaDownloadRequestExecutor { - public OkHttpMediaDownloadRequestExecutor(RequestHttp requestHttp, File tmpDirFile) { + public OkHttpMediaDownloadRequestExecutor(RequestHttp requestHttp, File tmpDirFile) { super(requestHttp, tmpDirFile); } @@ -51,7 +51,7 @@ public File execute(String uri, String queryParam, WxType wxType) throws WxError throw new WxErrorException(WxError.fromJson(response.body().string(), wxType)); } - String fileName = new HttpResponseProxy(response).getFileName(); + String fileName = HttpResponseProxy.from(response).getFileName(); if (StringUtils.isBlank(fileName)) { return null; } diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/okhttp/OkHttpMediaInputStreamUploadRequestExecutor.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/okhttp/OkHttpMediaInputStreamUploadRequestExecutor.java index 613bd7ecfa..c30cc619aa 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/okhttp/OkHttpMediaInputStreamUploadRequestExecutor.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/okhttp/OkHttpMediaInputStreamUploadRequestExecutor.java @@ -20,7 +20,7 @@ * created on 2022/02/15 */ public class OkHttpMediaInputStreamUploadRequestExecutor extends MediaInputStreamUploadRequestExecutor { - public OkHttpMediaInputStreamUploadRequestExecutor(RequestHttp requestHttp) { + public OkHttpMediaInputStreamUploadRequestExecutor(RequestHttp requestHttp) { super(requestHttp); } diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/okhttp/OkHttpMediaUploadRequestExecutor.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/okhttp/OkHttpMediaUploadRequestExecutor.java index 1b5241ff70..6a7b0b794d 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/okhttp/OkHttpMediaUploadRequestExecutor.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/okhttp/OkHttpMediaUploadRequestExecutor.java @@ -18,7 +18,7 @@ * created on 2017/5/5 */ public class OkHttpMediaUploadRequestExecutor extends MediaUploadRequestExecutor { - public OkHttpMediaUploadRequestExecutor(RequestHttp requestHttp) { + public OkHttpMediaUploadRequestExecutor(RequestHttp requestHttp) { super(requestHttp); } diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/okhttp/OkHttpMinishopMediaUploadRequestCustomizeExecutor.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/okhttp/OkHttpMinishopMediaUploadRequestCustomizeExecutor.java index a8b76321ca..a2c78f423b 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/okhttp/OkHttpMinishopMediaUploadRequestCustomizeExecutor.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/okhttp/OkHttpMinishopMediaUploadRequestCustomizeExecutor.java @@ -18,7 +18,7 @@ */ @Slf4j public class OkHttpMinishopMediaUploadRequestCustomizeExecutor extends MinishopUploadRequestCustomizeExecutor { - public OkHttpMinishopMediaUploadRequestCustomizeExecutor(RequestHttp requestHttp, String respType, String imgUrl) { + public OkHttpMinishopMediaUploadRequestCustomizeExecutor(RequestHttp requestHttp, String respType, String imgUrl) { super(requestHttp, respType, imgUrl); } @@ -50,7 +50,7 @@ public WxMinishopImageUploadCustomizeResult execute(String uri, File file, WxTyp if (error.getErrorCode() != 0) { throw new WxErrorException(error); } - log.info("responseContent: " + responseContent); + log.info("responseContent: {}", responseContent); return WxMinishopImageUploadCustomizeResult.fromJson(responseContent); } diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/okhttp/OkHttpMinishopMediaUploadRequestExecutor.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/okhttp/OkHttpMinishopMediaUploadRequestExecutor.java index 5c40b1f6ba..f2df3c7e73 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/okhttp/OkHttpMinishopMediaUploadRequestExecutor.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/okhttp/OkHttpMinishopMediaUploadRequestExecutor.java @@ -1,12 +1,10 @@ package me.chanjar.weixin.common.util.http.okhttp; import lombok.extern.slf4j.Slf4j; -import me.chanjar.weixin.common.bean.result.WxMediaUploadResult; import me.chanjar.weixin.common.bean.result.WxMinishopImageUploadResult; import me.chanjar.weixin.common.enums.WxType; import me.chanjar.weixin.common.error.WxError; import me.chanjar.weixin.common.error.WxErrorException; -import me.chanjar.weixin.common.util.http.MediaUploadRequestExecutor; import me.chanjar.weixin.common.util.http.MinishopUploadRequestExecutor; import me.chanjar.weixin.common.util.http.RequestHttp; import okhttp3.*; @@ -22,7 +20,7 @@ */ @Slf4j public class OkHttpMinishopMediaUploadRequestExecutor extends MinishopUploadRequestExecutor { - public OkHttpMinishopMediaUploadRequestExecutor(RequestHttp requestHttp) { + public OkHttpMinishopMediaUploadRequestExecutor(RequestHttp requestHttp) { super(requestHttp); } @@ -43,7 +41,7 @@ public WxMinishopImageUploadResult execute(String uri, File file, WxType wxType) if (error.getErrorCode() != 0) { throw new WxErrorException(error); } - log.info("responseContent: " + responseContent); + log.info("responseContent: {}", responseContent); return WxMinishopImageUploadResult.fromJson(responseContent); } diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/okhttp/OkHttpResponseProxy.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/okhttp/OkHttpResponseProxy.java new file mode 100644 index 0000000000..95c290735c --- /dev/null +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/okhttp/OkHttpResponseProxy.java @@ -0,0 +1,20 @@ +package me.chanjar.weixin.common.util.http.okhttp; + +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.common.util.http.HttpResponseProxy; +import okhttp3.Response; + +public class OkHttpResponseProxy implements HttpResponseProxy { + + private final Response response; + + public OkHttpResponseProxy(Response response) { + this.response = response; + } + + @Override + public String getFileName() throws WxErrorException { + String content = this.response.header("Content-disposition"); + return HttpResponseProxy.extractFileNameFromContentString(content); + } +} diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/okhttp/OkHttpSimpleGetRequestExecutor.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/okhttp/OkHttpSimpleGetRequestExecutor.java index 2a41ea0508..d475222872 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/okhttp/OkHttpSimpleGetRequestExecutor.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/okhttp/OkHttpSimpleGetRequestExecutor.java @@ -17,7 +17,7 @@ * created on 2017/5/4 */ public class OkHttpSimpleGetRequestExecutor extends SimpleGetRequestExecutor { - public OkHttpSimpleGetRequestExecutor(RequestHttp requestHttp) { + public OkHttpSimpleGetRequestExecutor(RequestHttp requestHttp) { super(requestHttp); } diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/okhttp/OkHttpSimplePostRequestExecutor.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/okhttp/OkHttpSimplePostRequestExecutor.java index a289f362e3..3044f29d60 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/okhttp/OkHttpSimplePostRequestExecutor.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/okhttp/OkHttpSimplePostRequestExecutor.java @@ -18,7 +18,7 @@ */ @Slf4j public class OkHttpSimplePostRequestExecutor extends SimplePostRequestExecutor { - public OkHttpSimplePostRequestExecutor(RequestHttp requestHttp) { + public OkHttpSimplePostRequestExecutor(RequestHttp requestHttp) { super(requestHttp); } diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/json/GsonParser.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/json/GsonParser.java index 061a3cb2ee..f2646436c0 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/json/GsonParser.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/json/GsonParser.java @@ -10,17 +10,16 @@ * @author niefy */ public class GsonParser { - private static final JsonParser JSON_PARSER = new JsonParser(); public static JsonObject parse(String json) { - return JSON_PARSER.parse(json).getAsJsonObject(); + return JsonParser.parseString(json).getAsJsonObject(); } public static JsonObject parse(Reader json) { - return JSON_PARSER.parse(json).getAsJsonObject(); + return JsonParser.parseReader(json).getAsJsonObject(); } public static JsonObject parse(JsonReader json) { - return JSON_PARSER.parse(json).getAsJsonObject(); + return JsonParser.parseReader(json).getAsJsonObject(); } } diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/json/WxMenuGsonAdapter.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/json/WxMenuGsonAdapter.java index 50d3b0d630..5e7f9b41d9 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/json/WxMenuGsonAdapter.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/json/WxMenuGsonAdapter.java @@ -1,11 +1,3 @@ -/* - * KINGSTAR MEDIA SOLUTIONS Co.,LTD. Copyright c 2005-2013. All rights reserved. - * - * This source code is the property of KINGSTAR MEDIA SOLUTIONS LTD. It is intended - * only for the use of KINGSTAR MEDIA application development. Reengineering, reproduction - * arose from modification of the original source, or other redistribution of this source - * is not permitted without written permission of the KINGSTAR MEDIA SOLUTIONS LTD. - */ package me.chanjar.weixin.common.util.json; import com.google.gson.*; @@ -14,6 +6,7 @@ import me.chanjar.weixin.common.bean.menu.WxMenuRule; import java.lang.reflect.Type; +import java.util.Optional; /** @@ -21,96 +14,111 @@ */ public class WxMenuGsonAdapter implements JsonSerializer, JsonDeserializer { + // JSON字段常量定义 + private static final String FIELD_BUTTON = "button"; + private static final String FIELD_MATCH_RULE = "matchrule"; + private static final String FIELD_SUB_BUTTON = "sub_button"; + private static final String FIELD_MENU = "menu"; + + // 菜单按钮字段常量 + private static final String FIELD_TYPE = "type"; + private static final String FIELD_NAME = "name"; + private static final String FIELD_KEY = "key"; + private static final String FIELD_URL = "url"; + private static final String FIELD_MEDIA_ID = "media_id"; + private static final String FIELD_ARTICLE_ID = "article_id"; + private static final String FIELD_APP_ID = "appid"; + private static final String FIELD_PAGE_PATH = "pagepath"; + + // 菜单规则字段常量 + private static final String FIELD_TAG_ID = "tag_id"; + private static final String FIELD_SEX = "sex"; + private static final String FIELD_COUNTRY = "country"; + private static final String FIELD_PROVINCE = "province"; + private static final String FIELD_CITY = "city"; + private static final String FIELD_CLIENT_PLATFORM_TYPE = "client_platform_type"; + private static final String FIELD_LANGUAGE = "language"; + @Override public JsonElement serialize(WxMenu menu, Type typeOfSrc, JsonSerializationContext context) { JsonObject json = new JsonObject(); - JsonArray buttonArray = new JsonArray(); - for (WxMenuButton button : menu.getButtons()) { - JsonObject buttonJson = convertToJson(button); - buttonArray.add(buttonJson); - } - json.add("button", buttonArray); - + Optional.ofNullable(menu.getButtons()) + .ifPresent(buttons -> buttons.stream() + .map(this::convertToJson) + .forEach(buttonArray::add)); + json.add(FIELD_BUTTON, buttonArray); if (menu.getMatchRule() != null) { - json.add("matchrule", convertToJson(menu.getMatchRule())); + json.add(FIELD_MATCH_RULE, convertToJson(menu.getMatchRule())); } - return json; } protected JsonObject convertToJson(WxMenuButton button) { JsonObject buttonJson = new JsonObject(); - buttonJson.addProperty("type", button.getType()); - buttonJson.addProperty("name", button.getName()); - buttonJson.addProperty("key", button.getKey()); - buttonJson.addProperty("url", button.getUrl()); - buttonJson.addProperty("media_id", button.getMediaId()); - buttonJson.addProperty("article_id", button.getArticleId()); - buttonJson.addProperty("appid", button.getAppId()); - buttonJson.addProperty("pagepath", button.getPagePath()); - if (button.getSubButtons() != null && button.getSubButtons().size() > 0) { + addPropertyIfNotNull(buttonJson, FIELD_TYPE, button.getType()); + addPropertyIfNotNull(buttonJson, FIELD_NAME, button.getName()); + addPropertyIfNotNull(buttonJson, FIELD_KEY, button.getKey()); + addPropertyIfNotNull(buttonJson, FIELD_URL, button.getUrl()); + addPropertyIfNotNull(buttonJson, FIELD_MEDIA_ID, button.getMediaId()); + addPropertyIfNotNull(buttonJson, FIELD_ARTICLE_ID, button.getArticleId()); + addPropertyIfNotNull(buttonJson, FIELD_APP_ID, button.getAppId()); + addPropertyIfNotNull(buttonJson, FIELD_PAGE_PATH, button.getPagePath()); + if (button.getSubButtons() != null && !button.getSubButtons().isEmpty()) { JsonArray buttonArray = new JsonArray(); - for (WxMenuButton sub_button : button.getSubButtons()) { - buttonArray.add(convertToJson(sub_button)); - } - buttonJson.add("sub_button", buttonArray); + button.getSubButtons().stream() + .map(this::convertToJson) + .forEach(buttonArray::add); + buttonJson.add(FIELD_SUB_BUTTON, buttonArray); } return buttonJson; } protected JsonObject convertToJson(WxMenuRule menuRule) { JsonObject matchRule = new JsonObject(); - matchRule.addProperty("tag_id", menuRule.getTagId()); - matchRule.addProperty("sex", menuRule.getSex()); - matchRule.addProperty("country", menuRule.getCountry()); - matchRule.addProperty("province", menuRule.getProvince()); - matchRule.addProperty("city", menuRule.getCity()); - matchRule.addProperty("client_platform_type", menuRule.getClientPlatformType()); - matchRule.addProperty("language", menuRule.getLanguage()); + addPropertyIfNotNull(matchRule, FIELD_TAG_ID, menuRule.getTagId()); + addPropertyIfNotNull(matchRule, FIELD_SEX, menuRule.getSex()); + addPropertyIfNotNull(matchRule, FIELD_COUNTRY, menuRule.getCountry()); + addPropertyIfNotNull(matchRule, FIELD_PROVINCE, menuRule.getProvince()); + addPropertyIfNotNull(matchRule, FIELD_CITY, menuRule.getCity()); + addPropertyIfNotNull(matchRule, FIELD_CLIENT_PLATFORM_TYPE, menuRule.getClientPlatformType()); + addPropertyIfNotNull(matchRule, FIELD_LANGUAGE, menuRule.getLanguage()); return matchRule; } - @Deprecated - private WxMenuRule convertToRule(JsonObject json) { - WxMenuRule menuRule = new WxMenuRule(); - //变态的微信接口,这里居然反人类的使用和序列化时不一样的名字 - //menuRule.setTagId(GsonHelper.getString(json,"tag_id")); - menuRule.setTagId(GsonHelper.getString(json, "group_id")); - menuRule.setSex(GsonHelper.getString(json, "sex")); - menuRule.setCountry(GsonHelper.getString(json, "country")); - menuRule.setProvince(GsonHelper.getString(json, "province")); - menuRule.setCity(GsonHelper.getString(json, "city")); - menuRule.setClientPlatformType(GsonHelper.getString(json, "client_platform_type")); - menuRule.setLanguage(GsonHelper.getString(json, "language")); - return menuRule; + private void addPropertyIfNotNull(JsonObject obj, String key, String value) { + if (value != null) { + obj.addProperty(key, value); + } } @Override public WxMenu deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { - /* - * 操蛋的微信 - * 创建菜单时是 { button : ... } - * 查询菜单时是 { menu : { button : ... } } - * 现在企业号升级为企业微信后,没有此问题,因此需要单独处理 - */ - JsonArray buttonsJson = json.getAsJsonObject().get("menu").getAsJsonObject().get("button").getAsJsonArray(); - return this.buildMenuFromJson(buttonsJson); + JsonObject root = json.getAsJsonObject(); + JsonArray buttonsJson = null; + if (root.has(FIELD_MENU)) { + JsonObject menuObj = root.getAsJsonObject(FIELD_MENU); + buttonsJson = menuObj.getAsJsonArray(FIELD_BUTTON); + } else if (root.has(FIELD_BUTTON)) { + buttonsJson = root.getAsJsonArray(FIELD_BUTTON); + } + if (buttonsJson == null) { + throw new JsonParseException("No button array found in menu JSON"); + } + return buildMenuFromJson(buttonsJson); } protected WxMenu buildMenuFromJson(JsonArray buttonsJson) { WxMenu menu = new WxMenu(); - for (int i = 0; i < buttonsJson.size(); i++) { - JsonObject buttonJson = buttonsJson.get(i).getAsJsonObject(); + for (JsonElement btnElem : buttonsJson) { + JsonObject buttonJson = btnElem.getAsJsonObject(); WxMenuButton button = convertFromJson(buttonJson); menu.getButtons().add(button); - if (buttonJson.get("sub_button") == null || buttonJson.get("sub_button").isJsonNull()) { - continue; - } - JsonArray sub_buttonsJson = buttonJson.get("sub_button").getAsJsonArray(); - for (int j = 0; j < sub_buttonsJson.size(); j++) { - JsonObject sub_buttonJson = sub_buttonsJson.get(j).getAsJsonObject(); - button.getSubButtons().add(convertFromJson(sub_buttonJson)); + if (buttonJson.has(FIELD_SUB_BUTTON) && buttonJson.get(FIELD_SUB_BUTTON).isJsonArray()) { + JsonArray sub_buttonsJson = buttonJson.getAsJsonArray(FIELD_SUB_BUTTON); + for (JsonElement subBtnElem : sub_buttonsJson) { + button.getSubButtons().add(convertFromJson(subBtnElem.getAsJsonObject())); + } } } return menu; @@ -118,14 +126,14 @@ protected WxMenu buildMenuFromJson(JsonArray buttonsJson) { protected WxMenuButton convertFromJson(JsonObject json) { WxMenuButton button = new WxMenuButton(); - button.setName(GsonHelper.getString(json, "name")); - button.setKey(GsonHelper.getString(json, "key")); - button.setUrl(GsonHelper.getString(json, "url")); - button.setType(GsonHelper.getString(json, "type")); - button.setMediaId(GsonHelper.getString(json, "media_id")); - button.setArticleId(GsonHelper.getString(json, "article_id")); - button.setAppId(GsonHelper.getString(json, "appid")); - button.setPagePath(GsonHelper.getString(json, "pagepath")); + button.setName(GsonHelper.getString(json, FIELD_NAME)); + button.setKey(GsonHelper.getString(json, FIELD_KEY)); + button.setUrl(GsonHelper.getString(json, FIELD_URL)); + button.setType(GsonHelper.getString(json, FIELD_TYPE)); + button.setMediaId(GsonHelper.getString(json, FIELD_MEDIA_ID)); + button.setArticleId(GsonHelper.getString(json, FIELD_ARTICLE_ID)); + button.setAppId(GsonHelper.getString(json, FIELD_APP_ID)); + button.setPagePath(GsonHelper.getString(json, FIELD_PAGE_PATH)); return button; } diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/json/WxNetCheckResultGsonAdapter.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/json/WxNetCheckResultGsonAdapter.java index 65c15fbc38..61492cbc7a 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/json/WxNetCheckResultGsonAdapter.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/json/WxNetCheckResultGsonAdapter.java @@ -20,7 +20,7 @@ public WxNetCheckResult deserialize(JsonElement json, Type typeOfT, JsonDeserial JsonArray dnssJson = json.getAsJsonObject().get("dns").getAsJsonArray(); List dnsInfoList = new ArrayList<>(); - if (dnssJson != null && dnssJson.size() > 0) { + if (dnssJson != null && !dnssJson.isEmpty()) { for (int i = 0; i < dnssJson.size(); i++) { JsonObject buttonJson = dnssJson.get(i).getAsJsonObject(); WxNetCheckResult.WxNetCheckDnsInfo dnsInfo = new WxNetCheckResult.WxNetCheckDnsInfo(); @@ -32,7 +32,7 @@ public WxNetCheckResult deserialize(JsonElement json, Type typeOfT, JsonDeserial JsonArray pingsJson = json.getAsJsonObject().get("ping").getAsJsonArray(); List pingInfoList = new ArrayList<>(); - if (pingsJson != null && pingsJson.size() > 0) { + if (pingsJson != null && !pingsJson.isEmpty()) { for (int i = 0; i < pingsJson.size(); i++) { JsonObject pingJson = pingsJson.get(i).getAsJsonObject(); WxNetCheckResult.WxNetCheckPingInfo pingInfo = new WxNetCheckResult.WxNetCheckPingInfo(); diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/locks/RedisTemplateSimpleDistributedLock.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/locks/RedisTemplateSimpleDistributedLock.java index 214b4547b0..3f5ce4d692 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/locks/RedisTemplateSimpleDistributedLock.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/locks/RedisTemplateSimpleDistributedLock.java @@ -1,16 +1,11 @@ package me.chanjar.weixin.common.util.locks; import lombok.Getter; -import org.springframework.data.redis.connection.RedisStringCommands; -import org.springframework.data.redis.core.RedisCallback; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.data.redis.core.script.DefaultRedisScript; import org.springframework.data.redis.core.script.RedisScript; -import org.springframework.data.redis.core.types.Expiration; -import java.nio.charset.StandardCharsets; -import java.util.Arrays; -import java.util.List; +import java.util.Collections; import java.util.UUID; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Condition; @@ -66,19 +61,20 @@ public void lockInterruptibly() throws InterruptedException { @Override public boolean tryLock() { String value = valueThreadLocal.get(); - if (value == null || value.length() == 0) { + if (value == null || value.isEmpty()) { value = UUID.randomUUID().toString(); valueThreadLocal.set(value); } - final byte[] keyBytes = key.getBytes(StandardCharsets.UTF_8); - final byte[] valueBytes = value.getBytes(StandardCharsets.UTF_8); - List redisResults = redisTemplate.executePipelined((RedisCallback) connection -> { - connection.set(keyBytes, valueBytes, Expiration.milliseconds(leaseMilliseconds), RedisStringCommands.SetOption.SET_IF_ABSENT); - connection.get(keyBytes); - return null; - }); - Object currentLockSecret = redisResults.size() > 1 ? redisResults.get(1) : redisResults.get(0); - return currentLockSecret != null && currentLockSecret.toString().equals(value); + + // Use high-level StringRedisTemplate API to ensure consistent key serialization + Boolean lockAcquired = redisTemplate.opsForValue().setIfAbsent(key, value, leaseMilliseconds, TimeUnit.MILLISECONDS); + if (Boolean.TRUE.equals(lockAcquired)) { + return true; + } + + // Check if we already hold the lock (reentrant behavior) + String currentValue = redisTemplate.opsForValue().get(key); + return value.equals(currentValue); } @Override @@ -98,8 +94,8 @@ public boolean tryLock(long time, TimeUnit unit) throws InterruptedException { public void unlock() { if (valueThreadLocal.get() != null) { // 提示: 必须指定returnType, 类型: 此处必须为Long, 不能是Integer - RedisScript script = new DefaultRedisScript("if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end", Long.class); - redisTemplate.execute(script, Arrays.asList(key), valueThreadLocal.get()); + RedisScript script = new DefaultRedisScript<>("if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end", Long.class); + redisTemplate.execute(script, Collections.singletonList(key), valueThreadLocal.get()); valueThreadLocal.remove(); } } diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/res/StringManager.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/res/StringManager.java index e5bdb38804..fd2f13a553 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/res/StringManager.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/res/StringManager.java @@ -102,7 +102,7 @@ private StringManager(String packageName, Locale locale) { * * @param packageName The package name */ - public static final synchronized StringManager getManager( + public static synchronized StringManager getManager( String packageName) { return getManager(packageName, Locale.getDefault()); } @@ -115,7 +115,7 @@ public static final synchronized StringManager getManager( * @param packageName The package name * @param locale The Locale */ - public static final synchronized StringManager getManager( + public static synchronized StringManager getManager( String packageName, Locale locale) { Map map = MANAGERS.get(packageName); diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/xml/IntegerArrayConverter.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/xml/IntegerArrayConverter.java index 3532fcab08..710547c746 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/xml/IntegerArrayConverter.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/xml/IntegerArrayConverter.java @@ -25,7 +25,7 @@ public String toString(Object obj) { @Override public Object fromString(String str) { - if (str == null || str.length() == 0) { + if (str == null || str.isEmpty()) { return null; } diff --git a/weixin-java-common/src/test/java/me/chanjar/weixin/common/util/http/HttpResponseProxyTest.java b/weixin-java-common/src/test/java/me/chanjar/weixin/common/util/http/HttpResponseProxyTest.java index 4d188b50bc..1b20b98d74 100644 --- a/weixin-java-common/src/test/java/me/chanjar/weixin/common/util/http/HttpResponseProxyTest.java +++ b/weixin-java-common/src/test/java/me/chanjar/weixin/common/util/http/HttpResponseProxyTest.java @@ -1,6 +1,7 @@ package me.chanjar.weixin.common.util.http; import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.common.util.http.apache.ApacheHttpResponseProxy; import org.testng.annotations.Test; import static org.testng.Assert.*; diff --git a/weixin-java-common/src/test/java/me/chanjar/weixin/common/util/http/apache/DefaultApacheHttpClientBuilderTest.java b/weixin-java-common/src/test/java/me/chanjar/weixin/common/util/http/apache/DefaultApacheHttpClientBuilderTest.java index 08de63167b..7296d29d44 100644 --- a/weixin-java-common/src/test/java/me/chanjar/weixin/common/util/http/apache/DefaultApacheHttpClientBuilderTest.java +++ b/weixin-java-common/src/test/java/me/chanjar/weixin/common/util/http/apache/DefaultApacheHttpClientBuilderTest.java @@ -79,13 +79,13 @@ void testHttpClientWithInterceptor() throws Exception { HttpUriRequest request = new HttpGet("http://localhost:8080"); HttpContext context = HttpClientContext.create(); try (CloseableHttpResponse resp = client.execute(request, context)) { - Assert.assertEquals("requestInterceptor1", context.getAttribute("interceptor_called"), "成功调用 requestInterceptor1 并向 content 中写入了数据"); + Assert.assertEquals(context.getAttribute("interceptor_called"), "requestInterceptor1", "成功调用 requestInterceptor1 并向 content 中写入了数据"); // 测试拦截器执行顺序 - Assert.assertEquals("requestInterceptor1", interceptorOrder.get(0)); - Assert.assertEquals("requestInterceptor2", interceptorOrder.get(1)); - Assert.assertEquals("responseInterceptor1", interceptorOrder.get(2)); - Assert.assertEquals("responseInterceptor2", interceptorOrder.get(3)); + Assert.assertEquals(interceptorOrder.get(0), "requestInterceptor1"); + Assert.assertEquals(interceptorOrder.get(1), "requestInterceptor2"); + Assert.assertEquals(interceptorOrder.get(2), "responseInterceptor1"); + Assert.assertEquals(interceptorOrder.get(3), "responseInterceptor2"); } } } diff --git a/weixin-java-common/src/test/java/me/chanjar/weixin/common/util/http/apache/SSLConfigurationTest.java b/weixin-java-common/src/test/java/me/chanjar/weixin/common/util/http/apache/SSLConfigurationTest.java new file mode 100644 index 0000000000..cecda5ca54 --- /dev/null +++ b/weixin-java-common/src/test/java/me/chanjar/weixin/common/util/http/apache/SSLConfigurationTest.java @@ -0,0 +1,116 @@ +package me.chanjar.weixin.common.util.http.apache; + +import org.apache.http.conn.ssl.SSLConnectionSocketFactory; +import org.apache.http.impl.client.CloseableHttpClient; +import org.testng.Assert; +import org.testng.annotations.Test; + +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSocket; +import javax.net.ssl.SSLSocketFactory; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.util.Arrays; +import java.util.List; + +/** + * 测试SSL配置,特别是TLS协议版本配置 + * Test SSL configuration, especially TLS protocol version configuration + */ +public class SSLConfigurationTest { + + @Test + public void testDefaultTLSProtocols() throws Exception { + // Create a new instance to check the default configuration + Class builderClass = DefaultApacheHttpClientBuilder.class; + Object builder = builderClass.getDeclaredMethod("get").invoke(null); + + // 验证默认支持的TLS协议版本包含现代版本 + Field supportedProtocolsField = builderClass.getDeclaredField("supportedProtocols"); + supportedProtocolsField.setAccessible(true); + String[] supportedProtocols = (String[]) supportedProtocolsField.get(builder); + + List protocolList = Arrays.asList(supportedProtocols); + + System.out.println("Default supported TLS protocols: " + Arrays.toString(supportedProtocols)); + + // 主要验证:应该支持TLS 1.2和/或1.3 (现代安全版本) + // Main validation: Should support TLS 1.2 and/or 1.3 (modern secure versions) + Assert.assertTrue(protocolList.contains("TLSv1.2"), "Should support TLS 1.2"); + Assert.assertTrue(protocolList.contains("TLSv1.3"), "Should support TLS 1.3"); + + // 验证不再是只有TLS 1.0 (这是导致原问题的根本原因) + // Verify it's no longer just TLS 1.0 (which was the root cause of the original issue) + Assert.assertTrue(protocolList.size() > 0, "Should support at least one TLS version"); + boolean hasModernTLS = protocolList.contains("TLSv1.2") || protocolList.contains("TLSv1.3"); + Assert.assertTrue(hasModernTLS, "Should support at least one modern TLS version (1.2 or 1.3)"); + + // 验证不是原来的老旧配置 (只有 "TLSv1") + // Verify it's not the old configuration (only "TLSv1") + boolean isOldConfig = protocolList.size() == 1 && protocolList.contains("TLSv1"); + Assert.assertFalse(isOldConfig, "Should not be the old configuration that only supported TLS 1.0"); + } + + @Test + public void testCustomTLSProtocols() throws Exception { + // Test that we can set custom TLS protocols + String[] customProtocols = {"TLSv1.2", "TLSv1.3"}; + + // Create a new builder instance using reflection to avoid singleton issues in testing + Class builderClass = DefaultApacheHttpClientBuilder.class; + Constructor constructor = builderClass.getDeclaredConstructor(); + constructor.setAccessible(true); + Object builder = constructor.newInstance(); + + // Set custom protocols + builderClass.getMethod("supportedProtocols", String[].class).invoke(builder, (Object) customProtocols); + + Field supportedProtocolsField = builderClass.getDeclaredField("supportedProtocols"); + supportedProtocolsField.setAccessible(true); + String[] actualProtocols = (String[]) supportedProtocolsField.get(builder); + + Assert.assertEquals(actualProtocols, customProtocols, "Custom protocols should be set correctly"); + + System.out.println("Custom supported TLS protocols: " + Arrays.toString(actualProtocols)); + } + + @Test + public void testSSLContextCreation() throws Exception { + DefaultApacheHttpClientBuilder builder = DefaultApacheHttpClientBuilder.get(); + + // 构建HTTP客户端以验证SSL工厂是否正确创建 + CloseableHttpClient client = builder.build(); + Assert.assertNotNull(client, "HTTP client should be created successfully"); + + // 验证SSL上下文支持现代TLS协议 + SSLContext sslContext = SSLContext.getDefault(); + SSLSocketFactory socketFactory = sslContext.getSocketFactory(); + + // 创建一个SSL socket来检查支持的协议 + try (SSLSocket socket = (SSLSocket) socketFactory.createSocket()) { + String[] supportedProtocols = socket.getSupportedProtocols(); + List supportedList = Arrays.asList(supportedProtocols); + + // JVM应该支持TLS 1.2(在JDK 8+中默认可用) + Assert.assertTrue(supportedList.contains("TLSv1.2"), + "JVM should support TLS 1.2. Supported protocols: " + Arrays.toString(supportedProtocols)); + + System.out.println("JVM supported TLS protocols: " + Arrays.toString(supportedProtocols)); + } + + client.close(); + } + + @Test + public void testBuilderChaining() { + DefaultApacheHttpClientBuilder builder = DefaultApacheHttpClientBuilder.get(); + + // 测试方法链调用 + ApacheHttpClientBuilder result = builder + .supportedProtocols(new String[]{"TLSv1.2", "TLSv1.3"}) + .httpProxyHost("proxy.example.com") + .httpProxyPort(8080); + + Assert.assertSame(result, builder, "Builder methods should return the same instance for method chaining"); + } +} \ No newline at end of file diff --git a/weixin-java-common/src/test/java/me/chanjar/weixin/common/util/http/apache/SSLIntegrationTest.java b/weixin-java-common/src/test/java/me/chanjar/weixin/common/util/http/apache/SSLIntegrationTest.java new file mode 100644 index 0000000000..e732360e87 --- /dev/null +++ b/weixin-java-common/src/test/java/me/chanjar/weixin/common/util/http/apache/SSLIntegrationTest.java @@ -0,0 +1,73 @@ +package me.chanjar.weixin.common.util.http.apache; + +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.impl.client.CloseableHttpClient; +import org.testng.Assert; +import org.testng.annotations.Test; + +/** + * 集成测试 - 验证SSL配置可以正常访问HTTPS网站 + * Integration test - Verify SSL configuration can access HTTPS websites properly + */ +public class SSLIntegrationTest { + + @Test + public void testHTTPSConnectionWithModernTLS() throws Exception { + DefaultApacheHttpClientBuilder builder = DefaultApacheHttpClientBuilder.get(); + + // 使用默认配置(支持现代TLS版本)创建客户端 + CloseableHttpClient client = builder.build(); + + // 测试访问一个需要现代TLS的网站 + // Test accessing a website that requires modern TLS + HttpGet httpGet = new HttpGet("https://api.weixin.qq.com/"); + + try (CloseableHttpResponse response = client.execute(httpGet)) { + // 验证能够成功建立HTTPS连接(不管响应内容是什么) + // Verify that HTTPS connection can be established successfully (regardless of response content) + Assert.assertNotNull(response, "Should be able to establish HTTPS connection"); + Assert.assertNotNull(response.getStatusLine(), "Should receive a status response"); + + int statusCode = response.getStatusLine().getStatusCode(); + // 任何HTTP状态码都表示SSL握手成功 + // Any HTTP status code indicates successful SSL handshake + Assert.assertTrue(statusCode > 0, "Should receive a valid HTTP status code, got: " + statusCode); + + System.out.println("HTTPS connection test successful. Status: " + response.getStatusLine()); + } catch (javax.net.ssl.SSLHandshakeException e) { + Assert.fail("SSL handshake should not fail with modern TLS configuration. Error: " + e.getMessage()); + } finally { + client.close(); + } + } + + @Test + public void testCustomTLSConfiguration() throws Exception { + DefaultApacheHttpClientBuilder builder = DefaultApacheHttpClientBuilder.get(); + + // 配置为只支持TLS 1.2和1.3(最安全的配置) + // Configure to only support TLS 1.2 and 1.3 (most secure configuration) + builder.supportedProtocols(new String[]{"TLSv1.2", "TLSv1.3"}); + + CloseableHttpClient client = builder.build(); + + // 测试这个配置是否能正常工作 + HttpGet httpGet = new HttpGet("https://httpbin.org/get"); + + try (CloseableHttpResponse response = client.execute(httpGet)) { + Assert.assertNotNull(response, "Should be able to establish HTTPS connection with TLS 1.2/1.3"); + int statusCode = response.getStatusLine().getStatusCode(); + Assert.assertEquals(statusCode, 200, "Should get HTTP 200 response from httpbin.org"); + + System.out.println("Custom TLS configuration test successful. Status: " + response.getStatusLine()); + } catch (javax.net.ssl.SSLHandshakeException e) { + // 这个测试可能会因为网络环境而失败,所以我们只是记录警告 + // This test might fail due to network environment, so we just log a warning + System.out.println("Warning: SSL handshake failed with custom TLS config: " + e.getMessage()); + System.out.println("This might be due to network restrictions in the test environment."); + } finally { + client.close(); + } + } +} \ No newline at end of file diff --git a/weixin-java-common/src/test/java/me/chanjar/weixin/common/util/locks/RedisTemplateSimpleDistributedLockSerializationTest.java b/weixin-java-common/src/test/java/me/chanjar/weixin/common/util/locks/RedisTemplateSimpleDistributedLockSerializationTest.java new file mode 100644 index 0000000000..ea4a131d37 --- /dev/null +++ b/weixin-java-common/src/test/java/me/chanjar/weixin/common/util/locks/RedisTemplateSimpleDistributedLockSerializationTest.java @@ -0,0 +1,100 @@ +package me.chanjar.weixin.common.util.locks; + +import org.springframework.data.redis.connection.jedis.JedisConnectionFactory; +import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.data.redis.serializer.StringRedisSerializer; +import org.testng.annotations.BeforeTest; +import org.testng.annotations.Test; + +import static org.testng.Assert.*; + +/** + * 测试 RedisTemplateSimpleDistributedLock 在自定义 Key 序列化时的兼容性 + * + * 这个测试验证修复后的实现确保 tryLock 和 unlock 使用一致的键序列化方式 + */ +@Test(enabled = false) // 默认禁用,需要Redis实例才能运行 +public class RedisTemplateSimpleDistributedLockSerializationTest { + + private RedisTemplateSimpleDistributedLock redisLock; + private StringRedisTemplate redisTemplate; + + @BeforeTest + public void init() { + JedisConnectionFactory connectionFactory = new JedisConnectionFactory(); + connectionFactory.setHostName("127.0.0.1"); + connectionFactory.setPort(6379); + connectionFactory.afterPropertiesSet(); + + // 创建一个带自定义键序列化的 StringRedisTemplate + StringRedisTemplate redisTemplate = new StringRedisTemplate(connectionFactory); + + // 使用自定义键序列化器,模拟在键前面添加前缀的场景 + redisTemplate.setKeySerializer(new StringRedisSerializer() { + @Override + public byte[] serialize(String string) { + if (string == null) return null; + // 添加 "System:" 前缀,模拟用户自定义的键序列化 + return super.serialize("System:" + string); + } + + @Override + public String deserialize(byte[] bytes) { + if (bytes == null) return null; + String result = super.deserialize(bytes); + // 移除前缀进行反序列化 + return result != null && result.startsWith("System:") ? result.substring(7) : result; + } + }); + + this.redisTemplate = redisTemplate; + this.redisLock = new RedisTemplateSimpleDistributedLock(redisTemplate, "test_lock_key", 60000); + } + + @Test(description = "测试自定义键序列化器下的锁操作一致性") + public void testLockConsistencyWithCustomKeySerializer() { + // 1. 获取锁应该成功 + assertTrue(redisLock.tryLock(), "第一次获取锁应该成功"); + assertNotNull(redisLock.getLockSecretValue(), "锁值应该存在"); + + // 2. 验证键已正确存储(通过 redisTemplate 直接查询) + String actualValue = redisTemplate.opsForValue().get("test_lock_key"); + assertEquals(actualValue, redisLock.getLockSecretValue(), "通过 redisTemplate 查询的值应该与锁值相同"); + + // 3. 再次尝试获取同一把锁应该成功(可重入) + assertTrue(redisLock.tryLock(), "可重入锁应该再次获取成功"); + + // 4. 释放锁应该成功 + redisLock.unlock(); + assertNull(redisLock.getLockSecretValue(), "释放锁后锁值应该为空"); + + // 5. 验证键已被删除 + actualValue = redisTemplate.opsForValue().get("test_lock_key"); + assertNull(actualValue, "释放锁后 Redis 中的键应该被删除"); + + // 6. 释放已释放的锁应该是安全的 + redisLock.unlock(); // 不应该抛出异常 + } + + @Test(description = "测试不同线程使用相同键的锁排他性") + public void testLockExclusivityWithCustomKeySerializer() throws InterruptedException { + // 第一个锁实例获取锁 + assertTrue(redisLock.tryLock(), "第一个锁实例应该成功获取锁"); + + // 创建第二个锁实例使用相同的键 + RedisTemplateSimpleDistributedLock anotherLock = new RedisTemplateSimpleDistributedLock( + redisTemplate, "test_lock_key", 60000); + + // 第二个锁实例不应该能获取锁 + assertFalse(anotherLock.tryLock(), "第二个锁实例不应该能获取已被占用的锁"); + + // 释放第一个锁 + redisLock.unlock(); + + // 现在第二个锁实例应该能获取锁 + assertTrue(anotherLock.tryLock(), "第一个锁释放后,第二个锁实例应该能获取锁"); + + // 清理 + anotherLock.unlock(); + } +} \ No newline at end of file diff --git a/weixin-java-common/src/test/java/me/chanjar/weixin/common/util/locks/RedisTemplateSimpleDistributedLockTest.java b/weixin-java-common/src/test/java/me/chanjar/weixin/common/util/locks/RedisTemplateSimpleDistributedLockTest.java index 4b65e31f0b..b278eeafa0 100644 --- a/weixin-java-common/src/test/java/me/chanjar/weixin/common/util/locks/RedisTemplateSimpleDistributedLockTest.java +++ b/weixin-java-common/src/test/java/me/chanjar/weixin/common/util/locks/RedisTemplateSimpleDistributedLockTest.java @@ -1,8 +1,10 @@ package me.chanjar.weixin.common.util.locks; import lombok.extern.slf4j.Slf4j; +import org.jetbrains.annotations.NotNull; import org.springframework.data.redis.connection.jedis.JedisConnectionFactory; import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.data.redis.serializer.StringRedisSerializer; import org.testng.annotations.BeforeTest; import org.testng.annotations.Test; @@ -13,9 +15,10 @@ import static org.testng.Assert.*; @Slf4j -@Test(enabled = false) +@Test(enabled = true) public class RedisTemplateSimpleDistributedLockTest { + private static final String KEY_PREFIX = "System:"; RedisTemplateSimpleDistributedLock redisLock; StringRedisTemplate redisTemplate; @@ -29,6 +32,28 @@ public void init() { connectionFactory.setPort(6379); connectionFactory.afterPropertiesSet(); StringRedisTemplate redisTemplate = new StringRedisTemplate(connectionFactory); + // 自定义序列化器,为 key 自动加前缀 + redisTemplate.setKeySerializer(new StringRedisSerializer() { + @NotNull + @Override + public byte[] serialize(String string) { + if (string == null) { + return super.serialize(null); + } + // 添加前缀 + return super.serialize(KEY_PREFIX + string); + } + + @NotNull + @Override + public String deserialize(byte[] bytes) { + String key = super.deserialize(bytes); + if (key.startsWith(KEY_PREFIX)) { + return key.substring(KEY_PREFIX.length()); + } + return key; + } + }); this.redisTemplate = redisTemplate; this.redisLock = new RedisTemplateSimpleDistributedLock(redisTemplate, 60000); this.lockCurrentExecuteCounter = new AtomicInteger(0); diff --git a/weixin-java-cp/pom.xml b/weixin-java-cp/pom.xml index 467ba98858..6ee31e759b 100644 --- a/weixin-java-cp/pom.xml +++ b/weixin-java-cp/pom.xml @@ -7,7 +7,7 @@ com.github.binarywang wx-java - 4.7.2.B + 4.7.8.B weixin-java-cp @@ -30,6 +30,11 @@ okhttp provided + + org.apache.httpcomponents.client5 + httpclient5 + provided + redis.clients jedis @@ -84,7 +89,7 @@ org.bouncycastle bcprov-jdk18on - 1.78.1 + 1.80 @@ -101,7 +106,6 @@ com.fasterxml.jackson.core jackson-core - 2.13.4 test diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpAgentWorkBenchService.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpAgentWorkBenchService.java index c50aa2f5fc..67c57a8a88 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpAgentWorkBenchService.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpAgentWorkBenchService.java @@ -36,4 +36,12 @@ public interface WxCpAgentWorkBenchService { * @throws WxErrorException the wx error exception */ void setWorkBenchData(WxCpAgentWorkBench wxCpAgentWorkBench) throws WxErrorException; + + /** + * Batch sets work bench data. + * + * @param wxCpAgentWorkBench the wx cp agent work bench + * @throws WxErrorException the wx error exception + */ + void batchSetWorkBenchData(WxCpAgentWorkBench wxCpAgentWorkBench) throws WxErrorException; } diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpGroupRobotService.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpGroupRobotService.java index e396ed58ac..c1a8d56255 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpGroupRobotService.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpGroupRobotService.java @@ -70,6 +70,23 @@ public interface WxCpGroupRobotService { */ void sendMarkdown(String webhookUrl, String content) throws WxErrorException; + /** + * 发送markdown_v2类型的消息 + * + * @param content markdown内容,最长不超过4096个字节,必须是utf8编码 + * @throws WxErrorException 异常 + */ + void sendMarkdownV2(String content) throws WxErrorException; + + /** + * 发送markdown_v2类型的消息 + * + * @param webhookUrl webhook地址 + * @param content markdown内容,最长不超过4096个字节,必须是utf8编码 + * @throws WxErrorException 异常 + */ + void sendMarkdownV2(String webhookUrl, String content) throws WxErrorException; + /** * 发送image类型的消息 * 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 86b342f2fc..5a53829dc0 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 @@ -75,6 +75,19 @@ public interface WxCpKfService { */ WxCpKfServicerOpResp addServicer(String openKfid, List userIdList) throws WxErrorException; + /** + * 接待人员管理 + * 添加指定客服账号的接待人员,每个客服账号目前最多可添加2000个接待人员,20个部门。 + * userid_list和department_id_list至少需要填其中一个 + * + * @param openKfid 客服帐号ID + * @param userIdList 接待人员userid列表。第三方应用填密文userid,即open_userid 可填充个数:1 ~ 100。超过100个需分批调用。 + * @param departmentIdList 接待人员部门id列表 可填充个数:0 ~ 20。 + * @return 添加客服账号结果 wx cp kf servicer op resp + * @throws WxErrorException 异常 + */ + WxCpKfServicerOpResp addServicer(String openKfid, List userIdList,List departmentIdList) throws WxErrorException; + /** * 接待人员管理 * 从客服帐号删除接待人员 @@ -86,6 +99,19 @@ public interface WxCpKfService { */ WxCpKfServicerOpResp delServicer(String openKfid, List userIdList) throws WxErrorException; + /** + * 接待人员管理 + * 从客服帐号删除接待人员 + * userid_list和department_id_list至少需要填其中一个 + * + * @param openKfid 客服帐号ID + * @param userIdList 接待人员userid列表。第三方应用填密文userid,即open_userid 可填充个数:1 ~ 100。超过100个需分批调用。 + * @param departmentIdList 接待人员部门id列表 可填充个数:0 ~ 100。超过100个需分批调用。 + * @return 删除客服账号结果 wx cp kf servicer op resp + * @throws WxErrorException 异常 + */ + WxCpKfServicerOpResp delServicer(String openKfid, List userIdList, List departmentIdList) throws WxErrorException; + /** * 接待人员管理 * 获取某个客服帐号的接待人员列表 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 82f6db9178..e874b26f42 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 @@ -2,6 +2,8 @@ import me.chanjar.weixin.common.bean.result.WxMediaUploadResult; import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.cp.bean.media.MediaUploadByUrlReq; +import me.chanjar.weixin.cp.bean.media.MediaUploadByUrlResult; import java.io.File; import java.io.IOException; @@ -133,4 +135,21 @@ WxMediaUploadResult upload(String mediaType, String filename, String url) * @throws WxErrorException the wx error exception */ String uploadImg(File file) throws WxErrorException; + + /** + * 生成异步上传任务 + * 跟上传临时素材拿到的media_id使用场景是不通用的,目前适配的接口如下:https://developer.work.weixin.qq.com/document/path/96488#%E4%BD%BF%E7%94%A8%E5%9C%BA%E6%99%AF%E8%AF%B4%E6%98%8E + * @param req 请求参数 + * @return 返回异步任务id + * @throws WxErrorException the wx error exception + */ + String uploadByUrl(MediaUploadByUrlReq req) throws WxErrorException; + + /** + * 查询异步任务结果 + * @param jobId 任务id。最长为128字节,60分钟内有效 + * @return 返回异步任务结果 + * @throws WxErrorException the wx error exception + */ + MediaUploadByUrlResult uploadByUrl(String jobId) throws WxErrorException; } diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpMeetingService.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpMeetingService.java index e3ee866118..d761f99d0b 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpMeetingService.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpMeetingService.java @@ -5,8 +5,6 @@ import me.chanjar.weixin.cp.bean.oa.meeting.WxCpMeetingUpdateResult; import me.chanjar.weixin.cp.bean.oa.meeting.WxCpUserMeetingIdResult; -import java.util.List; - /** * 企业微信日程接口. * 企业和开发者通过会议接口可以便捷地预定及管理会议,用于小组周会、部门例会等场景。 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 4647e0ed3f..ee57107b5c 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 @@ -22,7 +22,7 @@ public interface WxCpOaService { * * 请求方式:POST(HTTPS) * 请求地址: https://qyapi.weixin.qq.com/cgi-bin/oa/applyevent?access_token=ACCESS_TOKEN - * 文档地址:https://work.weixin.qq.com/api/doc/90000/90135/91853 + * 文档地址 * * * @param request 请求 @@ -34,7 +34,7 @@ public interface WxCpOaService { /** *
    *  获取打卡数据
-   *  API doc : https://work.weixin.qq.com/api/doc#90000/90135/90262
+   *  文档地址
    * 
* * @param openCheckinDataType 打卡类型。1:上下班打卡;2:外出打卡;3:全部打卡 @@ -50,7 +50,7 @@ List getCheckinData(Integer openCheckinDataType, Date startTime /** *
    *   获取打卡规则
-   *   API doc : https://work.weixin.qq.com/api/doc#90000/90135/90263
+   *  文档地址
    * 
* * @param datetime 需要获取规则的当天日期 @@ -64,7 +64,7 @@ List getCheckinData(Integer openCheckinDataType, Date startTime /** *
    *   获取企业所有打卡规则
-   *   API doc : https://work.weixin.qq.com/api/doc/90000/90135/93384
+   * 文档地址
    * 
* * @return 打卡规则列表 crop checkin option @@ -82,7 +82,7 @@ List getCheckinData(Integer openCheckinDataType, Date startTime * * 一次拉取调用最多拉取100个审批记录,可以通过多次拉取的方式来满足需求,但调用频率不可超过600次/分。 * - * API doc : https://work.weixin.qq.com/api/doc/90000/90135/91816 + * 文档地址 * * * @param startTime 开始时间 @@ -121,7 +121,7 @@ WxCpApprovalInfo getApprovalInfo(@NonNull Date startTime, @NonNull Date endTime, * * 一次拉取调用最多拉取100个审批记录,可以通过多次拉取的方式来满足需求,但调用频率不可超过600次/分。 * - * API doc : https://work.weixin.qq.com/api/doc/90000/90135/91816 + * 文档地址 * * 1 接口频率限制 600次/分钟 * 2 请求的参数endtime需要大于startime, 起始时间跨度不能超过31天; @@ -146,7 +146,7 @@ WxCpApprovalInfo getApprovalInfo(@NonNull Date startTime, @NonNull Date endTime, * * 企业可通过审批应用或自建应用Secret调用本接口,根据审批单号查询企业微信“审批应用”的审批申请详情。 * - * API Doc : https://work.weixin.qq.com/api/doc/90000/90135/91983 + * 文档地址 * * * @param spNo 审批单编号。 diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpOaWeDriveService.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpOaWeDriveService.java index 8c3efbc1ab..e7217616b8 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpOaWeDriveService.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpOaWeDriveService.java @@ -48,12 +48,11 @@ public interface WxCpOaWeDriveService { * 请求方式:POST(HTTPS) * 请求地址: ... * - * @param userId the user id * @param spaceId the space id * @return wx cp base resp * @throws WxErrorException the wx error exception */ - WxCpBaseResp spaceDismiss(@NonNull String userId, @NonNull String spaceId) throws WxErrorException; + WxCpBaseResp spaceDismiss(@NonNull String spaceId) throws WxErrorException; /** * 获取空间信息 @@ -62,12 +61,11 @@ public interface WxCpOaWeDriveService { * 请求方式:POST(HTTPS) * 请求地址: ... * - * @param userId the user id * @param spaceId the space id * @return wx cp space info * @throws WxErrorException the wx error exception */ - WxCpSpaceInfo spaceInfo(@NonNull String userId, @NonNull String spaceId) throws WxErrorException; + WxCpSpaceInfo spaceInfo(@NonNull String spaceId) throws WxErrorException; /** * 添加成员/部门 @@ -115,12 +113,11 @@ public interface WxCpOaWeDriveService { * 请求方式:POST(HTTPS) * 请求地址: ... * - * @param userId the user id * @param spaceId the space id * @return wx cp space share * @throws WxErrorException the wx error exception */ - WxCpSpaceShare spaceShare(@NonNull String userId, @NonNull String spaceId) throws WxErrorException; + WxCpSpaceShare spaceShare(@NonNull String spaceId) throws WxErrorException; /** * 获取文件列表 @@ -155,18 +152,18 @@ public interface WxCpOaWeDriveService { * 请求方式:POST(HTTPS) * 请求地址: ... * - * @param fileId 文件fileid(只支持下载普通文件,不支持下载文件夹或微文档) + * @param fileId 文件fileid(只支持下载普通文件,不支持下载文件夹或微文档) * @param selectedTicket 微盘和文件选择器jsapi返回的selectedTicket。若填此参数,则不需要填fileid。 * @return { - * "errcode": 0, - * "errmsg": "ok", - * "download_url": "DOWNLOAD_URL", - * "cookie_name": "COOKIE_NAME", - * "cookie_value": "COOKIE_VALUE" + * "errcode": 0, + * "errmsg": "ok", + * "download_url": "DOWNLOAD_URL", + * "cookie_name": "COOKIE_NAME", + * "cookie_value": "COOKIE_VALUE" * } * @throws WxErrorException the wx error exception */ - WxCpFileDownload fileDownload( String fileId, String selectedTicket) throws WxErrorException; + WxCpFileDownload fileDownload(String fileId, String selectedTicket) throws WxErrorException; /** * 重命名文件 @@ -271,14 +268,13 @@ WxCpFileCreate fileCreate(@NonNull String spaceId, @NonNull String fatherId, @No * 请求方式:POST(HTTPS) * 请求地址: ... * - * @param userId the user id * @param fileId the file id * @param authScope the auth scope * @param auth the auth * @return wx cp base resp * @throws WxErrorException the wx error exception */ - WxCpBaseResp fileSetting(@NonNull String userId, @NonNull String fileId, @NonNull Integer authScope, Integer auth) throws WxErrorException; + WxCpBaseResp fileSetting(@NonNull String fileId, @NonNull Integer authScope, Integer auth) throws WxErrorException; /** * 获取分享链接 @@ -287,11 +283,10 @@ WxCpFileCreate fileCreate(@NonNull String spaceId, @NonNull String fatherId, @No * 请求方式:POST(HTTPS) * 请求地址: ... * - * @param userId the user id * @param fileId the file id * @return wx cp file share * @throws WxErrorException the wx error exception */ - WxCpFileShare fileShare(@NonNull String userId, @NonNull String fileId) throws WxErrorException; + WxCpFileShare fileShare(@NonNull String fileId) throws WxErrorException; } diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/BaseWxCpServiceImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/BaseWxCpServiceImpl.java index f613f6138c..d0b7441d90 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/BaseWxCpServiceImpl.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/BaseWxCpServiceImpl.java @@ -110,7 +110,7 @@ public boolean checkSignature(String msgSignature, String timestamp, String nonc return SHA1.gen(this.configStorage.getToken(), timestamp, nonce, data) .equals(msgSignature); } catch (Exception e) { - log.error("Checking signature failed, and the reason is :" + e.getMessage()); + log.error("Checking signature failed, and the reason is :{}", e.getMessage()); return false; } } diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpAgentWorkBenchServiceImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpAgentWorkBenchServiceImpl.java index bb5c191e96..b0bbb38642 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpAgentWorkBenchServiceImpl.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpAgentWorkBenchServiceImpl.java @@ -38,4 +38,10 @@ public void setWorkBenchData(WxCpAgentWorkBench wxCpAgentWorkBench) throws WxErr final String url = String.format(this.mainService.getWxCpConfigStorage().getApiUrl(WORKBENCH_DATA_SET)); this.mainService.post(url, wxCpAgentWorkBench.toUserDataString()); } + + @Override + public void batchSetWorkBenchData(WxCpAgentWorkBench wxCpAgentWorkBench) throws WxErrorException { + final String url = String.format(this.mainService.getWxCpConfigStorage().getApiUrl(WORKBENCH_BATCH_DATA_SET)); + this.mainService.post(url, wxCpAgentWorkBench.toBatchUserDataString()); + } } 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 be754f229b..48bd952a83 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 @@ -8,7 +8,6 @@ import me.chanjar.weixin.cp.api.WxCpCorpGroupService; import me.chanjar.weixin.cp.api.WxCpService; import me.chanjar.weixin.cp.bean.corpgroup.WxCpCorpGroupCorp; -import me.chanjar.weixin.cp.bean.corpgroup.WxCpCorpGroupCorpListAppShareInfoResp; import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder; import java.util.List; diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpGroupRobotServiceImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpGroupRobotServiceImpl.java index 21246d2415..8373c6c8ee 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpGroupRobotServiceImpl.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpGroupRobotServiceImpl.java @@ -42,6 +42,11 @@ public void sendMarkdown(String content) throws WxErrorException { this.sendMarkdown(this.getWebhookUrl(), content); } + @Override + public void sendMarkdownV2(String content) throws WxErrorException { + this.sendMarkdownV2(this.getWebhookUrl(), content); + } + @Override public void sendImage(String base64, String md5) throws WxErrorException { this.sendImage(this.getWebhookUrl(), base64, md5); @@ -70,6 +75,14 @@ public void sendMarkdown(String webhookUrl, String content) throws WxErrorExcept .toJson()); } + @Override + public void sendMarkdownV2(String webhookUrl, String content) throws WxErrorException { + this.cpService.postWithoutToken(webhookUrl, new WxCpGroupRobotMessage() + .setMsgType(GroupRobotMsgType.MARKDOWN_V2) + .setContent(content) + .toJson()); + } + @Override public void sendImage(String webhookUrl, String base64, String md5) throws WxErrorException { this.cpService.postWithoutToken(webhookUrl, new WxCpGroupRobotMessage() diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpKfServiceImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpKfServiceImpl.java index 29e84c516f..be4f2a5850 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpKfServiceImpl.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpKfServiceImpl.java @@ -70,23 +70,62 @@ public WxCpKfAccountLinkResp getAccountLink(WxCpKfAccountLink link) throws WxErr @Override public WxCpKfServicerOpResp addServicer(String openKfid, List userIdList) throws WxErrorException { - return servicerOp(openKfid, userIdList, SERVICER_ADD); + return servicerOp(openKfid, userIdList, null, SERVICER_ADD); + } + + @Override + public WxCpKfServicerOpResp addServicer(String openKfId, List userIdList, List departmentIdList) throws WxErrorException { + validateParameters(SERVICER_ADD, userIdList, departmentIdList); + return servicerOp(openKfId, userIdList, departmentIdList, SERVICER_ADD); } @Override public WxCpKfServicerOpResp delServicer(String openKfid, List userIdList) throws WxErrorException { - return servicerOp(openKfid, userIdList, SERVICER_DEL); + return servicerOp(openKfid, userIdList, null, SERVICER_DEL); } - private WxCpKfServicerOpResp servicerOp(String openKfid, List userIdList, String uri) throws WxErrorException { + @Override + public WxCpKfServicerOpResp delServicer(String openKfid, List userIdList, List departmentIdList) throws WxErrorException { + validateParameters(SERVICER_DEL, userIdList, departmentIdList); + return servicerOp(openKfid, userIdList, departmentIdList, SERVICER_DEL); + } + + private void validateParameters(String uri, List userIdList, List departmentIdList) { + if ((userIdList == null || userIdList.isEmpty()) && (departmentIdList == null || departmentIdList.isEmpty())) { + throw new IllegalArgumentException("userid_list和department_id_list至少需要填其中一个"); + } + if (SERVICER_DEL.equals(uri)) { + if (userIdList != null && userIdList.size() > 100) { + throw new IllegalArgumentException("可填充个数:0 ~ 100。超过100个需分批调用。"); + } + if (departmentIdList != null && departmentIdList.size() > 100) { + throw new IllegalArgumentException("可填充个数:0 ~ 100。超过100个需分批调用。"); + } + } else { + if (userIdList != null && userIdList.size() > 100) { + throw new IllegalArgumentException("可填充个数:0 ~ 100。超过100个需分批调用。"); + } + if (departmentIdList != null && departmentIdList.size() > 20) { + throw new IllegalArgumentException("可填充个数:0 ~ 20。"); + } + } + } + + private WxCpKfServicerOpResp servicerOp(String openKfid, List userIdList, List departmentIdList, String uri) throws WxErrorException { String url = cpService.getWxCpConfigStorage().getApiUrl(uri); JsonObject json = new JsonObject(); json.addProperty("open_kfid", openKfid); - JsonArray userIdArray = new JsonArray(); - userIdList.forEach(userIdArray::add); - json.add("userid_list", userIdArray); - + if (userIdList != null && !userIdList.isEmpty()) { + JsonArray userIdArray = new JsonArray(); + userIdList.forEach(userIdArray::add); + json.add("userid_list", userIdArray); + } + if (departmentIdList != null && !departmentIdList.isEmpty()) { + JsonArray departmentIdArray = new JsonArray(); + departmentIdList.forEach(departmentIdArray::add); + json.add("department_id_list", departmentIdArray); + } String responseContent = cpService.post(url, json.toString()); return WxCpKfServicerOpResp.fromJson(responseContent); } diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpMediaServiceImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpMediaServiceImpl.java index 863dd7c1d4..a128a35ccb 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpMediaServiceImpl.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpMediaServiceImpl.java @@ -1,5 +1,6 @@ package me.chanjar.weixin.cp.api.impl; +import com.google.gson.JsonObject; import lombok.RequiredArgsConstructor; import me.chanjar.weixin.common.bean.result.WxMediaUploadResult; import me.chanjar.weixin.common.error.WxErrorException; @@ -9,8 +10,13 @@ import me.chanjar.weixin.common.util.http.InputStreamData; import me.chanjar.weixin.common.util.http.MediaInputStreamUploadRequestExecutor; import me.chanjar.weixin.common.util.http.MediaUploadRequestExecutor; +import me.chanjar.weixin.common.util.json.GsonHelper; +import me.chanjar.weixin.common.util.json.GsonParser; import me.chanjar.weixin.cp.api.WxCpMediaService; import me.chanjar.weixin.cp.api.WxCpService; +import me.chanjar.weixin.cp.bean.media.MediaUploadByUrlReq; +import me.chanjar.weixin.cp.bean.media.MediaUploadByUrlResult; +import org.apache.commons.io.IOUtils; import java.io.File; import java.io.IOException; @@ -20,7 +26,12 @@ import java.nio.file.Files; import java.util.UUID; -import static me.chanjar.weixin.cp.constant.WxCpApiPathConsts.Media.*; +import static me.chanjar.weixin.cp.constant.WxCpApiPathConsts.Media.GET_UPLOAD_BY_URL_RESULT; +import static me.chanjar.weixin.cp.constant.WxCpApiPathConsts.Media.IMG_UPLOAD; +import static me.chanjar.weixin.cp.constant.WxCpApiPathConsts.Media.JSSDK_MEDIA_GET; +import static me.chanjar.weixin.cp.constant.WxCpApiPathConsts.Media.MEDIA_GET; +import static me.chanjar.weixin.cp.constant.WxCpApiPathConsts.Media.MEDIA_UPLOAD; +import static me.chanjar.weixin.cp.constant.WxCpApiPathConsts.Media.UPLOAD_BY_URL; /** *
@@ -57,12 +68,7 @@ public WxMediaUploadResult upload(String mediaType, String filename, String url)
         , this.mainService.getWxCpConfigStorage().getApiUrl(MEDIA_UPLOAD + mediaType),
         new InputStreamData(inputStream, filename));
     } finally {
-      if (inputStream != null) {
-        try {
-          inputStream.close();
-        } catch (IOException e) {
-        }
-      }
+      IOUtils.closeQuietly(inputStream);
       if (conn != null) {
         conn.disconnect();
       }
@@ -119,4 +125,20 @@ public String uploadImg(File file) throws WxErrorException {
     return this.mainService.execute(MediaUploadRequestExecutor.create(this.mainService.getRequestHttp()), url, file)
       .getUrl();
   }
+
+  @Override
+  public String uploadByUrl(MediaUploadByUrlReq req) throws WxErrorException {
+    final String url = this.mainService.getWxCpConfigStorage().getApiUrl(UPLOAD_BY_URL);
+    String responseContent = this.mainService.post(url, req.toJson());
+    return GsonHelper.getString(GsonParser.parse(responseContent), "jobid");
+  }
+
+  @Override
+  public MediaUploadByUrlResult uploadByUrl(String jobId) throws WxErrorException {
+    final String url = this.mainService.getWxCpConfigStorage().getApiUrl(GET_UPLOAD_BY_URL_RESULT);
+    JsonObject jsonObject = new JsonObject();
+    jsonObject.addProperty("jobid", jobId);
+    String post = this.mainService.post(url, jsonObject.toString());
+    return MediaUploadByUrlResult.fromJson(post);
+  }
 }
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpMeetingServiceImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpMeetingServiceImpl.java
index 3fc9d8218f..341bc97eab 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpMeetingServiceImpl.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpMeetingServiceImpl.java
@@ -11,7 +11,6 @@
 import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder;
 
 import java.util.HashMap;
-import java.util.List;
 import java.util.Map;
 
 import static me.chanjar.weixin.cp.constant.WxCpApiPathConsts.Oa.*;
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 5ede317fbb..7f9b693938 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
@@ -66,9 +66,9 @@ public WxCpChatDatas getChatDatas(long seq, @NonNull long limit, String proxy, S
 
     List libList = Arrays.asList(libFiles);
     // 判断windows系统会话存档sdk中dll的加载,因为会互相依赖所以是有顺序的,否则会导致无法加载sdk #2598
-    List osLib = new LinkedList();
-    List fileLib = new ArrayList();
-    libList.stream().forEach(s -> {
+    List osLib = new LinkedList<>();
+    List fileLib = new ArrayList<>();
+    libList.forEach(s -> {
       if (s.contains("lib")) {
         osLib.add(s);
       } else {
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpOaServiceImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpOaServiceImpl.java
index 53aaa00ca7..59cde79a93 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpOaServiceImpl.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpOaServiceImpl.java
@@ -140,7 +140,7 @@ public WxCpApprovalInfo getApprovalInfo(@NonNull Date startTime, @NonNull Date e
     if (filters != null && !filters.isEmpty()) {
       JsonArray filterJsonArray = new JsonArray();
       for (WxCpApprovalInfoQueryFilter filter : filters) {
-        filterJsonArray.add(new JsonParser().parse(filter.toJson()));
+        filterJsonArray.add(JsonParser.parseString(filter.toJson()));
       }
       jsonObject.add("filters", filterJsonArray);
     }
@@ -181,7 +181,7 @@ public WxCpApprovalInfo getApprovalInfo(@NonNull Date startTime, @NonNull Date e
     if (filters != null && !filters.isEmpty()) {
       JsonArray filterJsonArray = new JsonArray();
       for (WxCpApprovalInfoQueryFilter filter : filters) {
-        filterJsonArray.add(new JsonParser().parse(filter.toJson()));
+        filterJsonArray.add(JsonParser.parseString(filter.toJson()));
       }
       jsonObject.add("filters", filterJsonArray);
     }
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpOaWeDriveServiceImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpOaWeDriveServiceImpl.java
index 597851aae4..a41195ae84 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpOaWeDriveServiceImpl.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpOaWeDriveServiceImpl.java
@@ -39,20 +39,18 @@ public WxCpBaseResp spaceRename(@NonNull WxCpSpaceRenameRequest request) throws
   }
 
   @Override
-  public WxCpBaseResp spaceDismiss(@NonNull String userId, @NonNull String spaceId) throws WxErrorException {
+  public WxCpBaseResp spaceDismiss(@NonNull String spaceId) throws WxErrorException {
     String apiUrl = this.cpService.getWxCpConfigStorage().getApiUrl(SPACE_DISMISS);
     JsonObject jsonObject = new JsonObject();
-    jsonObject.addProperty("userid", userId);
     jsonObject.addProperty("spaceid", spaceId);
     String responseContent = this.cpService.post(apiUrl, jsonObject.toString());
     return WxCpBaseResp.fromJson(responseContent);
   }
 
   @Override
-  public WxCpSpaceInfo spaceInfo(@NonNull String userId, @NonNull String spaceId) throws WxErrorException {
+  public WxCpSpaceInfo spaceInfo(@NonNull String spaceId) throws WxErrorException {
     String apiUrl = this.cpService.getWxCpConfigStorage().getApiUrl(SPACE_INFO);
     JsonObject jsonObject = new JsonObject();
-    jsonObject.addProperty("userid", userId);
     jsonObject.addProperty("spaceid", spaceId);
     String responseContent = this.cpService.post(apiUrl, jsonObject.toString());
     return WxCpSpaceInfo.fromJson(responseContent);
@@ -80,10 +78,9 @@ public WxCpBaseResp spaceSetting(@NonNull WxCpSpaceSettingRequest request) throw
   }
 
   @Override
-  public WxCpSpaceShare spaceShare(@NonNull String userId, @NonNull String spaceId) throws WxErrorException {
+  public WxCpSpaceShare spaceShare(@NonNull String spaceId) throws WxErrorException {
     String apiUrl = this.cpService.getWxCpConfigStorage().getApiUrl(SPACE_SHARE);
     JsonObject jsonObject = new JsonObject();
-    jsonObject.addProperty("userid", userId);
     jsonObject.addProperty("spaceid", spaceId);
     String responseContent = this.cpService.post(apiUrl, jsonObject.toString());
     return WxCpSpaceShare.fromJson(responseContent);
@@ -166,11 +163,9 @@ public WxCpBaseResp fileAclDel(@NonNull WxCpFileAclDelRequest request) throws Wx
   }
 
   @Override
-  public WxCpBaseResp fileSetting(@NonNull String userId, @NonNull String fileId, @NonNull Integer authScope,
-                                  Integer auth) throws WxErrorException {
+  public WxCpBaseResp fileSetting(@NonNull String fileId, @NonNull Integer authScope, Integer auth) throws WxErrorException {
     String apiUrl = this.cpService.getWxCpConfigStorage().getApiUrl(FILE_SETTING);
     JsonObject jsonObject = new JsonObject();
-    jsonObject.addProperty("userid", userId);
     jsonObject.addProperty("fileid", fileId);
     jsonObject.addProperty("auth_scope", authScope);
     if (auth != null) {
@@ -181,10 +176,9 @@ public WxCpBaseResp fileSetting(@NonNull String userId, @NonNull String fileId,
   }
 
   @Override
-  public WxCpFileShare fileShare(@NonNull String userId, @NonNull String fileId) throws WxErrorException {
+  public WxCpFileShare fileShare(@NonNull String fileId) throws WxErrorException {
     String apiUrl = this.cpService.getWxCpConfigStorage().getApiUrl(FILE_SHARE);
     JsonObject jsonObject = new JsonObject();
-    jsonObject.addProperty("userid", userId);
     jsonObject.addProperty("fileid", fileId);
     String responseContent = this.cpService.post(apiUrl, jsonObject.toString());
     return WxCpFileShare.fromJson(responseContent);
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpSchoolUserServiceImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpSchoolUserServiceImpl.java
index 58bf20873f..bdb067f923 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpSchoolUserServiceImpl.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpSchoolUserServiceImpl.java
@@ -99,7 +99,7 @@ public WxCpBaseResp updateStudent(@NonNull String studentUserId, String newStude
     if (StringUtils.isNotEmpty(name)) {
       jsonObject.addProperty("name", name);
     }
-    if (departments != null && departments.size() > 0) {
+    if (departments != null && !departments.isEmpty()) {
       JsonArray jsonArray = new JsonArray();
       for (Integer depart : departments) {
         jsonArray.add(new JsonPrimitive(depart));
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpServiceApacheHttpClientImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpServiceApacheHttpClientImpl.java
index 7e69152a17..1042f88d67 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpServiceApacheHttpClientImpl.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpServiceApacheHttpClientImpl.java
@@ -1,21 +1,19 @@
 package me.chanjar.weixin.cp.api.impl;
 
-
 import me.chanjar.weixin.common.bean.WxAccessToken;
 import me.chanjar.weixin.common.enums.WxType;
 import me.chanjar.weixin.common.error.WxError;
 import me.chanjar.weixin.common.error.WxErrorException;
 import me.chanjar.weixin.common.error.WxRuntimeException;
-import me.chanjar.weixin.common.util.http.HttpType;
+import me.chanjar.weixin.common.util.http.HttpClientType;
+import me.chanjar.weixin.common.util.http.apache.ApacheBasicResponseHandler;
 import me.chanjar.weixin.common.util.http.apache.ApacheHttpClientBuilder;
 import me.chanjar.weixin.common.util.http.apache.DefaultApacheHttpClientBuilder;
 import me.chanjar.weixin.cp.config.WxCpConfigStorage;
 import me.chanjar.weixin.cp.constant.WxCpApiPathConsts;
 import org.apache.http.HttpHost;
 import org.apache.http.client.config.RequestConfig;
-import org.apache.http.client.methods.CloseableHttpResponse;
 import org.apache.http.client.methods.HttpGet;
-import org.apache.http.impl.client.BasicResponseHandler;
 import org.apache.http.impl.client.CloseableHttpClient;
 
 import java.io.IOException;
@@ -40,8 +38,8 @@ public HttpHost getRequestHttpProxy() {
   }
 
   @Override
-  public HttpType getRequestType() {
-    return HttpType.APACHE_HTTP;
+  public HttpClientType getRequestType() {
+    return HttpClientType.APACHE_HTTP;
   }
 
   @Override
@@ -61,13 +59,7 @@ public String getAccessToken(boolean forceRefresh) throws WxErrorException {
             .setProxy(this.httpProxy).build();
           httpGet.setConfig(config);
         }
-        String resultContent;
-        try (CloseableHttpClient httpClient = getRequestHttpClient();
-             CloseableHttpResponse response = httpClient.execute(httpGet)) {
-          resultContent = new BasicResponseHandler().handleResponse(response);
-        } finally {
-          httpGet.releaseConnection();
-        }
+        String resultContent = getRequestHttpClient().execute(httpGet, ApacheBasicResponseHandler.INSTANCE);
         WxError error = WxError.fromJson(resultContent, WxType.CP);
         if (error.getErrorCode() != 0) {
           throw new WxErrorException(error);
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
new file mode 100644
index 0000000000..92fd2dbd9b
--- /dev/null
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpServiceHttpComponentsImpl.java
@@ -0,0 +1,99 @@
+package me.chanjar.weixin.cp.api.impl;
+
+import me.chanjar.weixin.common.bean.WxAccessToken;
+import me.chanjar.weixin.common.enums.WxType;
+import me.chanjar.weixin.common.error.WxError;
+import me.chanjar.weixin.common.error.WxErrorException;
+import me.chanjar.weixin.common.error.WxRuntimeException;
+import me.chanjar.weixin.common.util.http.HttpClientType;
+import me.chanjar.weixin.common.util.http.hc.BasicResponseHandler;
+import me.chanjar.weixin.common.util.http.hc.DefaultHttpComponentsClientBuilder;
+import me.chanjar.weixin.common.util.http.hc.HttpComponentsClientBuilder;
+import me.chanjar.weixin.cp.config.WxCpConfigStorage;
+import me.chanjar.weixin.cp.constant.WxCpApiPathConsts;
+import org.apache.hc.client5.http.classic.methods.HttpGet;
+import org.apache.hc.client5.http.config.RequestConfig;
+import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
+import org.apache.hc.core5.http.HttpHost;
+
+import java.io.IOException;
+
+/**
+ * The type Wx cp service apache http client.
+ *
+ * @author altusea
+ */
+public class WxCpServiceHttpComponentsImpl extends BaseWxCpServiceImpl {
+
+  private CloseableHttpClient httpClient;
+  private HttpHost httpProxy;
+
+  @Override
+  public CloseableHttpClient getRequestHttpClient() {
+    return httpClient;
+  }
+
+  @Override
+  public HttpHost getRequestHttpProxy() {
+    return httpProxy;
+  }
+
+  @Override
+  public HttpClientType getRequestType() {
+    return HttpClientType.HTTP_COMPONENTS;
+  }
+
+  @Override
+  public String getAccessToken(boolean forceRefresh) throws WxErrorException {
+    if (!this.configStorage.isAccessTokenExpired() && !forceRefresh) {
+      return this.configStorage.getAccessToken();
+    }
+
+    synchronized (this.globalAccessTokenRefreshLock) {
+      String url = String.format(this.configStorage.getApiUrl(WxCpApiPathConsts.GET_TOKEN),
+        this.configStorage.getCorpId(), this.configStorage.getCorpSecret());
+
+      try {
+        HttpGet httpGet = new HttpGet(url);
+        if (this.httpProxy != null) {
+          RequestConfig config = RequestConfig.custom()
+            .setProxy(this.httpProxy).build();
+          httpGet.setConfig(config);
+        }
+        String resultContent = getRequestHttpClient().execute(httpGet, BasicResponseHandler.INSTANCE);
+        WxError error = WxError.fromJson(resultContent, WxType.CP);
+        if (error.getErrorCode() != 0) {
+          throw new WxErrorException(error);
+        }
+
+        WxAccessToken accessToken = WxAccessToken.fromJson(resultContent);
+        this.configStorage.updateAccessToken(accessToken.getAccessToken(), accessToken.getExpiresIn());
+      } catch (IOException e) {
+        throw new WxRuntimeException(e);
+      }
+    }
+    return this.configStorage.getAccessToken();
+  }
+
+  @Override
+  public void initHttp() {
+    HttpComponentsClientBuilder apacheHttpClientBuilder = DefaultHttpComponentsClientBuilder.get();
+
+    apacheHttpClientBuilder.httpProxyHost(this.configStorage.getHttpProxyHost())
+      .httpProxyPort(this.configStorage.getHttpProxyPort())
+      .httpProxyUsername(this.configStorage.getHttpProxyUsername())
+      .httpProxyPassword(this.configStorage.getHttpProxyPassword().toCharArray());
+
+    if (this.configStorage.getHttpProxyHost() != null && this.configStorage.getHttpProxyPort() > 0) {
+      this.httpProxy = new HttpHost(this.configStorage.getHttpProxyHost(), this.configStorage.getHttpProxyPort());
+    }
+
+    this.httpClient = apacheHttpClientBuilder.build();
+  }
+
+  @Override
+  public WxCpConfigStorage getWxCpConfigStorage() {
+    return this.configStorage;
+  }
+
+}
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpServiceImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpServiceImpl.java
index 661a0ed79f..f2a50db471 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpServiceImpl.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpServiceImpl.java
@@ -6,14 +6,12 @@
 import me.chanjar.weixin.common.error.WxError;
 import me.chanjar.weixin.common.error.WxErrorException;
 import me.chanjar.weixin.common.error.WxRuntimeException;
+import me.chanjar.weixin.common.util.http.apache.ApacheBasicResponseHandler;
 import me.chanjar.weixin.common.util.json.GsonParser;
 import me.chanjar.weixin.cp.config.WxCpConfigStorage;
 import me.chanjar.weixin.cp.constant.WxCpApiPathConsts;
 import org.apache.http.client.config.RequestConfig;
-import org.apache.http.client.methods.CloseableHttpResponse;
 import org.apache.http.client.methods.HttpGet;
-import org.apache.http.impl.client.BasicResponseHandler;
-import org.apache.http.impl.client.CloseableHttpClient;
 
 import java.io.IOException;
 import java.util.concurrent.locks.Lock;
@@ -55,13 +53,7 @@ public String getAccessToken(boolean forceRefresh) throws WxErrorException {
           RequestConfig config = RequestConfig.custom().setProxy(getRequestHttpProxy()).build();
           httpGet.setConfig(config);
         }
-        String resultContent;
-        try (CloseableHttpClient httpClient = getRequestHttpClient();
-             CloseableHttpResponse response = httpClient.execute(httpGet)) {
-          resultContent = new BasicResponseHandler().handleResponse(response);
-        } finally {
-          httpGet.releaseConnection();
-        }
+        String resultContent = getRequestHttpClient().execute(httpGet, ApacheBasicResponseHandler.INSTANCE);
         WxError error = WxError.fromJson(resultContent, WxType.CP);
         if (error.getErrorCode() != 0) {
           throw new WxErrorException(error);
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpServiceJoddHttpImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpServiceJoddHttpImpl.java
index ec8a3624ac..5081341851 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpServiceJoddHttpImpl.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpServiceJoddHttpImpl.java
@@ -9,7 +9,7 @@
 import me.chanjar.weixin.common.enums.WxType;
 import me.chanjar.weixin.common.error.WxError;
 import me.chanjar.weixin.common.error.WxErrorException;
-import me.chanjar.weixin.common.util.http.HttpType;
+import me.chanjar.weixin.common.util.http.HttpClientType;
 import me.chanjar.weixin.cp.config.WxCpConfigStorage;
 import me.chanjar.weixin.cp.constant.WxCpApiPathConsts;
 
@@ -33,8 +33,8 @@ public ProxyInfo getRequestHttpProxy() {
   }
 
   @Override
-  public HttpType getRequestType() {
-    return HttpType.JODD_HTTP;
+  public HttpClientType getRequestType() {
+    return HttpClientType.JODD_HTTP;
   }
 
   @Override
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpServiceOkHttpImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpServiceOkHttpImpl.java
index 73b933f646..af6a7e1408 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpServiceOkHttpImpl.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpServiceOkHttpImpl.java
@@ -5,7 +5,7 @@
 import me.chanjar.weixin.common.enums.WxType;
 import me.chanjar.weixin.common.error.WxError;
 import me.chanjar.weixin.common.error.WxErrorException;
-import me.chanjar.weixin.common.util.http.HttpType;
+import me.chanjar.weixin.common.util.http.HttpClientType;
 import me.chanjar.weixin.common.util.http.okhttp.DefaultOkHttpClientBuilder;
 import me.chanjar.weixin.common.util.http.okhttp.OkHttpProxyInfo;
 import me.chanjar.weixin.cp.config.WxCpConfigStorage;
@@ -36,8 +36,8 @@ public OkHttpProxyInfo getRequestHttpProxy() {
   }
 
   @Override
-  public HttpType getRequestType() {
-    return HttpType.OK_HTTP;
+  public HttpClientType getRequestType() {
+    return HttpClientType.OK_HTTP;
   }
 
   @Override
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpAgentWorkBench.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpAgentWorkBench.java
index e74173ee3f..4c17397ecd 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpAgentWorkBench.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpAgentWorkBench.java
@@ -6,6 +6,7 @@
 import lombok.Builder;
 import lombok.Data;
 import lombok.NoArgsConstructor;
+import me.chanjar.weixin.common.util.json.WxGsonBuilder;
 import me.chanjar.weixin.cp.bean.workbench.WorkBenchKeyData;
 import me.chanjar.weixin.cp.bean.workbench.WorkBenchList;
 import me.chanjar.weixin.cp.constant.WxCpConsts;
@@ -33,6 +34,10 @@ public class WxCpAgentWorkBench implements Serializable {
    * 用户的userid
    */
   private String userId;
+  /**
+   * 用户的userIds
+   */
+  private List useridList;
   /**
    * 应用id
    */
@@ -58,6 +63,15 @@ public class WxCpAgentWorkBench implements Serializable {
    * 参考示例:今日要闻
    */
   private Boolean enableWebviewClick;
+  /**
+   * 高度。可以有两种选择:single_row与double_row。当为single_row时,高度为106px(如果隐藏标题则为147px)。
+   * 当为double_row时,高度固定为171px(如果隐藏标题则为212px)。默认值为double_row
+   */
+  private String height;
+  /**
+   * 是否要隐藏展示了应用名称的标题部分,默认值为false。
+   */
+  private Boolean hideTitle;
 
   private List keyDataList;
 
@@ -93,6 +107,20 @@ public String toUserDataString() {
     return userDataObject.toString();
   }
 
+  /**
+   * 生成批量用户数据Json字符串
+   *
+   * @return the string
+   */
+  public String toBatchUserDataString() {
+    JsonObject userDataObject = new JsonObject();
+    userDataObject.addProperty("agentid", this.agentId);
+    JsonArray useridList = WxGsonBuilder.create().toJsonTree(this.useridList).getAsJsonArray();
+    userDataObject.add("userid_list", useridList);
+    this.handleBatch(userDataObject);
+    return userDataObject.toString();
+  }
+
   /**
    * 处理不用类型的工作台数据
    */
@@ -140,9 +168,13 @@ private void handle(JsonObject templateObject) {
         webview.addProperty("url", this.url);
         webview.addProperty("jump_url", this.jumpUrl);
         webview.addProperty("pagepath", this.pagePath);
-        if (null != this.enableWebviewClick) {
+        if (this.enableWebviewClick != null) {
           webview.addProperty("enable_webview_click", this.enableWebviewClick);
         }
+        webview.addProperty("height", this.height);
+        if (this.hideTitle != null) {
+          webview.addProperty("hide_title", this.hideTitle);
+        }
         templateObject.add("webview", webview);
         break;
       }
@@ -152,4 +184,79 @@ private void handle(JsonObject templateObject) {
     }
   }
 
+  /**
+   * 处理不用类型的工作台数据
+   */
+  private void handleBatch(JsonObject templateObject) {
+    switch (this.getType()) {
+      case WxCpConsts.WorkBenchType.KEYDATA: {
+        JsonArray keyDataArray = new JsonArray();
+        JsonObject itemsObject = new JsonObject();
+        for (WorkBenchKeyData keyDataItem : this.keyDataList) {
+          JsonObject keyDataObject = new JsonObject();
+          keyDataObject.addProperty("key", keyDataItem.getKey());
+          keyDataObject.addProperty("data", keyDataItem.getData());
+          keyDataObject.addProperty("jump_url", keyDataItem.getJumpUrl());
+          keyDataObject.addProperty("pagepath", keyDataItem.getPagePath());
+          keyDataArray.add(keyDataObject);
+        }
+        itemsObject.add("items", keyDataArray);
+        JsonObject dataObject = new JsonObject();
+        dataObject.addProperty("type", WxCpConsts.WorkBenchType.KEYDATA);
+        dataObject.add("keydata", itemsObject);
+        templateObject.add("data", dataObject);
+        break;
+      }
+      case WxCpConsts.WorkBenchType.IMAGE: {
+        JsonObject image = new JsonObject();
+        image.addProperty("url", this.url);
+        image.addProperty("jump_url", this.jumpUrl);
+        image.addProperty("pagepath", this.pagePath);
+        JsonObject dataObject = new JsonObject();
+        dataObject.addProperty("type", WxCpConsts.WorkBenchType.IMAGE);
+        dataObject.add("image", image);
+        templateObject.add("data", dataObject);
+        break;
+      }
+      case WxCpConsts.WorkBenchType.LIST: {
+        JsonArray listArray = new JsonArray();
+        JsonObject itemsObject = new JsonObject();
+        for (WorkBenchList listItem : this.lists) {
+          JsonObject listObject = new JsonObject();
+          listObject.addProperty("title", listItem.getTitle());
+          listObject.addProperty("jump_url", listItem.getJumpUrl());
+          listObject.addProperty("pagepath", listItem.getPagePath());
+          listArray.add(listObject);
+        }
+        itemsObject.add("items", listArray);
+        JsonObject dataObject = new JsonObject();
+        dataObject.addProperty("type", WxCpConsts.WorkBenchType.LIST);
+        dataObject.add("list", itemsObject);
+        templateObject.add("data", dataObject);
+        break;
+      }
+      case WxCpConsts.WorkBenchType.WEBVIEW: {
+        JsonObject webview = new JsonObject();
+        webview.addProperty("url", this.url);
+        webview.addProperty("jump_url", this.jumpUrl);
+        webview.addProperty("pagepath", this.pagePath);
+        if (this.enableWebviewClick != null) {
+          webview.addProperty("enable_webview_click", this.enableWebviewClick);
+        }
+        webview.addProperty("height", this.height);
+        if (this.hideTitle != null) {
+          webview.addProperty("hide_title", this.hideTitle);
+        }
+        JsonObject dataObject = new JsonObject();
+        dataObject.addProperty("type", WxCpConsts.WorkBenchType.WEBVIEW);
+        dataObject.add("webview", webview);
+        templateObject.add("data", dataObject);
+        break;
+      }
+      default: {
+        //do nothing
+      }
+    }
+  }
+
 }
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/corpgroup/WxCpCorpGroupCorpListAppShareInfoResp.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/corpgroup/WxCpCorpGroupCorpListAppShareInfoResp.java
index 1f02307f87..810b437e38 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/corpgroup/WxCpCorpGroupCorpListAppShareInfoResp.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/corpgroup/WxCpCorpGroupCorpListAppShareInfoResp.java
@@ -2,7 +2,6 @@
 
 import com.google.gson.annotations.SerializedName;
 import lombok.Data;
-import me.chanjar.weixin.cp.bean.WxCpBaseResp;
 
 import java.io.Serializable;
 import java.util.List;
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 0e6d75bf0c..20d6b32442 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
@@ -4,7 +4,6 @@
 import lombok.Data;
 import lombok.EqualsAndHashCode;
 import me.chanjar.weixin.cp.bean.WxCpBaseResp;
-import me.chanjar.weixin.cp.bean.external.acquisition.WxCpCustomerAcquisitionInfo;
 import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder;
 
 import java.io.Serializable;
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 79cb9a6932..6826413e13 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
@@ -2,10 +2,7 @@
 
 import com.google.gson.annotations.SerializedName;
 import lombok.*;
-import me.chanjar.weixin.common.bean.ToJson;
 import me.chanjar.weixin.cp.bean.WxCpBaseResp;
-import me.chanjar.weixin.cp.bean.external.acquisition.WxCpCustomerAcquisitionInfo;
-import me.chanjar.weixin.cp.bean.external.acquisition.WxCpCustomerAcquisitionList;
 import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder;
 
 import java.io.Serializable;
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
new file mode 100644
index 0000000000..c5cb21bde5
--- /dev/null
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/media/MediaUploadByUrlReq.java
@@ -0,0 +1,58 @@
+package me.chanjar.weixin.cp.bean.media;
+
+import lombok.Data;
+import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder;
+
+/**
+ * 生成异步上传任务
+ * @author imyzt
+ * @date 2025/04/27
+ */
+@Data
+public class MediaUploadByUrlReq {
+
+  /**
+   * 场景值。1-客户联系入群欢迎语素材(目前仅支持1)。 注意:每个场景值有对应的使用范围,详见上面的「使用场景说明」
+   */
+  private Integer scene;
+
+  /**
+   * 媒体文件类型。目前仅支持video-视频,file-普通文件 不超过32字节。
+   */
+  private String type;
+
+  /**
+   * 文件名,标识文件展示的名称。比如,使用该media_id发消息时,展示的文件名由该字段控制。 不超过128字节。
+   */
+  private String filename;
+
+  /**
+   * 文件cdn url。url要求支持Range分块下载 不超过1024字节。 如果为腾讯云cos链接,则需要设置为「公有读」权限。
+   */
+  private String url;
+
+  /**
+   * 文件md5。对比从url下载下来的文件md5是否一致。 不超过32字节。
+   */
+  private String md5;
+
+  /**
+   * From json wx cp base resp.
+   *
+   * @param json the json
+   * @return the wx cp base resp
+   */
+  public static MediaUploadByUrlReq fromJson(String json) {
+    return WxCpGsonBuilder.create().fromJson(json, MediaUploadByUrlReq.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/media/MediaUploadByUrlResult.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/media/MediaUploadByUrlResult.java
new file mode 100644
index 0000000000..cc931eed39
--- /dev/null
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/media/MediaUploadByUrlResult.java
@@ -0,0 +1,82 @@
+package me.chanjar.weixin.cp.bean.media;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import me.chanjar.weixin.cp.bean.WxCpBaseResp;
+import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder;
+
+import java.io.Serializable;
+
+/**
+ * 异步上传企微素材
+ * @author imyzt
+ * @date 2025/4/27
+ */
+@EqualsAndHashCode(callSuper = true)
+@Data
+public class MediaUploadByUrlResult extends WxCpBaseResp implements Serializable {
+
+  private static final long serialVersionUID = 330834334738622341L;
+
+  /**
+   * 任务状态。1-处理中,2-完成,3-异常失败
+   */
+  @SerializedName("status")
+  private Integer status;
+
+  @SerializedName("detail")
+  private Detail detail;
+
+  @Data
+  public static class Detail {
+
+    /**
+     * 任务失败返回码。当status为3时返回非0,其他返回0
+     * 830001 url非法 确认url是否支持Range分块下载
+     * 830003 url下载数据失败 确认url本身是否能正常访问
+     * 45001 文件大小超过限制 确认文件在5字节~200M范围内
+     * 301019 文件MD5不匹配 确认url对应的文件内容md5,跟所填的md5参数是否一致
+     * 注意: status=2时,此处微信并未返回任何值
+     */
+    @SerializedName("errcode")
+    private Integer errCode;
+
+    /**
+     * 注意: status=2时,此处微信并未返回任何值
+     */
+    @SerializedName("errmsg")
+    private String errMsg;
+
+    /**
+     * 媒体文件上传后获取的唯一标识,3天内有效。当status为2时返回。
+     */
+    @SerializedName("media_id")
+    private String mediaId;
+
+    /**
+     * 媒体文件创建的时间戳。当status为2时返回。
+     */
+    @SerializedName("created_at")
+    private String createdAt;
+  }
+
+  /**
+   * From json wx cp media upload by url result.
+   *
+   * @param json the json
+   * @return the wx cp media upload by url result
+   */
+  public static MediaUploadByUrlResult fromJson(String json) {
+    return WxCpGsonBuilder.create().fromJson(json, MediaUploadByUrlResult.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/message/WxCpGroupRobotMessage.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/message/WxCpGroupRobotMessage.java
index 6c889b6cec..97beeec189 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/message/WxCpGroupRobotMessage.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/message/WxCpGroupRobotMessage.java
@@ -14,7 +14,6 @@
 import java.util.List;
 
 import static me.chanjar.weixin.cp.constant.WxCpConsts.GroupRobotMsgType.*;
-import static me.chanjar.weixin.cp.constant.WxCpConsts.GroupRobotMsgType.TEMPLATE_CARD;
 
 /**
  * 微信群机器人消息
@@ -253,6 +252,12 @@ public String toJson() {
         messageJson.add("markdown", text);
         break;
       }
+      case MARKDOWN_V2: {
+        JsonObject text = new JsonObject();
+        text.addProperty("content", this.getContent());
+        messageJson.add("markdown_v2", text);
+        break;
+      }
       case IMAGE: {
         JsonObject text = new JsonObject();
         text.addProperty("base64", this.getBase64());
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/message/WxCpMessageSendResult.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/message/WxCpMessageSendResult.java
index 2ddf95d8da..0883651ae6 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/message/WxCpMessageSendResult.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/message/WxCpMessageSendResult.java
@@ -50,6 +50,9 @@ public static WxCpMessageSendResult fromJson(String json) {
   @SerializedName("invalidtag")
   private String invalidTag;
 
+  @SerializedName("unlicenseduser")
+  private String unlicensedUser;
+
   @SerializedName("msgid")
   private String msgId;
 
@@ -93,4 +96,13 @@ public List getInvalidPartyList() {
   public List getInvalidTagList() {
     return this.content2List(this.invalidTag);
   }
+
+  /**
+   * Gets unlicensed user list.
+   *
+   * @return the unlicensed user list
+   */
+  public List getUnlicensedUserList() {
+    return this.content2List(this.unlicensedUser);
+  }
 }
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/message/WxCpTpXmlMessage.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/message/WxCpTpXmlMessage.java
index 8b6b0689a7..122bc26272 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/message/WxCpTpXmlMessage.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/message/WxCpTpXmlMessage.java
@@ -403,7 +403,7 @@ public class WxCpTpXmlMessage implements Serializable {
    * The Agent id.
    */
   @XStreamAlias("AgentID")
-  protected String agentID;
+  protected Integer agentID;
 
   /**
    * The Pic url.
@@ -793,4 +793,6 @@ public static WxCpTpXmlMessage fromEncryptedXml(String encryptedXml, WxCpTpConfi
     log.debug("解密后的原始xml消息内容:{}", plainText);
     return fromXml(plainText);
   }
+
+
 }
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/message/WxCpXmlApprovalInfo.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/message/WxCpXmlApprovalInfo.java
index 7193c7cf6f..798a5c8b00 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/message/WxCpXmlApprovalInfo.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/message/WxCpXmlApprovalInfo.java
@@ -118,7 +118,7 @@ public static class NotifyNode implements Serializable {
     /**
      * 抄送人userid
      */
-    @XStreamAlias("ItemUserid")
+    @XStreamAlias("ItemUserId")
     @XStreamConverter(value = XStreamCDataConverter.class)
     private String itemUserId;
 
@@ -190,7 +190,7 @@ public static class Item implements Serializable {
     /**
      * 分支审批人userid
      */
-    @XStreamAlias("ItemUserid")
+    @XStreamAlias("ItemUserId")
     @XStreamConverter(value = XStreamCDataConverter.class)
     private String itemUserId;
 
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 fb4213f504..c5e55220e5 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
@@ -92,7 +92,7 @@ public class WxCpXmlMessage implements Serializable {
   private String content;
 
   @XStreamAlias("MsgId")
-  private Long msgId;
+  private String msgId;
 
   @XStreamAlias("PicUrl")
   @XStreamConverter(value = XStreamCDataConverter.class)
@@ -155,6 +155,18 @@ public class WxCpXmlMessage implements Serializable {
   @XStreamConverter(value = XStreamCDataConverter.class)
   private String memChangeCnt;
 
+  @XStreamAlias("MemChangeList")
+  @XStreamConverter(value = XStreamCDataConverter.class)
+  private String MemChangeList;
+
+  @XStreamAlias("LastMemVer")
+  @XStreamConverter(value = XStreamCDataConverter.class)
+  private String lastMemVer;
+
+  @XStreamAlias("CurMemVer")
+  @XStreamConverter(value = XStreamCDataConverter.class)
+  private String curMemVer;
+
   @XStreamAlias("Source")
   @XStreamConverter(value = XStreamCDataConverter.class)
   private String source;
@@ -198,6 +210,13 @@ public class WxCpXmlMessage implements Serializable {
   @XStreamAlias("SelectedItems")
   private List selectedItems;
 
+  /**
+   * 异步任务id
+   */
+  @XStreamAlias("JobId")
+  @XStreamConverter(value = XStreamCDataConverter.class)
+  private String jobId;
+
   /**
    * 微信客服
    * 调用拉取消息接口时,需要传此token,用于校验请求的合法性
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/messagebuilder/BaseBuilder.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/messagebuilder/BaseBuilder.java
index fcbc578a59..e7c2267018 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/messagebuilder/BaseBuilder.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/messagebuilder/BaseBuilder.java
@@ -6,10 +6,8 @@
 
 /**
  * The type Base builder.
- *
- * @param  the type parameter
  */
-public class BaseBuilder {
+public abstract class BaseBuilder {
   /**
    * The Msg type.
    */
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/msgaudit/WxCpChatModel.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/msgaudit/WxCpChatModel.java
index 8a9d2130d6..c88cb7b9be 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/msgaudit/WxCpChatModel.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/msgaudit/WxCpChatModel.java
@@ -603,7 +603,7 @@ public static class File implements Serializable {
     private String sdkFileId;
 
     @SerializedName("filesize")
-    private Integer fileSize;
+    private Long fileSize;
 
     /**
      * From json file.
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/WxCpCheckinDayData.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/WxCpCheckinDayData.java
index 1a8d47c82e..c06a6d79e2 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/WxCpCheckinDayData.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/WxCpCheckinDayData.java
@@ -24,7 +24,7 @@ public class WxCpCheckinDayData implements Serializable {
    * The type Base info.
    */
   @Data
-  public class BaseInfo implements Serializable {
+  public static class BaseInfo implements Serializable {
 
 
     private static final long serialVersionUID = 3679745559788648438L;
@@ -143,7 +143,7 @@ public class CheckinTime implements Serializable {
    * The type Summary info.
    */
   @Data
-  public class SummaryInfo implements Serializable {
+  public static class SummaryInfo implements Serializable {
     private static final long serialVersionUID = 3428576099259666595L;
     /**
      * checkin_count 当日打卡次数
@@ -186,7 +186,7 @@ public class SummaryInfo implements Serializable {
    * The type Holiday infos.
    */
   @Data
-  public class HolidayInfos implements Serializable {
+  public static class HolidayInfos implements Serializable {
     private static final long serialVersionUID = -6671577072585561527L;
     /**
      * sp_number 假勤相关信息
@@ -282,7 +282,7 @@ public class Data implements Serializable {
    * The type Exception infos.
    */
   @Data
-  public class ExceptionInfos implements Serializable {
+  public static class ExceptionInfos implements Serializable {
     private static final long serialVersionUID = -5987438373762518299L;
     /**
      * exception 校准状态类型:1-迟到;2-早退;3-缺卡;4-旷工;5-地点异常;6-设备异常
@@ -313,7 +313,7 @@ public class ExceptionInfos implements Serializable {
    * The type Ot info.
    */
   @Data
-  public class OtInfo implements Serializable {
+  public static class OtInfo implements Serializable {
     private static final long serialVersionUID = -6557759801572150175L;
     /**
      * ot_status 状态:0-无加班;1-正常;2-缺时长
@@ -344,7 +344,7 @@ public class OtInfo implements Serializable {
    * The type Sp item.
    */
   @Data
-  public class SpItem implements Serializable {
+  public static class SpItem implements Serializable {
     private static final long serialVersionUID = 2423158264958352024L;
     /**
      * type 类型:1-请假;2-补卡;3-出差;4-外出;100-外勤
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 f1c1a8580d..3bc542ccd0 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
@@ -151,6 +151,30 @@ public static class CheckinDate implements Serializable {
      */
     @SerializedName("flex_off_duty_time")
     private Integer flexOffDutyTime;
+
+    /**
+     * 是否允许弹性时间
+     */
+    @SerializedName("allow_flex")
+    private Boolean allowFlex;
+
+    /**
+     * 迟到规则
+     */
+    @SerializedName("late_rule")
+    private LateRule lateRule;
+
+    /**
+     * 最早可打卡时间限制
+     */
+    @SerializedName("max_allow_arrive_early")
+    private Integer maxAllowArriveEarly;
+
+    /**
+     * 最晚可打卡时间限制
+     */
+    @SerializedName("max_allow_arrive_late")
+    private Integer maxAllowArriveLate;
   }
 
   /**
@@ -160,6 +184,13 @@ public static class CheckinDate implements Serializable {
   public static class CheckinTime implements Serializable {
 
     private static final long serialVersionUID = -5507709858609705279L;
+
+    /**
+     * 时段id,为班次中某一堆上下班时间组合的id
+     */
+    @SerializedName("time_id")
+    private Integer timeId;
+
     /**
      * 上班时间,表示为距离当天0点的秒数。
      */
@@ -183,6 +214,60 @@ public static class CheckinTime implements Serializable {
      */
     @SerializedName("remind_off_work_sec")
     private Integer remindOffWorkSec;
+
+    /**
+     * 休息开始时间,仅单时段支持,距离0点的秒
+     */
+    @SerializedName("rest_begin_time")
+    private Integer restBeginTime;
+
+    /**
+     * 休息结束时间,仅单时段支持,距离0点的秒
+     */
+    @SerializedName("rest_end_time")
+    private Integer restEndTime;
+
+    /**
+     * 是否允许休息
+     */
+    @SerializedName("allow_rest")
+    private Boolean allowRest;
+
+    /**
+     * 最早可打卡时间,距离0点的秒数
+     */
+    @SerializedName("earliest_work_sec")
+    private Integer earliestWorkSec;
+
+    /**
+     * 最晚可打卡时间,距离0点的秒数
+     */
+    @SerializedName("latest_work_sec")
+    private Integer latestWorkSec;
+
+    /**
+     * 最早可下班打卡时间,距离0点的秒数
+     */
+    @SerializedName("earliest_off_work_sec")
+    private Integer earliestOffWorkSec;
+
+    /**
+     * 最晚可下班打卡时间,距离0点的秒数
+     */
+    @SerializedName("latest_off_work_sec")
+    private Integer latestOffWorkSec;
+
+    /**
+     * 上班无需打卡
+     */
+    @SerializedName("no_need_checkon")
+    private Boolean noNeedCheckon;
+
+    /**
+     * 下班无需打卡
+     */
+    @SerializedName("no_need_checkoff")
+    private Boolean noNeedCheckoff;
   }
 
   /**
@@ -438,6 +523,17 @@ public static class LateRule implements Serializable {
 
     private static final long serialVersionUID = 5604969713950037053L;
 
+    /**
+     * 晚走的时间 距离最晚一个下班的时间单位:秒
+     */
+    @SerializedName("offwork_after_time")
+    private Integer offWorkAfterTime;
+
+    /**
+     * 第二天第一个班次允许迟到的弹性时间单位:秒
+     */
+    @SerializedName("onwork_flex_time")
+    private Integer onWorkFlexTime;
 
     /**
      * 是否允许超时下班(下班晚走次日晚到)允许时onwork_flex_time,offwork_after_time才有意义
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/WxCpCheckinSchedule.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/WxCpCheckinSchedule.java
index af310439fc..1e8797cf7e 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/WxCpCheckinSchedule.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/WxCpCheckinSchedule.java
@@ -49,7 +49,7 @@ public class WxCpCheckinSchedule implements Serializable {
    * The type User schedule.
    */
   @Data
-  public class UserSchedule implements Serializable {
+  public static class UserSchedule implements Serializable {
     private static final long serialVersionUID = 9138985222324576857L;
     /**
      * scheduleList 个人排班表信息
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/WxCpCropCheckinOption.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/WxCpCropCheckinOption.java
index bda77447fe..8cdccd9953 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/WxCpCropCheckinOption.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/WxCpCropCheckinOption.java
@@ -53,6 +53,12 @@ public class WxCpCropCheckinOption extends WxCpCheckinGroupBase implements Seria
   @SerializedName("ot_info")
   private OtInfo otInfo;
 
+  /**
+   * 加班信息V2,新版API返回的加班信息结构
+   */
+  @SerializedName("ot_info_v2")
+  private OtInfoV2 otInfoV2;
+
   /**
    * 每月最多补卡次数,默认-1表示不限制
    */
@@ -418,4 +424,94 @@ public static class OtApplyInfo implements Serializable {
     private Integer otNonworkingDaySpanDayTime;
 
   }
+
+  /**
+   * 加班信息V2,新版API返回的加班信息结构
+   */
+  @Data
+  public static class OtInfoV2 implements Serializable {
+
+    private static final long serialVersionUID = 1610150484871066200L;
+
+    /**
+     * 工作日加班配置
+     */
+    @SerializedName("workdayconf")
+    private WorkdayConf workdayConf;
+
+    /**
+     * 非工作日加班配置
+     */
+    @SerializedName("restdayconf")
+    private RestdayConf restdayConf;
+
+    /**
+     * 节假日加班配置
+     */
+    @SerializedName("holidayconf")
+    private HolidayConf holidayConf;
+
+    /**
+     * 工作日加班配置
+     */
+    @Data
+    public static class WorkdayConf implements Serializable {
+      private static final long serialVersionUID = 1610150484871066201L;
+
+      /**
+       * 是否允许工作日加班,true为允许,false为不允许
+       */
+      @SerializedName("allow_ot")
+      private Boolean allowOt;
+
+      /**
+       * 加班类型
+       * 0:以加班申请核算打卡记录(根据打卡记录和加班申请核算),
+       * 1:以打卡时间为准(根据打卡时间计算),
+       * 2: 以加班申请审批为准(只根据加班申请计算)
+       */
+      @SerializedName("type")
+      private Integer type;
+    }
+
+    /**
+     * 非工作日加班配置
+     */
+    @Data
+    public static class RestdayConf implements Serializable {
+      private static final long serialVersionUID = 1610150484871066202L;
+
+      /**
+       * 是否允许非工作日加班,true为允许,false为不允许
+       */
+      @SerializedName("allow_ot")
+      private Boolean allowOt;
+
+      /**
+       * 加班类型
+       */
+      @SerializedName("type")
+      private Integer type;
+    }
+
+    /**
+     * 节假日加班配置
+     */
+    @Data
+    public static class HolidayConf implements Serializable {
+      private static final long serialVersionUID = 1610150484871066203L;
+
+      /**
+       * 是否允许节假日加班,true为允许,false为不允许
+       */
+      @SerializedName("allow_ot")
+      private Boolean allowOt;
+
+      /**
+       * 加班类型
+       */
+      @SerializedName("type")
+      private Integer type;
+    }
+  }
 }
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/WxCpOaApprovalTemplateResult.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/WxCpOaApprovalTemplateResult.java
index d10594a546..b926539008 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/WxCpOaApprovalTemplateResult.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/WxCpOaApprovalTemplateResult.java
@@ -17,7 +17,7 @@
 /**
  * 审批模板详情
  *
- * @author gyv12345 @163.com / Wang_Wong
+ * @author gyv12345@163.com / Wang_Wong
  */
 @Data
 @Builder
@@ -121,7 +121,7 @@ public static class TemplateOption implements Serializable {
 
     /**
      * 获取审批模板详情,value为list类型
-     * https://developer.work.weixin.qq.com/document/path/91982
+     * 文档链接
      */
     @SerializedName("value")
     private List value;
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/applydata/ContentValue.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/applydata/ContentValue.java
index 158206867e..92ec8a43e8 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/applydata/ContentValue.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/applydata/ContentValue.java
@@ -34,6 +34,9 @@ public class ContentValue implements Serializable {
 
   private List departments;
 
+  @SerializedName("new_tips")
+  private NewTips newTips;
+
   private List files;
 
   private List children;
@@ -114,6 +117,68 @@ public static class Department implements Serializable {
     private String name;
   }
 
+  /**
+   * The type Tips.
+   */
+  @Data
+  public static class NewTips implements Serializable {
+    private static final long serialVersionUID = 1094978100200056100L;
+    @SerializedName("tips_content")
+    private List tipsContent;
+
+    /**
+     * The type tips_content.
+     */
+    @Data
+    public static class TipsContent implements Serializable {
+      private static final long serialVersionUID = 559432801311084797L;
+      @SerializedName("text")
+      private Text text;
+      private String lang;
+
+      /**
+       * The type sub_text.
+       */
+      @Data
+      public static class Text implements Serializable {
+        private static final long serialVersionUID = -70174360931158924L;
+        @SerializedName("sub_text")
+        private List subText;
+      }
+
+      /**
+       * The type sub_text.
+       */
+      @Data
+      public static class SubText implements Serializable {
+        private static final long serialVersionUID = -8226911175438019317L;
+        private Integer type;
+        private Content content;
+
+        @Data
+        public static class Content implements Serializable {
+          private static final long serialVersionUID = -6813250009451940525L;
+          @SerializedName("plain_text")
+          private PlainText plainText;
+          private Link link;
+
+          @Data
+          public static class PlainText implements Serializable {
+            private static final long serialVersionUID = -599377674188314118L;
+            private String content;
+          }
+
+          @Data
+          public static class Link implements Serializable {
+            private static final long serialVersionUID = 2784173996170990308L;
+            private String title;
+            private String url;
+          }
+        }
+      }
+    }
+  }
+
   /**
    * The type File.
    */
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/meeting/WxCpMeeting.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/meeting/WxCpMeeting.java
index 0efbc5a772..0ad79538e4 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/meeting/WxCpMeeting.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/meeting/WxCpMeeting.java
@@ -65,6 +65,35 @@ public class WxCpMeeting implements Serializable, ToJson {
   @SerializedName("agentid")
   private Integer agentId;
 
+  /**
+   * 发起人所在部门
+   */
+  @SerializedName("main_department")
+  private Integer mainDepartment;
+
+  /**
+   * 会议的状态。
+   * 1:待开始
+   * 2:会议中
+   * 3:已结束
+   * 4:已取消
+   * 5:已过期
+   */
+  @SerializedName("status")
+  public Integer status;
+
+  /**
+   * 会议类型。
+   * 0:一次性会议
+   * 1:周期性会议
+   * 2:微信专属会议
+   * 3:Rooms 投屏会议
+   * 5:个人会议号会议
+   * 6:网络研讨会
+   */
+  @SerializedName("meeting_type")
+  private Integer meetingType;
+
 
   /**
    * 参与会议的成员。会议人数上限,以指定的「管理员」可预约的人数上限来校验,普通企业与会人员最多100;
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/meeting/WxCpMeetingUpdateResult.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/meeting/WxCpMeetingUpdateResult.java
index dfd200f2a8..21b8e88817 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/meeting/WxCpMeetingUpdateResult.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/meeting/WxCpMeetingUpdateResult.java
@@ -1,15 +1,11 @@
 package me.chanjar.weixin.cp.bean.oa.meeting;
 
-import com.google.common.base.Splitter;
 import com.google.gson.annotations.SerializedName;
 import lombok.Data;
 import me.chanjar.weixin.cp.bean.WxCpBaseResp;
 import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder;
-import org.apache.commons.lang3.StringUtils;
 
 import java.io.Serializable;
-import java.util.Collections;
-import java.util.List;
 
 /**
  * 为标签添加或移除用户结果对象类.
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/templatedata/TemplateOptions.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/templatedata/TemplateOptions.java
index 32ada7b338..8b1605a5a1 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/templatedata/TemplateOptions.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/templatedata/TemplateOptions.java
@@ -3,6 +3,7 @@
 import lombok.Data;
 
 import java.io.Serializable;
+import java.util.List;
 
 /**
  * The type Template options.
@@ -17,11 +18,8 @@ public class TemplateOptions implements Serializable {
   private String key;
 
   /**
-   * 创建审批模板,value为对象类型
-   * https://developer.work.weixin.qq.com/document/path/97437#%E9%99%845-%E5%8D%95%E9%80%89%E5%A4%9A%E9%80%89%E6%8E%A7%E4%BB%B6%EF%BC%88control%E5%8F%82%E6%95%B0%E4%B8%BAselector%EF%BC%89
-   *
-   * 获取审批模板详情,value为list类型
-   * https://developer.work.weixin.qq.com/document/path/91982
+   * 创建审批模板,value为对象类型
+   * 获取审批模板详情,value为list类型
    **/
-  private TemplateTitle value;
+  private List value;
 }
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/templatecard/MultipleSelect.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/templatecard/MultipleSelect.java
index c9f15e6d74..1a078bea46 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/templatecard/MultipleSelect.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/templatecard/MultipleSelect.java
@@ -59,7 +59,7 @@ public JsonObject toJson() {
     }
 // select_list
     List options = this.getOptions();
-    if (null != options && options.size() > 0) {
+    if (null != options && !options.isEmpty()) {
       JsonArray optionJsonArray = new JsonArray();
       for (CheckboxOption option : this.getOptions()) {
         JsonObject tempObject = option.toJson();
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/templatecard/TemplateCardButtonSelection.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/templatecard/TemplateCardButtonSelection.java
index f279eb2274..b74346a938 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/templatecard/TemplateCardButtonSelection.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/templatecard/TemplateCardButtonSelection.java
@@ -44,7 +44,7 @@ public JsonObject toJson() {
       btnObject.addProperty("selected_id", this.selectedId);
     }
 
-    if (this.optionList != null && this.optionList.size() > 0) {
+    if (this.optionList != null && !this.optionList.isEmpty()) {
       JsonArray optionJsonArray = new JsonArray();
       for (TemplateCardButtonSelectionOption jump : this.getOptionList()) {
         JsonObject tempObject = jump.toJson();
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 72f143fc37..2cd37821b5 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
@@ -130,7 +130,7 @@ public interface WxCpTpConfigStorage {
    * @return the aes key
    */
 //第三方应用的EncodingAESKey,用来检查签名
-  String getAesKey();
+  String getEncodingAESKey();
 
   /**
    * 企微服务商企业ID & 企业secret
@@ -146,6 +146,13 @@ public interface WxCpTpConfigStorage {
    */
   String getCorpSecret();
 
+  /**
+   * Sets provider secret.
+   *
+   * @param providerSecret the provider secret
+   */
+  void setProviderSecret(String providerSecret);
+
   /**
    * 服务商secret
    *
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/impl/AbstractWxCpInRedisConfigImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/impl/AbstractWxCpInRedisConfigImpl.java
index 780e722c30..a078e8cf9e 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/impl/AbstractWxCpInRedisConfigImpl.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/impl/AbstractWxCpInRedisConfigImpl.java
@@ -180,4 +180,17 @@ public boolean isAgentJsapiTicketExpired() {
     Long expire = redisOps.getExpire(this.agentJsapiTicketKey);
     return expire == null || expire < 2;
   }
+
+  @Override
+  public String toString() {
+    return "AbstractWxCpInRedisConfigImpl{" +
+      "corpId='" + getCorpId() + '\'' +
+      ", agentId=" + getAgentId() +
+      ", keyPrefix='" + keyPrefix + '\'' +
+      ", accessTokenKey='" + accessTokenKey + '\'' +
+      ", jsapiTicketKey='" + jsapiTicketKey + '\'' +
+      ", agentJsapiTicketKey='" + agentJsapiTicketKey + '\'' +
+      ", lockKey='" + lockKey + '\'' +
+      '}';
+  }
 }
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
new file mode 100644
index 0000000000..08eed33c16
--- /dev/null
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/impl/AbstractWxCpTpInRedisConfigImpl.java
@@ -0,0 +1,431 @@
+package me.chanjar.weixin.cp.config.impl;
+
+
+import lombok.Builder;
+import lombok.NonNull;
+import lombok.Setter;
+import me.chanjar.weixin.common.bean.WxAccessToken;
+import me.chanjar.weixin.common.redis.RedissonWxRedisOps;
+import me.chanjar.weixin.common.redis.WxRedisOps;
+import me.chanjar.weixin.common.util.http.apache.ApacheHttpClientBuilder;
+import me.chanjar.weixin.cp.bean.WxCpProviderToken;
+import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder;
+import org.apache.commons.lang3.StringUtils;
+import org.redisson.api.RedissonClient;
+
+import java.io.File;
+import java.io.Serializable;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.locks.Lock;
+
+/**
+ * 企业微信各种固定、授权配置的Redisson存储实现
+ */
+public abstract class AbstractWxCpTpInRedisConfigImpl extends WxCpTpDefaultConfigImpl implements Serializable {
+  private static final long serialVersionUID = -5385639031981770319L;
+
+  public AbstractWxCpTpInRedisConfigImpl(@NonNull WxRedisOps wxRedisOps) {
+    this(wxRedisOps, null);
+  }
+
+  public AbstractWxCpTpInRedisConfigImpl(@NonNull WxRedisOps wxRedisOps, String keyPrefix) {
+    this.wxRedisOps = wxRedisOps;
+    this.keyPrefix = keyPrefix;
+  }
+  /**
+   * The constant LOCK_KEY.
+   */
+// lock key
+  protected static final String LOCK_KEY = "wechat_tp_lock:";
+  /**
+   * The constant LOCKER_PROVIDER_ACCESS_TOKEN.
+   */
+  protected static final String LOCKER_PROVIDER_ACCESS_TOKEN = "providerAccessTokenLock";
+  /**
+   * The constant LOCKER_SUITE_ACCESS_TOKEN.
+   */
+  protected static final String LOCKER_SUITE_ACCESS_TOKEN = "suiteAccessTokenLock";
+  /**
+   * The constant LOCKER_ACCESS_TOKEN.
+   */
+  protected static final String LOCKER_ACCESS_TOKEN = "accessTokenLock";
+  /**
+   * The constant LOCKER_CORP_JSAPI_TICKET.
+   */
+  protected static final String LOCKER_CORP_JSAPI_TICKET = "corpJsapiTicketLock";
+  /**
+   * The constant LOCKER_SUITE_JSAPI_TICKET.
+   */
+  protected static final String LOCKER_SUITE_JSAPI_TICKET = "suiteJsapiTicketLock";
+  @NonNull
+  private final WxRedisOps wxRedisOps;
+  private final String suiteAccessTokenKey = ":suiteAccessTokenKey:";
+  private final String suiteTicketKey = ":suiteTicketKey:";
+  private final String accessTokenKey = ":accessTokenKey:";
+  private final String authCorpJsApiTicketKey = ":authCorpJsApiTicketKey:";
+  private final String authSuiteJsApiTicketKey = ":authSuiteJsApiTicketKey:";
+  private final String providerTokenKey = ":providerTokenKey:";
+  /**
+   * redis里面key的统一前缀
+   */
+  @Setter
+  private String keyPrefix = "";
+  private volatile String baseApiUrl;
+  private volatile String httpProxyHost;
+  private volatile int httpProxyPort;
+  private volatile String httpProxyUsername;
+  private volatile String httpProxyPassword;
+  private volatile ApacheHttpClientBuilder apacheHttpClientBuilder;
+  private volatile File tmpDirFile;
+
+
+  @Override
+  public void setBaseApiUrl(String baseUrl) {
+    this.baseApiUrl = baseUrl;
+  }
+
+  @Override
+  public String getApiUrl(String path) {
+    if (baseApiUrl == null) {
+      baseApiUrl = "https://qyapi.weixin.qq.com";
+    }
+    return baseApiUrl + path;
+  }
+
+
+  /**
+   * 第三方应用的suite access token相关
+   */
+  @Override
+  public String getSuiteAccessToken() {
+    return wxRedisOps.getValue(keyWithPrefix(suiteAccessTokenKey));
+  }
+
+  @Override
+  public WxAccessToken getSuiteAccessTokenEntity() {
+    String suiteAccessToken = wxRedisOps.getValue(keyWithPrefix(suiteAccessTokenKey));
+    Long expireIn = wxRedisOps.getExpire(keyWithPrefix(suiteAccessTokenKey));
+    if (StringUtils.isBlank(suiteAccessToken) || expireIn == null || expireIn == 0 || expireIn == -2) {
+      return new WxAccessToken();
+    }
+
+    WxAccessToken suiteAccessTokenEntity = new WxAccessToken();
+    suiteAccessTokenEntity.setAccessToken(suiteAccessToken);
+    suiteAccessTokenEntity.setExpiresIn(Math.max(Math.toIntExact(expireIn), 0));
+    return suiteAccessTokenEntity;
+  }
+
+  @Override
+  public boolean isSuiteAccessTokenExpired() {
+    //remain time to live in seconds, or key not exist
+    return wxRedisOps.getExpire(keyWithPrefix(suiteAccessTokenKey)) == 0L || wxRedisOps.getExpire(keyWithPrefix(suiteAccessTokenKey)) == -2;
+  }
+
+  @Override
+  public void expireSuiteAccessToken() {
+    wxRedisOps.expire(keyWithPrefix(suiteAccessTokenKey), 0, TimeUnit.SECONDS);
+  }
+
+  @Override
+  public void updateSuiteAccessToken(WxAccessToken suiteAccessToken) {
+    updateSuiteAccessToken(suiteAccessToken.getAccessToken(), suiteAccessToken.getExpiresIn());
+  }
+
+  @Override
+  public void updateSuiteAccessToken(String suiteAccessToken, int expiresInSeconds) {
+    wxRedisOps.setValue(keyWithPrefix(suiteAccessTokenKey), suiteAccessToken, expiresInSeconds, TimeUnit.SECONDS);
+  }
+
+  /**
+   * 第三方应用的suite ticket相关
+   */
+  @Override
+  public String getSuiteTicket() {
+    return wxRedisOps.getValue(keyWithPrefix(suiteTicketKey));
+  }
+
+  @Override
+  public boolean isSuiteTicketExpired() {
+    //remain time to live in seconds, or key not exist
+    return wxRedisOps.getExpire(keyWithPrefix(suiteTicketKey)) == 0L || wxRedisOps.getExpire(keyWithPrefix(suiteTicketKey)) == -2;
+  }
+
+  @Override
+  public void expireSuiteTicket() {
+    wxRedisOps.expire(keyWithPrefix(suiteTicketKey), 0, TimeUnit.SECONDS);
+  }
+
+  @Override
+  public void updateSuiteTicket(String suiteTicket, int expiresInSeconds) {
+    wxRedisOps.setValue(keyWithPrefix(suiteTicketKey), suiteTicket, expiresInSeconds, TimeUnit.SECONDS);
+  }
+
+  /**
+   * 第三方应用的其他配置,来自于企微配置
+   */
+  @Override
+  public String getSuiteId() {
+    return suiteId;
+  }
+
+  @Override
+  public String getSuiteSecret() {
+    return suiteSecret;
+  }
+
+  // 第三方应用的token,用来检查应用的签名
+  @Override
+  public String getToken() {
+    return token;
+  }
+
+  //第三方应用的EncodingAESKey,用来检查签名
+  @Override
+  public String getEncodingAESKey() {
+    return encodingAESKey;
+  }
+
+
+  /**
+   * 企微服务商企业ID & 企业secret, 来自于企微配置
+   */
+  @Override
+  public String getCorpId() {
+    return corpId;
+  }
+
+  @Override
+  public String getProviderSecret() {
+    return providerSecret;
+  }
+
+  @Override
+  public void setProviderSecret(String providerSecret) {
+    this.providerSecret = providerSecret;
+  }
+
+  /**
+   * 授权企业的access token相关
+   */
+  @Override
+  public String getAccessToken(String authCorpId) {
+    return wxRedisOps.getValue(keyWithPrefix(authCorpId) + accessTokenKey);
+  }
+
+  @Override
+  public WxAccessToken getAccessTokenEntity(String authCorpId) {
+    String accessToken = wxRedisOps.getValue(keyWithPrefix(authCorpId) + accessTokenKey);
+    Long expire = wxRedisOps.getExpire(keyWithPrefix(authCorpId) + accessTokenKey);
+    if (StringUtils.isBlank(accessToken) || expire == null || expire == 0 || expire == -2) {
+      return new WxAccessToken();
+    }
+
+    WxAccessToken accessTokenEntity = new WxAccessToken();
+    accessTokenEntity.setAccessToken(accessToken);
+    accessTokenEntity.setExpiresIn((int) ((expire - System.currentTimeMillis()) / 1000 + 200));
+    return accessTokenEntity;
+  }
+
+  @Override
+  public boolean isAccessTokenExpired(String authCorpId) {
+    //没有设置或者TTL为0,都是过期
+    return wxRedisOps.getExpire(keyWithPrefix(authCorpId) + accessTokenKey) == 0L
+      || wxRedisOps.getExpire(keyWithPrefix(authCorpId) + accessTokenKey) == -2;
+  }
+
+  @Override
+  public void expireAccessToken(String authCorpId) {
+    wxRedisOps.expire(keyWithPrefix(authCorpId) + accessTokenKey, 0, TimeUnit.SECONDS);
+  }
+
+  @Override
+  public void updateAccessToken(String authCorpId, String accessToken, int expiredInSeconds) {
+    wxRedisOps.setValue(keyWithPrefix(authCorpId) + accessTokenKey, accessToken, expiredInSeconds, TimeUnit.SECONDS);
+  }
+
+
+  /**
+   * 授权企业的js api ticket相关
+   */
+  @Override
+  public String getAuthCorpJsApiTicket(String authCorpId) {
+    return wxRedisOps.getValue(keyWithPrefix(authCorpId) + authCorpJsApiTicketKey);
+  }
+
+  @Override
+  public boolean isAuthCorpJsApiTicketExpired(String authCorpId) {
+    //没有设置或TTL为0,都是过期
+    return wxRedisOps.getExpire(keyWithPrefix(authCorpId) + authCorpJsApiTicketKey) == 0L
+      || wxRedisOps.getExpire(keyWithPrefix(authCorpId) + authCorpJsApiTicketKey) == -2;
+  }
+
+  @Override
+  public void expireAuthCorpJsApiTicket(String authCorpId) {
+    wxRedisOps.expire(keyWithPrefix(authCorpId) + authCorpJsApiTicketKey, 0, TimeUnit.SECONDS);
+  }
+
+  @Override
+  public void updateAuthCorpJsApiTicket(String authCorpId, String jsApiTicket, int expiredInSeconds) {
+    wxRedisOps.setValue(keyWithPrefix(authCorpId) + authCorpJsApiTicketKey, jsApiTicket, expiredInSeconds,
+      TimeUnit.SECONDS);
+  }
+
+
+  /**
+   * 授权企业的第三方应用js api ticket相关
+   */
+  @Override
+  public String getAuthSuiteJsApiTicket(String authCorpId) {
+    return wxRedisOps.getValue(keyWithPrefix(authCorpId) + authSuiteJsApiTicketKey);
+  }
+
+  @Override
+  public boolean isAuthSuiteJsApiTicketExpired(String authCorpId) {
+    //没有设置或者TTL为0,都是过期
+    return wxRedisOps.getExpire(keyWithPrefix(authCorpId) + authSuiteJsApiTicketKey) == 0L
+      || wxRedisOps.getExpire(keyWithPrefix(authCorpId) + authSuiteJsApiTicketKey) == -2;
+  }
+
+  @Override
+  public void expireAuthSuiteJsApiTicket(String authCorpId) {
+    wxRedisOps.expire(keyWithPrefix(authCorpId) + authSuiteJsApiTicketKey, 0, TimeUnit.SECONDS);
+  }
+
+  @Override
+  public void updateAuthSuiteJsApiTicket(String authCorpId, String jsApiTicket, int expiredInSeconds) {
+    wxRedisOps.setValue(keyWithPrefix(authCorpId) + authSuiteJsApiTicketKey, jsApiTicket, expiredInSeconds,
+      TimeUnit.SECONDS);
+  }
+
+  @Override
+  public boolean isProviderTokenExpired() {
+    //remain time to live in seconds, or key not exist
+    return wxRedisOps.getExpire(providerKeyWithPrefix(providerTokenKey)) == 0L || wxRedisOps.getExpire(providerKeyWithPrefix(providerTokenKey)) == -2;
+  }
+
+  @Override
+  public void updateProviderToken(String providerToken, int expiredInSeconds) {
+    wxRedisOps.setValue(providerKeyWithPrefix(providerTokenKey), providerToken, expiredInSeconds, TimeUnit.SECONDS);
+  }
+
+  @Override
+  public String getProviderToken() {
+    return wxRedisOps.getValue(providerKeyWithPrefix(providerTokenKey));
+  }
+
+  @Override
+  public WxCpProviderToken getProviderTokenEntity() {
+    String providerToken = wxRedisOps.getValue(providerKeyWithPrefix(providerTokenKey));
+    Long expire = wxRedisOps.getExpire(providerKeyWithPrefix(providerTokenKey));
+
+    if (StringUtils.isBlank(providerToken) || expire == null || expire == 0 || expire == -2) {
+      return new WxCpProviderToken();
+    }
+
+    WxCpProviderToken wxCpProviderToken = new WxCpProviderToken();
+    wxCpProviderToken.setProviderAccessToken(providerToken);
+    wxCpProviderToken.setExpiresIn(Math.max(Math.toIntExact(expire), 0));
+    return wxCpProviderToken;
+  }
+
+  @Override
+  public void expireProviderToken() {
+    wxRedisOps.expire(providerKeyWithPrefix(providerTokenKey), 0, TimeUnit.SECONDS);
+  }
+
+  /**
+   * 网络代理相关
+   */
+  @Override
+  public String getHttpProxyHost() {
+    return this.httpProxyHost;
+  }
+
+  @Override
+  public int getHttpProxyPort() {
+    return this.httpProxyPort;
+  }
+
+  @Override
+  public String getHttpProxyUsername() {
+    return this.httpProxyUsername;
+  }
+
+  @Override
+  public String getHttpProxyPassword() {
+    return this.httpProxyPassword;
+  }
+
+  @Override
+  public File getTmpDirFile() {
+    return tmpDirFile;
+  }
+
+  @Override
+  public Lock getProviderAccessTokenLock() {
+    return getProviderLockByKey(String.join(":", this.corpId, LOCKER_PROVIDER_ACCESS_TOKEN));
+  }
+
+  @Override
+  public Lock getSuiteAccessTokenLock() {
+    return getLockByKey(LOCKER_SUITE_ACCESS_TOKEN);
+  }
+
+  @Override
+  public Lock getAccessTokenLock(String authCorpId) {
+    return getLockByKey(String.join(":", authCorpId, LOCKER_ACCESS_TOKEN));
+  }
+
+  @Override
+  public Lock getAuthCorpJsapiTicketLock(String authCorpId) {
+    return getLockByKey(String.join(":", authCorpId, LOCKER_CORP_JSAPI_TICKET));
+  }
+
+  @Override
+  public Lock getSuiteJsapiTicketLock(String authCorpId) {
+    return getLockByKey(String.join(":", authCorpId, LOCKER_SUITE_JSAPI_TICKET));
+  }
+
+  private Lock getLockByKey(String key) {
+    // 最终key的模式:(keyPrefix:)wechat_tp_lock:suiteId:(authCorpId):lockKey
+    // 其中keyPrefix目前不支持外部配置,authCorpId只有涉及到corpAccessToken, suiteJsapiTicket, authCorpJsapiTicket时才会拼上
+    return this.wxRedisOps.getLock(String.join(":", keyWithPrefix(LOCK_KEY + this.suiteId), key));
+  }
+
+  /**
+   * 单独处理provider,且不应和suite 有关系
+   */
+  private Lock getProviderLockByKey(String key) {
+    return this.wxRedisOps.getLock(String.join(":", providerKeyWithPrefix(LOCK_KEY), key));
+  }
+
+  @Override
+  public ApacheHttpClientBuilder getApacheHttpClientBuilder() {
+    return this.apacheHttpClientBuilder;
+  }
+
+  @Override
+  public boolean autoRefreshToken() {
+    return false;
+  }
+
+  @Override
+  public String toString() {
+    return WxCpGsonBuilder.create().toJson(this);
+  }
+
+  /**
+   * 一个provider 会有多个suite,需要唯一标识作为前缀
+   */
+  private String keyWithPrefix(String key) {
+    return keyPrefix + ":" + suiteId + ":" + key;
+  }
+
+  /**
+   * provider 应该独享一个key,且不和任何suite关联
+   * 一个provider  会有多个suite,不同的suite 都应该指向同一个provider 的数据
+   */
+  private String providerKeyWithPrefix(String key) {
+    return keyPrefix + ":" + corpId + ":" + key;
+  }
+}
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/impl/WxCpCorpGroupDefaultConfigImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/impl/WxCpCorpGroupDefaultConfigImpl.java
index bef838c90d..b3d4834426 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/impl/WxCpCorpGroupDefaultConfigImpl.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/impl/WxCpCorpGroupDefaultConfigImpl.java
@@ -18,6 +18,8 @@
  * @author libo
  */
 public class WxCpCorpGroupDefaultConfigImpl implements WxCpCorpGroupConfigStorage, Serializable {
+  private static final long serialVersionUID = -8392908346536154435L;
+
   private final transient Map corpAccessTokenLocker = new ConcurrentHashMap<>();
 
   private final Map corpAccessTokenMap = new HashMap<>();
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/impl/WxCpCorpGroupRedissonConfigImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/impl/WxCpCorpGroupRedissonConfigImpl.java
index 8146b580d0..1ef05ba8b3 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/impl/WxCpCorpGroupRedissonConfigImpl.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/impl/WxCpCorpGroupRedissonConfigImpl.java
@@ -23,6 +23,8 @@
  */
 @Builder
 public class WxCpCorpGroupRedissonConfigImpl implements WxCpCorpGroupConfigStorage, Serializable {
+  private static final long serialVersionUID = 1269450173683931930L;
+
   private final transient Map corpAccessTokenLocker = new ConcurrentHashMap<>();
 
   /**
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 a4b8af4677..fc124251f5 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
@@ -28,20 +28,32 @@ public class WxCpTpDefaultConfigImpl implements WxCpTpConfigStorage, Serializabl
   private final transient Map accessTokenLocker = new ConcurrentHashMap<>();
   private final transient Map authCorpJsapiTicketLocker = new ConcurrentHashMap<>();
   private final transient Map authSuiteJsapiTicketLocker = new ConcurrentHashMap<>();
-  private volatile String corpId;
-  private volatile String corpSecret;
+  /**
+   * 企微服务商企业ID & 企业secret,来自于企微配置
+   */
+  protected volatile String corpId;
   /**
    * 服务商secret
    */
-  private volatile String providerSecret;
+  protected volatile String providerSecret;
   private volatile String providerToken;
   private volatile long providerTokenExpiresTime;
-  private volatile String suiteId;
-  private volatile String suiteSecret;
-  private volatile String token;
+  /**
+   * 第三方应用的其他配置,来自于企微配置
+   */
+  protected volatile String suiteId;
+
+  protected volatile String suiteSecret;
+  /**
+   * 第三方应用的token,用来检查应用的签名
+   */
+  protected volatile String token;
   private volatile String suiteAccessToken;
   private volatile long suiteAccessTokenExpiresTime;
-  private volatile String aesKey;
+  /**
+   * 第三方应用的EncodingAESKey,用来检查签名
+   */
+  protected volatile String encodingAESKey;
   private volatile String suiteTicket;
   private volatile long suiteTicketExpiresTime;
   private volatile String oauth2redirectUri;
@@ -186,11 +198,10 @@ public String getSuiteId() {
   /**
    * Sets suite id.
    *
-   * @param corpId the corp id
+   * @param suiteId
    */
-  @Deprecated
-  public void setSuiteId(String corpId) {
-    this.suiteId = corpId;
+  public void setSuiteId(String suiteId) {
+    this.suiteId = suiteId;
   }
 
   @Override
@@ -200,10 +211,7 @@ public String getSuiteSecret() {
 
   /**
    * Sets suite secret.
-   *
-   * @param corpSecret the corp secret
    */
-  @Deprecated
   public void setSuiteSecret(String corpSecret) {
     this.suiteSecret = corpSecret;
   }
@@ -218,24 +226,22 @@ public String getToken() {
    *
    * @param token the token
    */
-  @Deprecated
   public void setToken(String token) {
     this.token = token;
   }
 
   @Override
-  public String getAesKey() {
-    return this.aesKey;
+  public String getEncodingAESKey() {
+    return this.encodingAESKey;
   }
 
   /**
-   * Sets aes key.
+   * Sets aes key. encodingAESKey
    *
-   * @param aesKey the aes key
+   * @param encodingAESKey the aes key
    */
-  @Deprecated
-  public void setAesKey(String aesKey) {
-    this.aesKey = aesKey;
+  public void setEncodingAESKey(String encodingAESKey) {
+    this.encodingAESKey = encodingAESKey;
   }
 
 
@@ -249,24 +255,19 @@ public String getCorpId() {
    *
    * @param corpId the corp id
    */
-  @Deprecated
   public void setCorpId(String corpId) {
     this.corpId = corpId;
   }
 
   @Override
   public String getCorpSecret() {
-    return this.corpSecret;
+    return this.providerSecret;
   }
 
-  /**
-   * Sets corp secret.
-   *
-   * @param corpSecret the corp secret
-   */
-  @Deprecated
-  public void setCorpSecret(String corpSecret) {
-    this.corpSecret = corpSecret;
+
+  @Override
+  public void setProviderSecret(String providerSecret) {
+    this.providerSecret = providerSecret;
   }
 
   @Override
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/impl/WxCpTpJedisConfigImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/impl/WxCpTpJedisConfigImpl.java
new file mode 100644
index 0000000000..045761256b
--- /dev/null
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/impl/WxCpTpJedisConfigImpl.java
@@ -0,0 +1,24 @@
+package me.chanjar.weixin.cp.config.impl;
+
+import lombok.NonNull;
+import me.chanjar.weixin.common.redis.JedisWxRedisOps;
+import redis.clients.jedis.Jedis;
+import redis.clients.jedis.util.Pool;
+
+/**
+ * 基于 jedis 的实现
+ *
+ * @author yl
+ * created on  2023/04/23
+ */
+public class WxCpTpJedisConfigImpl extends AbstractWxCpTpInRedisConfigImpl {
+  private static final long serialVersionUID = -1869372247414407433L;
+
+  public WxCpTpJedisConfigImpl(Pool jedisPool) {
+    this(jedisPool, null);
+  }
+
+  public WxCpTpJedisConfigImpl(@NonNull Pool jedisPool, String keyPrefix) {
+    super(new JedisWxRedisOps(jedisPool), keyPrefix);
+  }
+}
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/impl/WxCpTpRedisTemplateConfigImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/impl/WxCpTpRedisTemplateConfigImpl.java
new file mode 100644
index 0000000000..0a94afc797
--- /dev/null
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/impl/WxCpTpRedisTemplateConfigImpl.java
@@ -0,0 +1,24 @@
+package me.chanjar.weixin.cp.config.impl;
+
+import lombok.Builder;
+import lombok.NonNull;
+import me.chanjar.weixin.common.redis.RedisTemplateWxRedisOps;
+import org.springframework.data.redis.core.StringRedisTemplate;
+
+/**
+ * 基于 RedisTemplate 的实现
+ *
+ * @author yl
+ * created on  2023/04/23
+ */
+public class WxCpTpRedisTemplateConfigImpl extends AbstractWxCpTpInRedisConfigImpl {
+  private static final long serialVersionUID = -1660004125413310620L;
+
+  public WxCpTpRedisTemplateConfigImpl(@NonNull StringRedisTemplate stringRedisTemplate) {
+    this(stringRedisTemplate, null);
+  }
+
+  public WxCpTpRedisTemplateConfigImpl(@NonNull StringRedisTemplate stringRedisTemplate, String keyPrefix) {
+    super(new RedisTemplateWxRedisOps(stringRedisTemplate), keyPrefix);
+  }
+}
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/impl/WxCpTpRedissonConfigImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/impl/WxCpTpRedissonConfigImpl.java
index 02193cfd33..fe8723a8a0 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/impl/WxCpTpRedissonConfigImpl.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/impl/WxCpTpRedissonConfigImpl.java
@@ -1,444 +1,25 @@
 package me.chanjar.weixin.cp.config.impl;
 
-
 import lombok.Builder;
+import lombok.Data;
 import lombok.NonNull;
-import lombok.Setter;
-import me.chanjar.weixin.common.bean.WxAccessToken;
-import me.chanjar.weixin.common.redis.WxRedisOps;
-import me.chanjar.weixin.common.util.http.apache.ApacheHttpClientBuilder;
-import me.chanjar.weixin.cp.bean.WxCpProviderToken;
-import me.chanjar.weixin.cp.config.WxCpTpConfigStorage;
-import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder;
-import org.apache.commons.lang3.StringUtils;
-
-import java.io.File;
-import java.io.Serializable;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.locks.Lock;
+import me.chanjar.weixin.common.redis.RedissonWxRedisOps;
+import org.redisson.api.RedissonClient;
 
 /**
- * 企业微信各种固定、授权配置的Redisson存储实现
+ * 基于Redisson的实现
+ *
+ * @author yuanqixun  created on  2020 /5/13
+ * @author yl
  */
-@Builder
-public class WxCpTpRedissonConfigImpl implements WxCpTpConfigStorage, Serializable {
-  private static final long serialVersionUID = -5385639031981770319L;
-
-  /**
-   * The constant LOCK_KEY.
-   */
-// lock key
-  protected static final String LOCK_KEY = "wechat_tp_lock:";
-  /**
-   * The constant LOCKER_PROVIDER_ACCESS_TOKEN.
-   */
-  protected static final String LOCKER_PROVIDER_ACCESS_TOKEN = "providerAccessTokenLock";
-  /**
-   * The constant LOCKER_SUITE_ACCESS_TOKEN.
-   */
-  protected static final String LOCKER_SUITE_ACCESS_TOKEN = "suiteAccessTokenLock";
-  /**
-   * The constant LOCKER_ACCESS_TOKEN.
-   */
-  protected static final String LOCKER_ACCESS_TOKEN = "accessTokenLock";
-  /**
-   * The constant LOCKER_CORP_JSAPI_TICKET.
-   */
-  protected static final String LOCKER_CORP_JSAPI_TICKET = "corpJsapiTicketLock";
-  /**
-   * The constant LOCKER_SUITE_JSAPI_TICKET.
-   */
-  protected static final String LOCKER_SUITE_JSAPI_TICKET = "suiteJsapiTicketLock";
-  @NonNull
-  private final WxRedisOps wxRedisOps;
-  private final String suiteAccessTokenKey = ":suiteAccessTokenKey:";
-  private final String suiteTicketKey = ":suiteTicketKey:";
-  private final String accessTokenKey = ":accessTokenKey:";
-  private final String authCorpJsApiTicketKey = ":authCorpJsApiTicketKey:";
-  private final String authSuiteJsApiTicketKey = ":authSuiteJsApiTicketKey:";
-  private final String providerTokenKey = ":providerTokenKey:";
-  /**
-   * redis里面key的统一前缀
-   */
-  @Setter
-  private String keyPrefix = "";
-  private volatile String baseApiUrl;
-  private volatile String httpProxyHost;
-  private volatile int httpProxyPort;
-  private volatile String httpProxyUsername;
-  private volatile String httpProxyPassword;
-  private volatile ApacheHttpClientBuilder apacheHttpClientBuilder;
-  private volatile File tmpDirFile;
-  /**
-   * 第三方应用的其他配置,来自于企微配置
-   */
-  private volatile String suiteId;
-  private volatile String suiteSecret;
-  /**
-   * 第三方应用的token,用来检查应用的签名
-   */
-  private volatile String token;
-  /**
-   * 第三方应用的EncodingAESKey,用来检查签名
-   */
-  private volatile String aesKey;
-  /**
-   * 企微服务商企业ID & 企业secret,来自于企微配置
-   */
-  private volatile String corpId;
-  private volatile String corpSecret;
-  /**
-   * 服务商secret
-   */
-  private volatile String providerSecret;
-
-  @Override
-  public void setBaseApiUrl(String baseUrl) {
-    this.baseApiUrl = baseUrl;
-  }
-
-  @Override
-  public String getApiUrl(String path) {
-    if (baseApiUrl == null) {
-      baseApiUrl = "https://qyapi.weixin.qq.com";
-    }
-    return baseApiUrl + path;
-  }
-
-
-  /**
-   * 第三方应用的suite access token相关
-   */
-  @Override
-  public String getSuiteAccessToken() {
-    return wxRedisOps.getValue(keyWithPrefix(suiteAccessTokenKey));
-  }
-
-  @Override
-  public WxAccessToken getSuiteAccessTokenEntity() {
-    String suiteAccessToken = wxRedisOps.getValue(keyWithPrefix(suiteAccessTokenKey));
-    Long expireIn = wxRedisOps.getExpire(keyWithPrefix(suiteAccessTokenKey));
-    if (StringUtils.isBlank(suiteAccessToken) || expireIn == null || expireIn == 0 || expireIn == -2) {
-      return new WxAccessToken();
-    }
-
-    WxAccessToken suiteAccessTokenEntity = new WxAccessToken();
-    suiteAccessTokenEntity.setAccessToken(suiteAccessToken);
-    suiteAccessTokenEntity.setExpiresIn(Math.max(Math.toIntExact(expireIn), 0));
-    return suiteAccessTokenEntity;
-  }
-
-  @Override
-  public boolean isSuiteAccessTokenExpired() {
-    //remain time to live in seconds, or key not exist
-    return wxRedisOps.getExpire(keyWithPrefix(suiteAccessTokenKey)) == 0L || wxRedisOps.getExpire(keyWithPrefix(suiteAccessTokenKey)) == -2;
-  }
-
-  @Override
-  public void expireSuiteAccessToken() {
-    wxRedisOps.expire(keyWithPrefix(suiteAccessTokenKey), 0, TimeUnit.SECONDS);
-  }
-
-  @Override
-  public void updateSuiteAccessToken(WxAccessToken suiteAccessToken) {
-    updateSuiteAccessToken(suiteAccessToken.getAccessToken(), suiteAccessToken.getExpiresIn());
-  }
-
-  @Override
-  public void updateSuiteAccessToken(String suiteAccessToken, int expiresInSeconds) {
-    wxRedisOps.setValue(keyWithPrefix(suiteAccessTokenKey), suiteAccessToken, expiresInSeconds, TimeUnit.SECONDS);
-  }
-
-  /**
-   * 第三方应用的suite ticket相关
-   */
-  @Override
-  public String getSuiteTicket() {
-    return wxRedisOps.getValue(keyWithPrefix(suiteTicketKey));
-  }
-
-  @Override
-  public boolean isSuiteTicketExpired() {
-    //remain time to live in seconds, or key not exist
-    return wxRedisOps.getExpire(keyWithPrefix(suiteTicketKey)) == 0L || wxRedisOps.getExpire(keyWithPrefix(suiteTicketKey)) == -2;
-  }
-
-  @Override
-  public void expireSuiteTicket() {
-    wxRedisOps.expire(keyWithPrefix(suiteTicketKey), 0, TimeUnit.SECONDS);
-  }
-
-  @Override
-  public void updateSuiteTicket(String suiteTicket, int expiresInSeconds) {
-    wxRedisOps.setValue(keyWithPrefix(suiteTicketKey), suiteTicket, expiresInSeconds, TimeUnit.SECONDS);
-  }
-
-  /**
-   * 第三方应用的其他配置,来自于企微配置
-   */
-  @Override
-  public String getSuiteId() {
-    return suiteId;
-  }
-
-  @Override
-  public String getSuiteSecret() {
-    return suiteSecret;
-  }
-
-  // 第三方应用的token,用来检查应用的签名
-  @Override
-  public String getToken() {
-    return token;
-  }
-
-  //第三方应用的EncodingAESKey,用来检查签名
-  @Override
-  public String getAesKey() {
-    return aesKey;
-  }
-
-
-  /**
-   * 企微服务商企业ID & 企业secret, 来自于企微配置
-   */
-  @Override
-  public String getCorpId() {
-    return corpId;
-  }
-
-  @Override
-  public String getCorpSecret() {
-    return corpSecret;
-  }
-
-  @Override
-  public String getProviderSecret() {
-    return providerSecret;
-  }
-
-  /**
-   * 授权企业的access token相关
-   */
-  @Override
-  public String getAccessToken(String authCorpId) {
-    return wxRedisOps.getValue(keyWithPrefix(authCorpId) + accessTokenKey);
-  }
-
-  @Override
-  public WxAccessToken getAccessTokenEntity(String authCorpId) {
-    String accessToken = wxRedisOps.getValue(keyWithPrefix(authCorpId) + accessTokenKey);
-    Long expire = wxRedisOps.getExpire(keyWithPrefix(authCorpId) + accessTokenKey);
-    if (StringUtils.isBlank(accessToken) || expire == null || expire == 0 || expire == -2) {
-      return new WxAccessToken();
-    }
-
-    WxAccessToken accessTokenEntity = new WxAccessToken();
-    accessTokenEntity.setAccessToken(accessToken);
-    accessTokenEntity.setExpiresIn((int) ((expire - System.currentTimeMillis()) / 1000 + 200));
-    return accessTokenEntity;
-  }
-
-  @Override
-  public boolean isAccessTokenExpired(String authCorpId) {
-    //没有设置或者TTL为0,都是过期
-    return wxRedisOps.getExpire(keyWithPrefix(authCorpId) + accessTokenKey) == 0L
-      || wxRedisOps.getExpire(keyWithPrefix(authCorpId) + accessTokenKey) == -2;
-  }
-
-  @Override
-  public void expireAccessToken(String authCorpId) {
-    wxRedisOps.expire(keyWithPrefix(authCorpId) + accessTokenKey, 0, TimeUnit.SECONDS);
-  }
-
-  @Override
-  public void updateAccessToken(String authCorpId, String accessToken, int expiredInSeconds) {
-    wxRedisOps.setValue(keyWithPrefix(authCorpId) + accessTokenKey, accessToken, expiredInSeconds, TimeUnit.SECONDS);
-  }
-
-
-  /**
-   * 授权企业的js api ticket相关
-   */
-  @Override
-  public String getAuthCorpJsApiTicket(String authCorpId) {
-    return wxRedisOps.getValue(keyWithPrefix(authCorpId) + authCorpJsApiTicketKey);
-  }
-
-  @Override
-  public boolean isAuthCorpJsApiTicketExpired(String authCorpId) {
-    //没有设置或TTL为0,都是过期
-    return wxRedisOps.getExpire(keyWithPrefix(authCorpId) + authCorpJsApiTicketKey) == 0L
-      || wxRedisOps.getExpire(keyWithPrefix(authCorpId) + authCorpJsApiTicketKey) == -2;
-  }
-
-  @Override
-  public void expireAuthCorpJsApiTicket(String authCorpId) {
-    wxRedisOps.expire(keyWithPrefix(authCorpId) + authCorpJsApiTicketKey, 0, TimeUnit.SECONDS);
-  }
-
-  @Override
-  public void updateAuthCorpJsApiTicket(String authCorpId, String jsApiTicket, int expiredInSeconds) {
-    wxRedisOps.setValue(keyWithPrefix(authCorpId) + authCorpJsApiTicketKey, jsApiTicket, expiredInSeconds,
-      TimeUnit.SECONDS);
-  }
-
-
-  /**
-   * 授权企业的第三方应用js api ticket相关
-   */
-  @Override
-  public String getAuthSuiteJsApiTicket(String authCorpId) {
-    return wxRedisOps.getValue(keyWithPrefix(authCorpId) + authSuiteJsApiTicketKey);
-  }
-
-  @Override
-  public boolean isAuthSuiteJsApiTicketExpired(String authCorpId) {
-    //没有设置或者TTL为0,都是过期
-    return wxRedisOps.getExpire(keyWithPrefix(authCorpId) + authSuiteJsApiTicketKey) == 0L
-      || wxRedisOps.getExpire(keyWithPrefix(authCorpId) + authSuiteJsApiTicketKey) == -2;
-  }
-
-  @Override
-  public void expireAuthSuiteJsApiTicket(String authCorpId) {
-    wxRedisOps.expire(keyWithPrefix(authCorpId) + authSuiteJsApiTicketKey, 0, TimeUnit.SECONDS);
-  }
-
-  @Override
-  public void updateAuthSuiteJsApiTicket(String authCorpId, String jsApiTicket, int expiredInSeconds) {
-    wxRedisOps.setValue(keyWithPrefix(authCorpId) + authSuiteJsApiTicketKey, jsApiTicket, expiredInSeconds,
-      TimeUnit.SECONDS);
-  }
-
-  @Override
-  public boolean isProviderTokenExpired() {
-    //remain time to live in seconds, or key not exist
-    return wxRedisOps.getExpire(providerKeyWithPrefix(providerTokenKey)) == 0L || wxRedisOps.getExpire(providerKeyWithPrefix(providerTokenKey)) == -2;
-  }
-
-  @Override
-  public void updateProviderToken(String providerToken, int expiredInSeconds) {
-    wxRedisOps.setValue(providerKeyWithPrefix(providerTokenKey), providerToken, expiredInSeconds, TimeUnit.SECONDS);
-  }
-
-  @Override
-  public String getProviderToken() {
-    return wxRedisOps.getValue(providerKeyWithPrefix(providerTokenKey));
-  }
-
-  @Override
-  public WxCpProviderToken getProviderTokenEntity() {
-    String providerToken = wxRedisOps.getValue(providerKeyWithPrefix(providerTokenKey));
-    Long expire = wxRedisOps.getExpire(providerKeyWithPrefix(providerTokenKey));
-
-    if (StringUtils.isBlank(providerToken) || expire == null || expire == 0 || expire == -2) {
-      return new WxCpProviderToken();
-    }
-
-    WxCpProviderToken wxCpProviderToken = new WxCpProviderToken();
-    wxCpProviderToken.setProviderAccessToken(providerToken);
-    wxCpProviderToken.setExpiresIn(Math.max(Math.toIntExact(expire), 0));
-    return wxCpProviderToken;
-  }
-
-  @Override
-  public void expireProviderToken() {
-    wxRedisOps.expire(providerKeyWithPrefix(providerTokenKey), 0, TimeUnit.SECONDS);
-  }
-
-  /**
-   * 网络代理相关
-   */
-  @Override
-  public String getHttpProxyHost() {
-    return this.httpProxyHost;
-  }
-
-  @Override
-  public int getHttpProxyPort() {
-    return this.httpProxyPort;
-  }
-
-  @Override
-  public String getHttpProxyUsername() {
-    return this.httpProxyUsername;
-  }
-
-  @Override
-  public String getHttpProxyPassword() {
-    return this.httpProxyPassword;
-  }
-
-  @Override
-  public File getTmpDirFile() {
-    return tmpDirFile;
-  }
-
-  @Override
-  public Lock getProviderAccessTokenLock() {
-    return getProviderLockByKey(String.join(":", this.corpId, LOCKER_PROVIDER_ACCESS_TOKEN));
-  }
-
-  @Override
-  public Lock getSuiteAccessTokenLock() {
-    return getLockByKey(LOCKER_SUITE_ACCESS_TOKEN);
-  }
-
-  @Override
-  public Lock getAccessTokenLock(String authCorpId) {
-    return getLockByKey(String.join(":", authCorpId, LOCKER_ACCESS_TOKEN));
-  }
-
-  @Override
-  public Lock getAuthCorpJsapiTicketLock(String authCorpId) {
-    return getLockByKey(String.join(":", authCorpId, LOCKER_CORP_JSAPI_TICKET));
-  }
-
-  @Override
-  public Lock getSuiteJsapiTicketLock(String authCorpId) {
-    return getLockByKey(String.join(":", authCorpId, LOCKER_SUITE_JSAPI_TICKET));
-  }
-
-  private Lock getLockByKey(String key) {
-    // 最终key的模式:(keyPrefix:)wechat_tp_lock:suiteId:(authCorpId):lockKey
-    // 其中keyPrefix目前不支持外部配置,authCorpId只有涉及到corpAccessToken, suiteJsapiTicket, authCorpJsapiTicket时才会拼上
-    return this.wxRedisOps.getLock(String.join(":", keyWithPrefix(LOCK_KEY + this.suiteId), key));
-  }
-
-  /**
-   * 单独处理provider,且不应和suite 有关系
-   */
-  private Lock getProviderLockByKey(String key) {
-    return this.wxRedisOps.getLock(String.join(":", providerKeyWithPrefix(LOCK_KEY), key));
-  }
-
-  @Override
-  public ApacheHttpClientBuilder getApacheHttpClientBuilder() {
-    return this.apacheHttpClientBuilder;
-  }
-
-  @Override
-  public boolean autoRefreshToken() {
-    return false;
-  }
-
-  @Override
-  public String toString() {
-    return WxCpGsonBuilder.create().toJson(this);
-  }
+public class WxCpTpRedissonConfigImpl extends AbstractWxCpTpInRedisConfigImpl {
+  private static final long serialVersionUID = -5674792341070783967L;
 
-  /**
-   * 一个provider 会有多个suite,需要唯一标识作为前缀
-   */
-  private String keyWithPrefix(String key) {
-    return keyPrefix + ":" + suiteId + ":" + key;
+  public WxCpTpRedissonConfigImpl(@NonNull RedissonClient redissonClient) {
+    this(redissonClient, null);
   }
 
-  /**
-   * provider 应该独享一个key,且不和任何suite关联
-   * 一个provider  会有多个suite,不同的suite 都应该指向同一个provider 的数据
-   */
-  private String providerKeyWithPrefix(String key) {
-    return keyPrefix + ":" + corpId + ":" + key;
+  public WxCpTpRedissonConfigImpl(@NonNull RedissonClient redissonClient, String keyPrefix) {
+    super(new RedissonWxRedisOps(redissonClient), keyPrefix);
   }
 }
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 3aecf72120..fb6f8ef686 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
@@ -130,6 +130,10 @@ interface WorkBench {
      * The constant WORKBENCH_DATA_SET.
      */
     String WORKBENCH_DATA_SET = "/cgi-bin/agent/set_workbench_data";
+    /**
+     * The constant WORKBENCH_BATCH_DATA_SET.
+     */
+    String WORKBENCH_BATCH_DATA_SET = "/cgi-bin/agent/batch_set_workbench_data";
   }
 
   /**
@@ -234,6 +238,12 @@ interface Media {
      * The constant JSSDK_MEDIA_GET.
      */
     String JSSDK_MEDIA_GET = "/cgi-bin/media/get/jssdk";
+
+    /** The constant GET_UPLOAD_BY_URL_RESULT. */
+    String GET_UPLOAD_BY_URL_RESULT = "/cgi-bin/media/get_upload_by_url_result";
+
+    /** The constant UPLOAD_BY_URL. */
+    String UPLOAD_BY_URL = "/cgi-bin/media/upload_by_url";
   }
 
   /**
@@ -845,6 +855,10 @@ interface Tp {
      * The constant GET_PERMANENT_CODE.
      */
     String GET_PERMANENT_CODE = "/cgi-bin/service/get_permanent_code";
+    /**
+     * The constant GET_V2_PERMANENT_CODE.
+     */
+    String GET_V2_PERMANENT_CODE = "/cgi-bin/service/v2/get_permanent_code";
     /**
      * The constant GET_SUITE_TOKEN.
      */
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 606dcea6d2..ff3f8e0e6c 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
@@ -219,6 +219,11 @@ public static class EventType {
      */
     public static final String CUSTOMER_ACQUISITION = "customer_acquisition";
 
+    /**
+     * 异步上传临时素材结果回调通知
+     */
+    public static final String UPLOAD_MEDIA_JOB_FINISH = "upload_media_job_finish";
+
   }
 
   /**
@@ -625,6 +630,11 @@ public static class GroupRobotMsgType {
      */
     public static final String MARKDOWN = "markdown";
 
+    /**
+     * markdown_v2消息.
+     */
+    public static final String MARKDOWN_V2 = "markdown_v2";
+
     /**
      * 图文消息(点击跳转到外链).
      */
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 5fb56cc157..9991073739 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
@@ -17,7 +17,6 @@
 import me.chanjar.weixin.cp.bean.corpgroup.WxCpCorpGroupCorpGetTokenReq;
 import me.chanjar.weixin.cp.bean.corpgroup.WxCpMaTransferSession;
 import me.chanjar.weixin.cp.config.WxCpCorpGroupConfigStorage;
-import me.chanjar.weixin.cp.config.WxCpTpConfigStorage;
 import me.chanjar.weixin.cp.corpgroup.service.WxCpCgService;
 import me.chanjar.weixin.cp.corpgroup.service.WxCpLinkedCorpService;
 
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/corpgroup/service/impl/WxCpCgServiceApacheHttpClientImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/corpgroup/service/impl/WxCpCgServiceApacheHttpClientImpl.java
index fde2c76bb2..13349c3d80 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/corpgroup/service/impl/WxCpCgServiceApacheHttpClientImpl.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/corpgroup/service/impl/WxCpCgServiceApacheHttpClientImpl.java
@@ -1,6 +1,6 @@
 package me.chanjar.weixin.cp.corpgroup.service.impl;
 
-import me.chanjar.weixin.common.util.http.HttpType;
+import me.chanjar.weixin.common.util.http.HttpClientType;
 import me.chanjar.weixin.common.util.http.apache.ApacheHttpClientBuilder;
 import me.chanjar.weixin.common.util.http.apache.DefaultApacheHttpClientBuilder;
 import org.apache.http.HttpHost;
@@ -25,8 +25,8 @@ public HttpHost getRequestHttpProxy() {
   }
 
   @Override
-  public HttpType getRequestType() {
-    return HttpType.APACHE_HTTP;
+  public HttpClientType getRequestType() {
+    return HttpClientType.APACHE_HTTP;
   }
 
   @Override
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
new file mode 100644
index 0000000000..d5c60ad037
--- /dev/null
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/corpgroup/service/impl/WxCpCgServiceHttpComponentsImpl.java
@@ -0,0 +1,47 @@
+package me.chanjar.weixin.cp.corpgroup.service.impl;
+
+import me.chanjar.weixin.common.util.http.HttpClientType;
+import me.chanjar.weixin.common.util.http.hc.DefaultHttpComponentsClientBuilder;
+import me.chanjar.weixin.common.util.http.hc.HttpComponentsClientBuilder;
+import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
+import org.apache.hc.core5.http.HttpHost;
+
+/**
+ * @author altusea
+ */
+public class WxCpCgServiceHttpComponentsImpl extends BaseWxCpCgServiceImpl {
+  private CloseableHttpClient httpClient;
+  private HttpHost httpProxy;
+
+  @Override
+  public CloseableHttpClient getRequestHttpClient() {
+    return httpClient;
+  }
+
+  @Override
+  public HttpHost getRequestHttpProxy() {
+    return httpProxy;
+  }
+
+  @Override
+  public HttpClientType getRequestType() {
+    return HttpClientType.HTTP_COMPONENTS;
+  }
+
+  @Override
+  public void initHttp() {
+    HttpComponentsClientBuilder apacheHttpClientBuilder = DefaultHttpComponentsClientBuilder.get();
+
+    apacheHttpClientBuilder.httpProxyHost(this.configStorage.getHttpProxyHost())
+      .httpProxyPort(this.configStorage.getHttpProxyPort())
+      .httpProxyUsername(this.configStorage.getHttpProxyUsername())
+      .httpProxyPassword(this.configStorage.getHttpProxyPassword().toCharArray());
+
+    if (this.configStorage.getHttpProxyHost() != null && this.configStorage.getHttpProxyPort() > 0) {
+      this.httpProxy = new HttpHost(this.configStorage.getHttpProxyHost(), this.configStorage.getHttpProxyPort());
+    }
+
+    this.httpClient = apacheHttpClientBuilder.build();
+  }
+
+}
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/message/WxCpMessageRouter.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/message/WxCpMessageRouter.java
index a2417ec86d..94f0838a9d 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/message/WxCpMessageRouter.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/message/WxCpMessageRouter.java
@@ -205,12 +205,12 @@ public WxCpXmlOutMessage route(final WxCpXmlMessage wxMessage, final Map futures = new ArrayList<>();
+    final List> futures = new ArrayList<>();
     for (final WxCpMessageRouterRule rule : matchRules) {
       // 返回最后一个非异步的rule的执行结果
       if (rule.isAsync()) {
@@ -228,9 +228,9 @@ public WxCpXmlOutMessage route(final WxCpXmlMessage wxMessage, final Map 0) {
+    if (!futures.isEmpty()) {
       this.executorService.submit(() -> {
-        for (Future future : futures) {
+        for (Future future : futures) {
           try {
             future.get();
             log.debug("End session access: async=true, sessionId={}", wxMessage.getFromUserName());
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/message/WxCpTpMessageRouter.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/message/WxCpTpMessageRouter.java
index 8887a23d5f..564be38692 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/message/WxCpTpMessageRouter.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/message/WxCpTpMessageRouter.java
@@ -213,12 +213,12 @@ public WxCpXmlOutMessage route(final String suiteId, final WxCpTpXmlMessage wxMe
       }
     }
 
-    if (matchRules.size() == 0) {
+    if (matchRules.isEmpty()) {
       return null;
     }
 
     WxCpXmlOutMessage res = null;
-    final List futures = new ArrayList<>();
+    final List> futures = new ArrayList<>();
     for (final WxCpTpMessageRouterRule rule : matchRules) {
       // 返回最后一个非异步的rule的执行结果
       if (rule.isAsync()) {
@@ -236,9 +236,9 @@ public WxCpXmlOutMessage route(final String suiteId, final WxCpTpXmlMessage wxMe
       }
     }
 
-    if (futures.size() > 0) {
+    if (!futures.isEmpty()) {
       this.executorService.submit(() -> {
-        for (Future future : futures) {
+        for (Future future : futures) {
           try {
             future.get();
             log.debug("End session access: async=true, sessionId={}", wxMessage.getSuiteId());
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 78c52d5c36..10268bcb31 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
@@ -6,8 +6,6 @@
 import me.chanjar.weixin.cp.bean.WxCpTpTagIdListConvertResult;
 import me.chanjar.weixin.cp.bean.WxCpTpUnionidToExternalUseridResult;
 
-import java.util.List;
-
 /**
  * 
  *  企业微信三方应用ID转换接口
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/WxCpTpService.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/WxCpTpService.java
index 5c433c0b49..b24be535da 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/WxCpTpService.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/WxCpTpService.java
@@ -8,6 +8,7 @@
 import me.chanjar.weixin.common.util.http.RequestExecutor;
 import me.chanjar.weixin.common.util.http.RequestHttp;
 import me.chanjar.weixin.cp.bean.*;
+import me.chanjar.weixin.cp.bean.message.WxCpTpXmlMessage;
 import me.chanjar.weixin.cp.config.WxCpTpConfigStorage;
 
 import java.util.List;
@@ -186,6 +187,8 @@ public interface WxCpTpService {
   @Deprecated
   WxCpTpCorp getPermanentCode(String authCode) throws WxErrorException;
 
+  WxCpTpCorp getV2PermanentCode(String authCode) throws WxErrorException;
+
   /**
    * 获取企业永久授权码信息
    * 
@@ -200,6 +203,8 @@ public interface WxCpTpService {
    */
   WxCpTpPermanentCodeInfo getPermanentCodeInfo(String authCode) throws WxErrorException;
 
+  WxCpTpPermanentCodeInfo getV2PermanentCodeInfo(String authCode) throws WxErrorException;
+
   /**
    * 
    *   获取预授权链接
@@ -343,9 +348,7 @@ public interface WxCpTpService {
    * 获取WxCpTpConfigStorage 对象.
    *
    * @return WxCpTpConfigStorage wx cp tp config storage
-   * @deprecated storage应该在service内部使用 ,提供这个接口,容易破坏这个封装
    */
-  @Deprecated
   WxCpTpConfigStorage getWxCpTpConfigStorage();
 
   /**
@@ -527,6 +530,11 @@ public interface WxCpTpService {
    */
   WxCpTpLicenseService getWxCpTpLicenseService();
 
+  WxCpTpXmlMessage fromEncryptedXml(String encryptedXml,
+                                    String timestamp, String nonce, String msgSignature);
+
+  String getVerifyDecrypt(String sVerifyEchoStr);
+
   /**
    * 获取应用的管理员列表
    *
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 aa874f8549..f8f554b81a 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/impl/BaseWxCpTpServiceImpl.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/impl/BaseWxCpTpServiceImpl.java
@@ -25,8 +25,10 @@
 import me.chanjar.weixin.common.util.json.GsonParser;
 import me.chanjar.weixin.common.util.json.WxGsonBuilder;
 import me.chanjar.weixin.cp.bean.*;
+import me.chanjar.weixin.cp.bean.message.WxCpTpXmlMessage;
 import me.chanjar.weixin.cp.config.WxCpTpConfigStorage;
 import me.chanjar.weixin.cp.tp.service.*;
+import me.chanjar.weixin.cp.util.crypto.WxCpTpCryptUtil;
 import org.apache.commons.lang3.StringUtils;
 
 import java.io.File;
@@ -91,6 +93,7 @@ public abstract class BaseWxCpTpServiceImpl implements WxCpTpService, Requ
 
   private final WxSessionManager sessionManager = new StandardSessionManager();
 
+
   /**
    * 临时文件目录.
    */
@@ -104,7 +107,7 @@ public boolean checkSignature(String msgSignature, String timestamp, String nonc
       return SHA1.gen(this.configStorage.getToken(), timestamp, nonce, data)
         .equals(msgSignature);
     } catch (Exception e) {
-      log.error("Checking signature failed, and the reason is :" + e.getMessage());
+      log.error("Checking signature failed, and the reason is :{}", e.getMessage());
       return false;
     }
   }
@@ -259,6 +262,18 @@ public WxCpTpCorp getPermanentCode(String authCode) throws WxErrorException {
     return wxCpTpCorp;
   }
 
+  @Override
+  public WxCpTpCorp getV2PermanentCode(String authCode) throws WxErrorException {
+    JsonObject jsonObject = new JsonObject();
+    jsonObject.addProperty("auth_code", authCode);
+
+    String result = post(configStorage.getApiUrl(GET_V2_PERMANENT_CODE), jsonObject.toString());
+    jsonObject = GsonParser.parse(result);
+    WxCpTpCorp wxCpTpCorp = WxCpTpCorp.fromJson(jsonObject.get("auth_corp_info").getAsJsonObject().toString());
+    wxCpTpCorp.setPermanentCode(jsonObject.get("permanent_code").getAsString());
+    return wxCpTpCorp;
+  }
+
   @Override
   public WxCpTpPermanentCodeInfo getPermanentCodeInfo(String authCode) throws WxErrorException {
     JsonObject jsonObject = new JsonObject();
@@ -267,6 +282,14 @@ public WxCpTpPermanentCodeInfo getPermanentCodeInfo(String authCode) throws WxEr
     return WxCpTpPermanentCodeInfo.fromJson(result);
   }
 
+  @Override
+  public WxCpTpPermanentCodeInfo getV2PermanentCodeInfo(String authCode) throws WxErrorException {
+    JsonObject jsonObject = new JsonObject();
+    jsonObject.addProperty("auth_code", authCode);
+    String result = post(configStorage.getApiUrl(GET_V2_PERMANENT_CODE), jsonObject.toString());
+    return WxCpTpPermanentCodeInfo.fromJson(result);
+  }
+
   @Override
   @SneakyThrows
   public String getPreAuthUrl(String redirectUri, String state) throws WxErrorException {
@@ -452,7 +475,7 @@ protected  T executeInternal(RequestExecutor executor, String uri, E
       if (error.getErrorCode() == WxCpErrorMsgEnum.CODE_42009.getCode()) {
         // 强制设置wxCpTpConfigStorage它的suite access token过期了,这样在下一次请求里就会刷新suite access token
         this.configStorage.expireSuiteAccessToken();
-        if (this.getWxCpTpConfigStorage().autoRefreshToken()) {
+        if (this.configStorage.autoRefreshToken()) {
           log.warn("即将重新获取新的access_token,错误代码:{},错误信息:{}", error.getErrorCode(), error.getErrorMsg());
           return this.execute(executor, uri, data);
         }
@@ -646,6 +669,27 @@ public void setWxCpTpUserService(WxCpTpUserService wxCpTpUserService) {
     this.wxCpTpUserService = wxCpTpUserService;
   }
 
+
+  /**
+   *
+   * @param encryptedXml         the encrypted xml
+   * @param timestamp            the timestamp
+   * @param nonce                the nonce
+   * @param msgSignature         the msg signature
+   * @return                     the wx cp tp xml message
+   */
+  @Override
+  public WxCpTpXmlMessage fromEncryptedXml(String encryptedXml,
+                                            String timestamp, String nonce, String msgSignature) {
+   return WxCpTpXmlMessage.fromEncryptedXml(encryptedXml,this.configStorage,timestamp,nonce,msgSignature);
+  }
+
+  @Override
+  public String getVerifyDecrypt(String sVerifyEchoStr) {
+    WxCpTpCryptUtil cryptUtil = new WxCpTpCryptUtil(this.configStorage);
+    return cryptUtil.decrypt(sVerifyEchoStr);
+  }
+
   @Override
   public WxCpTpAdmin getAdminList(String authCorpId, Integer agentId) throws WxErrorException {
     JsonObject jsonObject = new JsonObject();
@@ -764,4 +808,9 @@ public WxCpTpOAuth2Service getWxCpTpOAuth2Service() {
   public void setWxCpTpOAuth2Service(WxCpTpOAuth2Service wxCpTpOAuth2Service) {
     this.wxCpTpOAuth2Service = wxCpTpOAuth2Service;
   }
+
+  @Override
+  public WxCpTpConfigStorage getWxCpTpConfigStorage() {
+    return this.configStorage;
+  }
 }
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/impl/WxCpTpIdConvertServiceImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/impl/WxCpTpIdConvertServiceImpl.java
index 7d0d80b452..6e14e6bbb9 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/impl/WxCpTpIdConvertServiceImpl.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/impl/WxCpTpIdConvertServiceImpl.java
@@ -14,8 +14,6 @@
 import me.chanjar.weixin.cp.tp.service.WxCpTpIdConvertService;
 import me.chanjar.weixin.cp.tp.service.WxCpTpService;
 
-import java.util.List;
-
 
 /**
  * @author cocoa
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/impl/WxCpTpServiceApacheHttpClientImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/impl/WxCpTpServiceApacheHttpClientImpl.java
index a128afd7e6..a287513331 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/impl/WxCpTpServiceApacheHttpClientImpl.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/impl/WxCpTpServiceApacheHttpClientImpl.java
@@ -1,27 +1,25 @@
 package me.chanjar.weixin.cp.tp.service.impl;
 
-
 import com.google.gson.JsonObject;
 import me.chanjar.weixin.common.enums.WxType;
 import me.chanjar.weixin.common.error.WxError;
 import me.chanjar.weixin.common.error.WxErrorException;
 import me.chanjar.weixin.common.error.WxRuntimeException;
-import me.chanjar.weixin.common.util.http.HttpType;
+import me.chanjar.weixin.common.util.http.HttpClientType;
+import me.chanjar.weixin.common.util.http.apache.ApacheBasicResponseHandler;
 import me.chanjar.weixin.common.util.http.apache.ApacheHttpClientBuilder;
 import me.chanjar.weixin.common.util.http.apache.DefaultApacheHttpClientBuilder;
 import me.chanjar.weixin.common.util.json.GsonParser;
 import me.chanjar.weixin.cp.config.WxCpTpConfigStorage;
 import me.chanjar.weixin.cp.constant.WxCpApiPathConsts;
-import org.apache.http.Consts;
 import org.apache.http.HttpHost;
 import org.apache.http.client.config.RequestConfig;
-import org.apache.http.client.methods.CloseableHttpResponse;
 import org.apache.http.client.methods.HttpPost;
 import org.apache.http.entity.StringEntity;
-import org.apache.http.impl.client.BasicResponseHandler;
 import org.apache.http.impl.client.CloseableHttpClient;
 
 import java.io.IOException;
+import java.nio.charset.StandardCharsets;
 
 /**
  * The type Wx cp tp service apache http client.
@@ -43,8 +41,8 @@ public HttpHost getRequestHttpProxy() {
   }
 
   @Override
-  public HttpType getRequestType() {
-    return HttpType.APACHE_HTTP;
+  public HttpClientType getRequestType() {
+    return HttpClientType.APACHE_HTTP;
   }
 
   @Override
@@ -65,23 +63,17 @@ public String getSuiteAccessToken(boolean forceRefresh) throws WxErrorException
         jsonObject.addProperty("suite_id", this.configStorage.getSuiteId());
         jsonObject.addProperty("suite_secret", this.configStorage.getSuiteSecret());
         jsonObject.addProperty("suite_ticket", this.getSuiteTicket());
-        StringEntity entity = new StringEntity(jsonObject.toString(), Consts.UTF_8);
+        StringEntity entity = new StringEntity(jsonObject.toString(), StandardCharsets.UTF_8);
         httpPost.setEntity(entity);
 
-        String resultContent;
-        try (CloseableHttpClient httpclient = getRequestHttpClient();
-             CloseableHttpResponse response = httpclient.execute(httpPost)) {
-          resultContent = new BasicResponseHandler().handleResponse(response);
-        } finally {
-          httpPost.releaseConnection();
-        }
+        String resultContent = getRequestHttpClient().execute(httpPost, ApacheBasicResponseHandler.INSTANCE);
         WxError error = WxError.fromJson(resultContent, WxType.CP);
         if (error.getErrorCode() != 0) {
           throw new WxErrorException(error);
         }
         jsonObject = GsonParser.parse(resultContent);
         String suiteAccussToken = jsonObject.get("suite_access_token").getAsString();
-        Integer expiresIn = jsonObject.get("expires_in").getAsInt();
+        int expiresIn = jsonObject.get("expires_in").getAsInt();
         this.configStorage.updateSuiteAccessToken(suiteAccussToken, expiresIn);
       } catch (IOException e) {
         throw new WxRuntimeException(e);
@@ -109,9 +101,9 @@ public void initHttp() {
     this.httpClient = apacheHttpClientBuilder.build();
   }
 
-  @Override
-  public WxCpTpConfigStorage getWxCpTpConfigStorage() {
-    return this.configStorage;
-  }
+//  @Override
+//  public WxCpTpConfigStorage getWxCpTpConfigStorage() {
+//    return this.configStorage;
+//  }
 
 }
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/impl/WxCpTpServiceHttpComponentsImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/impl/WxCpTpServiceHttpComponentsImpl.java
new file mode 100644
index 0000000000..bba597a3ee
--- /dev/null
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/impl/WxCpTpServiceHttpComponentsImpl.java
@@ -0,0 +1,106 @@
+package me.chanjar.weixin.cp.tp.service.impl;
+
+import com.google.gson.JsonObject;
+import me.chanjar.weixin.common.enums.WxType;
+import me.chanjar.weixin.common.error.WxError;
+import me.chanjar.weixin.common.error.WxErrorException;
+import me.chanjar.weixin.common.error.WxRuntimeException;
+import me.chanjar.weixin.common.util.http.HttpClientType;
+import me.chanjar.weixin.common.util.http.hc.BasicResponseHandler;
+import me.chanjar.weixin.common.util.http.hc.DefaultHttpComponentsClientBuilder;
+import me.chanjar.weixin.common.util.http.hc.HttpComponentsClientBuilder;
+import me.chanjar.weixin.common.util.json.GsonParser;
+import me.chanjar.weixin.cp.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;
+import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
+import org.apache.hc.core5.http.HttpHost;
+import org.apache.hc.core5.http.io.entity.StringEntity;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+
+/**
+ * The type Wx cp tp service apache http client.
+ *
+ * @author altusea
+ */
+public class WxCpTpServiceHttpComponentsImpl extends BaseWxCpTpServiceImpl {
+  private CloseableHttpClient httpClient;
+  private HttpHost httpProxy;
+
+  @Override
+  public CloseableHttpClient getRequestHttpClient() {
+    return httpClient;
+  }
+
+  @Override
+  public HttpHost getRequestHttpProxy() {
+    return httpProxy;
+  }
+
+  @Override
+  public HttpClientType getRequestType() {
+    return HttpClientType.HTTP_COMPONENTS;
+  }
+
+  @Override
+  public String getSuiteAccessToken(boolean forceRefresh) throws WxErrorException {
+    if (!this.configStorage.isSuiteAccessTokenExpired() && !forceRefresh) {
+      return this.configStorage.getSuiteAccessToken();
+    }
+
+    synchronized (this.globalSuiteAccessTokenRefreshLock) {
+      try {
+        HttpPost httpPost = new HttpPost(configStorage.getApiUrl(WxCpApiPathConsts.Tp.GET_SUITE_TOKEN));
+        if (this.httpProxy != null) {
+          RequestConfig config = RequestConfig.custom()
+            .setProxy(this.httpProxy).build();
+          httpPost.setConfig(config);
+        }
+        JsonObject jsonObject = new JsonObject();
+        jsonObject.addProperty("suite_id", this.configStorage.getSuiteId());
+        jsonObject.addProperty("suite_secret", this.configStorage.getSuiteSecret());
+        jsonObject.addProperty("suite_ticket", this.getSuiteTicket());
+        StringEntity entity = new StringEntity(jsonObject.toString(), StandardCharsets.UTF_8);
+        httpPost.setEntity(entity);
+
+        String resultContent = getRequestHttpClient().execute(httpPost, BasicResponseHandler.INSTANCE);
+        WxError error = WxError.fromJson(resultContent, WxType.CP);
+        if (error.getErrorCode() != 0) {
+          throw new WxErrorException(error);
+        }
+        jsonObject = GsonParser.parse(resultContent);
+        String suiteAccussToken = jsonObject.get("suite_access_token").getAsString();
+        int expiresIn = jsonObject.get("expires_in").getAsInt();
+        this.configStorage.updateSuiteAccessToken(suiteAccussToken, expiresIn);
+      } catch (IOException e) {
+        throw new WxRuntimeException(e);
+      }
+    }
+    return this.configStorage.getSuiteAccessToken();
+  }
+
+  @Override
+  public void initHttp() {
+    HttpComponentsClientBuilder apacheHttpClientBuilder = DefaultHttpComponentsClientBuilder.get();
+
+    apacheHttpClientBuilder.httpProxyHost(this.configStorage.getHttpProxyHost())
+      .httpProxyPort(this.configStorage.getHttpProxyPort())
+      .httpProxyUsername(this.configStorage.getHttpProxyUsername())
+      .httpProxyPassword(this.configStorage.getHttpProxyPassword().toCharArray());
+
+    if (this.configStorage.getHttpProxyHost() != null && this.configStorage.getHttpProxyPort() > 0) {
+      this.httpProxy = new HttpHost(this.configStorage.getHttpProxyHost(), this.configStorage.getHttpProxyPort());
+    }
+
+    this.httpClient = apacheHttpClientBuilder.build();
+  }
+
+//  @Override
+//  public WxCpTpConfigStorage getWxCpTpConfigStorage() {
+//    return this.configStorage;
+//  }
+
+}
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/impl/WxCpTpServiceJoddHttpImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/impl/WxCpTpServiceJoddHttpImpl.java
new file mode 100644
index 0000000000..9379f62e81
--- /dev/null
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/impl/WxCpTpServiceJoddHttpImpl.java
@@ -0,0 +1,104 @@
+package me.chanjar.weixin.cp.tp.service.impl;
+
+import com.google.gson.JsonObject;
+import jodd.http.HttpConnectionProvider;
+import jodd.http.HttpRequest;
+import jodd.http.HttpResponse;
+import jodd.http.ProxyInfo;
+import jodd.http.net.SocketHttpConnectionProvider;
+import me.chanjar.weixin.common.bean.WxAccessToken;
+import me.chanjar.weixin.common.enums.WxType;
+import me.chanjar.weixin.common.error.WxError;
+import me.chanjar.weixin.common.error.WxErrorException;
+import me.chanjar.weixin.common.util.http.HttpClientType;
+import me.chanjar.weixin.common.util.json.GsonParser;
+import me.chanjar.weixin.cp.api.impl.BaseWxCpServiceImpl;
+import me.chanjar.weixin.cp.config.WxCpConfigStorage;
+import me.chanjar.weixin.cp.constant.WxCpApiPathConsts;
+
+/**
+ * The type Wx cp service jodd http.
+ *
+ * @author someone
+ */
+public class WxCpTpServiceJoddHttpImpl extends BaseWxCpTpServiceImpl {
+  private HttpConnectionProvider httpClient;
+  private ProxyInfo httpProxy;
+
+  @Override
+  public HttpConnectionProvider getRequestHttpClient() {
+    return httpClient;
+  }
+
+  @Override
+  public ProxyInfo getRequestHttpProxy() {
+    return httpProxy;
+  }
+
+  @Override
+  public HttpClientType getRequestType() {
+    return HttpClientType.JODD_HTTP;
+  }
+
+  @Override
+  public String getSuiteAccessToken(boolean forceRefresh) throws WxErrorException {
+    if (!this.configStorage.isSuiteAccessTokenExpired() && !forceRefresh) {
+      return this.configStorage.getSuiteAccessToken();
+    }
+
+    synchronized (this.globalSuiteAccessTokenRefreshLock) {
+      // 构建请求 URL
+      String url = this.configStorage.getApiUrl(WxCpApiPathConsts.Tp.GET_SUITE_TOKEN);
+
+      JsonObject jsonObject = new JsonObject();
+      jsonObject.addProperty("suite_id", this.configStorage.getSuiteId());
+      jsonObject.addProperty("suite_secret", this.configStorage.getSuiteSecret());
+      jsonObject.addProperty("suite_ticket", this.getSuiteTicket());
+      String jsonBody = jsonObject.toString();
+
+      if (this.httpProxy != null) {
+        httpClient.useProxy(this.httpProxy);
+      }
+      // 创建 POST 请求
+      HttpRequest request = HttpRequest
+        .post(url)
+        .contentType("application/json")
+        .body(jsonBody);  // 使用 .body() 设置请求体
+
+      request.withConnectionProvider(httpClient);
+
+      // 发送请求
+      HttpResponse response = request.send();
+
+      // 解析响应
+      String resultContent = response.bodyText();
+      WxError error = WxError.fromJson(resultContent, WxType.CP);
+      if (error.getErrorCode() != 0) {
+        throw new WxErrorException(error);
+      }
+
+      // 更新 access token
+      jsonObject = GsonParser.parse(resultContent);
+      String suiteAccussToken = jsonObject.get("suite_access_token").getAsString();
+      int expiresIn = jsonObject.get("expires_in").getAsInt();
+      this.configStorage.updateSuiteAccessToken(suiteAccussToken, expiresIn);
+    }
+
+    return this.configStorage.getSuiteAccessToken();
+  }
+
+  @Override
+  public void initHttp() {
+    if (this.configStorage.getHttpProxyHost() != null && this.configStorage.getHttpProxyPort() > 0) {
+      httpProxy = new ProxyInfo(ProxyInfo.ProxyType.HTTP, configStorage.getHttpProxyHost(),
+        configStorage.getHttpProxyPort(), configStorage.getHttpProxyUsername(), configStorage.getHttpProxyPassword());
+    }
+
+    httpClient = new SocketHttpConnectionProvider();
+  }
+//
+//  @Override
+//  public WxCpConfigStorage getWxCpConfigStorage() {
+//    return this.configStorage;
+//  }
+}
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/impl/WxCpTpServiceOkHttpImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/impl/WxCpTpServiceOkHttpImpl.java
new file mode 100644
index 0000000000..63d5e95bae
--- /dev/null
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/impl/WxCpTpServiceOkHttpImpl.java
@@ -0,0 +1,130 @@
+package me.chanjar.weixin.cp.tp.service.impl;
+
+import com.google.gson.JsonObject;
+import lombok.extern.slf4j.Slf4j;
+import me.chanjar.weixin.common.bean.WxAccessToken;
+import me.chanjar.weixin.common.enums.WxType;
+import me.chanjar.weixin.common.error.WxError;
+import me.chanjar.weixin.common.error.WxErrorException;
+import me.chanjar.weixin.common.error.WxRuntimeException;
+import me.chanjar.weixin.common.util.http.HttpClientType;
+import me.chanjar.weixin.common.util.http.okhttp.DefaultOkHttpClientBuilder;
+import me.chanjar.weixin.common.util.http.okhttp.OkHttpProxyInfo;
+import me.chanjar.weixin.common.util.json.GsonParser;
+import me.chanjar.weixin.cp.api.impl.BaseWxCpServiceImpl;
+import me.chanjar.weixin.cp.config.WxCpConfigStorage;
+import me.chanjar.weixin.cp.constant.WxCpApiPathConsts;
+import okhttp3.*;
+
+import java.io.IOException;
+
+import static me.chanjar.weixin.cp.constant.WxCpApiPathConsts.GET_TOKEN;
+
+/**
+ * The type Wx cp service ok http.
+ *
+ * @author someone
+ */
+@Slf4j
+public class WxCpTpServiceOkHttpImpl extends BaseWxCpTpServiceImpl {
+  private OkHttpClient httpClient;
+  private OkHttpProxyInfo httpProxy;
+
+  @Override
+  public OkHttpClient getRequestHttpClient() {
+    return httpClient;
+  }
+
+  @Override
+  public OkHttpProxyInfo getRequestHttpProxy() {
+    return httpProxy;
+  }
+
+  @Override
+  public HttpClientType getRequestType() {
+    return HttpClientType.OK_HTTP;
+  }
+
+  @Override
+  public String getSuiteAccessToken(boolean forceRefresh) throws WxErrorException {
+    if (!this.configStorage.isSuiteAccessTokenExpired() && !forceRefresh) {
+      return this.configStorage.getSuiteAccessToken();
+    }
+
+    synchronized (this.globalSuiteAccessTokenRefreshLock) {
+      // 得到 httpClient
+      OkHttpClient client = getRequestHttpClient();
+
+      JsonObject jsonObject = new JsonObject();
+      jsonObject.addProperty("suite_id", this.configStorage.getSuiteId());
+      jsonObject.addProperty("suite_secret", this.configStorage.getSuiteSecret());
+      jsonObject.addProperty("suite_ticket", this.getSuiteTicket());
+      String jsonBody = jsonObject.toString();
+
+      RequestBody requestBody = RequestBody.create(
+        MediaType.get("application/json; charset=utf-8"),
+        jsonBody
+      );
+
+      // 构建 POST 请求
+      Request request = new Request.Builder()
+        .url(this.configStorage.getApiUrl(WxCpApiPathConsts.Tp.GET_SUITE_TOKEN)) // URL 不包含查询参数
+        .post(requestBody) // 使用 POST 方法
+        .build();
+
+      String resultContent = null;
+      try (Response response = client.newCall(request).execute()) {
+        if (!response.isSuccessful()) {
+          throw new IOException("Unexpected response code: " + response);
+        }
+        resultContent = response.body().string();
+      } catch (IOException e) {
+        log.error("获取 suite token 失败: {}", e.getMessage(), e);
+        throw new WxRuntimeException("获取 suite token 失败", e);
+      }
+
+      WxError error = WxError.fromJson(resultContent, WxType.CP);
+      if (error.getErrorCode() != 0) {
+        throw new WxErrorException(error);
+      }
+
+      jsonObject = GsonParser.parse(resultContent);
+      String suiteAccussToken = jsonObject.get("suite_access_token").getAsString();
+      int expiresIn = jsonObject.get("expires_in").getAsInt();
+      this.configStorage.updateSuiteAccessToken(suiteAccussToken, expiresIn);
+    }
+    return this.configStorage.getSuiteAccessToken();
+  }
+
+  @Override
+  public void initHttp() {
+    log.debug("WxCpServiceOkHttpImpl initHttp");
+    //设置代理
+    if (configStorage.getHttpProxyHost() != null && configStorage.getHttpProxyPort() > 0) {
+      httpProxy = OkHttpProxyInfo.httpProxy(configStorage.getHttpProxyHost(),
+        configStorage.getHttpProxyPort(),
+        configStorage.getHttpProxyUsername(),
+        configStorage.getHttpProxyPassword());
+      OkHttpClient.Builder clientBuilder = new OkHttpClient.Builder();
+      clientBuilder.proxy(getRequestHttpProxy().getProxy());
+      //设置授权
+      clientBuilder.authenticator(new Authenticator() {
+        @Override
+        public Request authenticate(Route route, Response response) throws IOException {
+          String credential = Credentials.basic(httpProxy.getProxyUsername(), httpProxy.getProxyPassword());
+          return response.request().newBuilder()
+            .header("Authorization", credential)
+            .build();
+        }
+      });
+      httpClient = clientBuilder.build();
+    } else {
+      httpClient = DefaultOkHttpClientBuilder.get().build();
+    }
+  }
+
+//  @Override
+//  public WxCpConfigStorage getWxCpConfigStorage() {
+//    return this.configStorage;
+//  }
+}
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/util/crypto/WxCpTpCryptUtil.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/util/crypto/WxCpTpCryptUtil.java
index ac6097446a..4e9783e3e8 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/util/crypto/WxCpTpCryptUtil.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/util/crypto/WxCpTpCryptUtil.java
@@ -23,7 +23,7 @@ public WxCpTpCryptUtil(WxCpTpConfigStorage wxCpTpConfigStorage) {
      * @param encodingAesKey 公众平台上,开发者设置的EncodingAESKey
      * @param appidOrCorpid          公众平台corpId
      */
-    String encodingAesKey = wxCpTpConfigStorage.getAesKey();
+    String encodingAesKey = wxCpTpConfigStorage.getEncodingAESKey();
     String token = wxCpTpConfigStorage.getToken();
     String corpId = wxCpTpConfigStorage.getCorpId();
 
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/util/json/WxCpChatGsonAdapter.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/util/json/WxCpChatGsonAdapter.java
index e3a03ffab3..0e78465ead 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/util/json/WxCpChatGsonAdapter.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/util/json/WxCpChatGsonAdapter.java
@@ -1,11 +1,3 @@
-/*
- * KINGSTAR MEDIA SOLUTIONS Co.,LTD. Copyright c 2005-2013. All rights reserved.
- *
- * This source code is the property of KINGSTAR MEDIA SOLUTIONS LTD. It is intended
- * only for the use of KINGSTAR MEDIA application development. Reengineering, reproduction
- * arose from modification of the original source, or other redistribution of this source
- * is not permitted without written permission of the KINGSTAR MEDIA SOLUTIONS LTD.
- */
 package me.chanjar.weixin.cp.util.json;
 
 import com.google.gson.*;
@@ -23,46 +15,44 @@
  */
 public class WxCpChatGsonAdapter implements JsonSerializer, JsonDeserializer {
 
+  public static final String FIELD_CHAT_ID = "chatid";
+  public static final String FIELD_NAME = "name";
+  public static final String FIELD_OWNER = "owner";
+  public static final String FIELD_USER_LIST = "userlist";
+
   @Override
   public JsonElement serialize(WxCpChat chat, Type typeOfSrc, JsonSerializationContext context) {
     JsonObject json = new JsonObject();
-    if (chat.getId() != null) {
-      json.addProperty("chatid", chat.getId());
-    }
-    if (chat.getName() != null) {
-      json.addProperty("name", chat.getName());
-    }
-    if (chat.getOwner() != null) {
-      json.addProperty("owner", chat.getOwner());
-    }
-    if (chat.getUsers() != null) {
+    addPropertyIfNotNull(json, FIELD_CHAT_ID, chat.getId());
+    addPropertyIfNotNull(json, FIELD_NAME, chat.getName());
+    addPropertyIfNotNull(json, FIELD_OWNER, chat.getOwner());
+    if (chat.getUsers() != null && !chat.getUsers().isEmpty()) {
       JsonArray users = new JsonArray();
-      for (String user : chat.getUsers()) {
-        users.add(user);
-      }
-      json.add("userlist", users);
+      chat.getUsers().forEach(users::add);
+      json.add(FIELD_USER_LIST, users);
     }
     return json;
   }
 
+  private void addPropertyIfNotNull(JsonObject json, String key, String value) {
+    if (value != null) {
+      json.addProperty(key, value);
+    }
+  }
+
   @Override
   public WxCpChat deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
     JsonObject chatJson = json.getAsJsonObject();
-
     WxCpChat chat = new WxCpChat();
     chat.setId(GsonHelper.getAsString(chatJson.get("chatid")));
     chat.setName(GsonHelper.getAsString(chatJson.get("name")));
     chat.setOwner(GsonHelper.getAsString(chatJson.get("owner")));
-
     JsonArray usersJson = chatJson.getAsJsonArray("userlist");
-    if (usersJson != null) {
+    if (usersJson != null && !usersJson.isEmpty()) {
       List users = new ArrayList<>(usersJson.size());
+      usersJson.forEach(e -> users.add(e.getAsString()));
       chat.setUsers(users);
-      for (JsonElement userJson : usersJson) {
-        users.add(userJson.getAsString());
-      }
     }
-
     return chat;
   }
 
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/util/json/WxCpDepartGsonAdapter.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/util/json/WxCpDepartGsonAdapter.java
index af9344b701..72d367c431 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/util/json/WxCpDepartGsonAdapter.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/util/json/WxCpDepartGsonAdapter.java
@@ -1,11 +1,3 @@
-/*
- * KINGSTAR MEDIA SOLUTIONS Co.,LTD. Copyright c 2005-2013. All rights reserved.
- *
- * This source code is the property of KINGSTAR MEDIA SOLUTIONS LTD. It is intended
- * only for the use of KINGSTAR MEDIA application development. Reengineering, reproduction
- * arose from modification of the original source, or other redistribution of this source
- * is not permitted without written permission of the KINGSTAR MEDIA SOLUTIONS LTD.
- */
 package me.chanjar.weixin.cp.util.json;
 
 import com.google.gson.*;
@@ -13,6 +5,8 @@
 import me.chanjar.weixin.cp.bean.WxCpDepart;
 
 import java.lang.reflect.Type;
+import java.util.Arrays;
+import java.util.Objects;
 
 /**
  * WxCpDepart的gson适配器.
@@ -30,60 +24,47 @@ public class WxCpDepartGsonAdapter implements JsonSerializer, JsonDe
   @Override
   public JsonElement serialize(WxCpDepart group, Type typeOfSrc, JsonSerializationContext context) {
     JsonObject json = new JsonObject();
-    if (group.getId() != null) {
-      json.addProperty(ID, group.getId());
-    }
-    if (group.getName() != null) {
-      json.addProperty(NAME, group.getName());
-    }
-    if (group.getEnName() != null) {
-      json.addProperty(EN_NAME, group.getEnName());
-    }
-    if (group.getDepartmentLeader() != null) {
+    addPropertyIfNotNull(json, ID, group.getId());
+    addPropertyIfNotNull(json, NAME, group.getName());
+    addPropertyIfNotNull(json, EN_NAME, group.getEnName());
+    if (group.getDepartmentLeader() != null && group.getDepartmentLeader().length > 0) {
       JsonArray jsonArray = new JsonArray();
-      for (String department : group.getDepartmentLeader()) {
-        jsonArray.add(new JsonPrimitive(department));
-      }
+      Arrays.stream(group.getDepartmentLeader()).filter(Objects::nonNull).forEach(jsonArray::add);
       json.add(DEPARTMENT_LEADER, jsonArray);
     }
-    if (group.getParentId() != null) {
-      json.addProperty(PARENT_ID, group.getParentId());
-    }
-    if (group.getOrder() != null) {
-      json.addProperty(ORDER, String.valueOf(group.getOrder()));
-    }
+    addPropertyIfNotNull(json, PARENT_ID, group.getParentId());
+    addPropertyIfNotNull(json, ORDER, group.getOrder());
     return json;
   }
 
+  private void addPropertyIfNotNull(JsonObject json, String key, Object value) {
+    if (value != null) {
+      if (value instanceof Number) {
+        json.addProperty(key, (Number) value);
+      } else {
+        json.addProperty(key, value.toString());
+      }
+    }
+  }
+
   @Override
   public WxCpDepart deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
     throws JsonParseException {
     WxCpDepart depart = new WxCpDepart();
     JsonObject departJson = json.getAsJsonObject();
-    if (departJson.get(ID) != null && !departJson.get(ID).isJsonNull()) {
-      depart.setId(GsonHelper.getAsLong(departJson.get(ID)));
-    }
-    if (departJson.get(NAME) != null && !departJson.get(NAME).isJsonNull()) {
-      depart.setName(GsonHelper.getAsString(departJson.get(NAME)));
-    }
-    if (departJson.get(EN_NAME) != null && !departJson.get(EN_NAME).isJsonNull()) {
-      depart.setEnName(GsonHelper.getAsString(departJson.get(EN_NAME)));
-    }
-    if (departJson.getAsJsonArray(DEPARTMENT_LEADER) != null && !departJson.get(DEPARTMENT_LEADER).isJsonNull()) {
-      JsonArray jsonArray = departJson.getAsJsonArray(DEPARTMENT_LEADER);
+    depart.setId(GsonHelper.getAsLong(departJson.get(ID)));
+    depart.setName(GsonHelper.getAsString(departJson.get(NAME)));
+    depart.setEnName(GsonHelper.getAsString(departJson.get(EN_NAME)));
+    JsonArray jsonArray = departJson.getAsJsonArray(DEPARTMENT_LEADER);
+    if (jsonArray != null && !jsonArray.isJsonNull()) {
       String[] departments = new String[jsonArray.size()];
-      int i = 0;
-      for (JsonElement jsonElement : jsonArray) {
-        departments[i++] = jsonElement.getAsString();
+      for (int i = 0; i < jsonArray.size(); i++) {
+        departments[i] = jsonArray.get(i).getAsString();
       }
       depart.setDepartmentLeader(departments);
     }
-    if (departJson.get(ORDER) != null && !departJson.get(ORDER).isJsonNull()) {
-      depart.setOrder(GsonHelper.getAsLong(departJson.get(ORDER)));
-    }
-    if (departJson.get(PARENT_ID) != null && !departJson.get(PARENT_ID).isJsonNull()) {
-      depart.setParentId(GsonHelper.getAsLong(departJson.get(PARENT_ID)));
-    }
+    depart.setOrder(GsonHelper.getAsLong(departJson.get(ORDER)));
+    depart.setParentId(GsonHelper.getAsLong(departJson.get(PARENT_ID)));
     return depart;
   }
 
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/util/json/WxCpTagGsonAdapter.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/util/json/WxCpTagGsonAdapter.java
index 41a5b78400..2f830f7166 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/util/json/WxCpTagGsonAdapter.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/util/json/WxCpTagGsonAdapter.java
@@ -1,11 +1,3 @@
-/*
- * KINGSTAR MEDIA SOLUTIONS Co.,LTD. Copyright c 2005-2013. All rights reserved.
- *
- * This source code is the property of KINGSTAR MEDIA SOLUTIONS LTD. It is intended
- * only for the use of KINGSTAR MEDIA application development. Reengineering, reproduction
- * arose from modification of the original source, or other redistribution of this source
- * is not permitted without written permission of the KINGSTAR MEDIA SOLUTIONS LTD.
- */
 package me.chanjar.weixin.cp.util.json;
 
 import com.google.gson.*;
@@ -21,19 +13,28 @@
  */
 public class WxCpTagGsonAdapter implements JsonSerializer, JsonDeserializer {
 
+  private static final String TAG_ID = "tagid";
+  private static final String TAG_NAME = "tagname";
+
   @Override
   public JsonElement serialize(WxCpTag tag, Type typeOfSrc, JsonSerializationContext context) {
     JsonObject o = new JsonObject();
-    o.addProperty("tagid", tag.getId());
-    o.addProperty("tagname", tag.getName());
+    addPropertyIfNotNull(o, TAG_ID, tag.getId());
+    addPropertyIfNotNull(o, TAG_NAME, tag.getName());
     return o;
   }
 
+  private void addPropertyIfNotNull(JsonObject obj, String key, String value) {
+    if (value != null) {
+      obj.addProperty(key, value);
+    }
+  }
+
   @Override
   public WxCpTag deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
     throws JsonParseException {
     JsonObject jsonObject = json.getAsJsonObject();
-    return new WxCpTag(GsonHelper.getString(jsonObject, "tagid"), GsonHelper.getString(jsonObject, "tagname"));
+    return new WxCpTag(GsonHelper.getString(jsonObject, TAG_ID), GsonHelper.getString(jsonObject, TAG_NAME));
   }
 
 }
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/util/json/WxCpUserGsonAdapter.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/util/json/WxCpUserGsonAdapter.java
index 0c32ba0060..1df32b8601 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/util/json/WxCpUserGsonAdapter.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/util/json/WxCpUserGsonAdapter.java
@@ -1,12 +1,3 @@
-/*
- * KINGSTAR MEDIA SOLUTIONS Co.,LTD. Copyright c 2005-2013. All rights reserved.
- *
- * This source code is the property of KINGSTAR MEDIA SOLUTIONS LTD. It is intended
- * only for the use of KINGSTAR MEDIA application development. Reengineering, reproduction
- * arose from modification of the original source, or other redistribution of this source
- * is not permitted without written permission of the KINGSTAR MEDIA SOLUTIONS LTD.
- */
-
 package me.chanjar.weixin.cp.util.json;
 
 import com.google.gson.*;
@@ -15,6 +6,8 @@
 import me.chanjar.weixin.cp.bean.WxCpUser;
 
 import java.lang.reflect.Type;
+import java.util.Arrays;
+import java.util.stream.IntStream;
 
 import static me.chanjar.weixin.cp.bean.WxCpUser.*;
 
@@ -31,66 +24,78 @@ public class WxCpUserGsonAdapter implements JsonDeserializer, JsonSeri
   private static final String DEPARTMENT = "department";
   private static final String EXTERNAL_CORP_NAME = "external_corp_name";
   private static final String WECHAT_CHANNELS = "wechat_channels";
+  private static final String ORDER = "order";
+  private static final String POSITIONS = "positions";
+  private static final String USER_ID = "userid";
+  private static final String NEW_USER_ID = "new_userid";
+  private static final String NAME = "name";
+  private static final String POSITION = "position";
+  private static final String MOBILE = "mobile";
+  private static final String GENDER = "gender";
+  private static final String EMAIL = "email";
+  private static final String BIZ_MAIL = "biz_mail";
+  private static final String AVATAR = "avatar";
+  private static final String THUMB_AVATAR = "thumb_avatar";
+  private static final String ADDRESS = "address";
+  private static final String AVATAR_MEDIAID = "avatar_mediaid";
+  private static final String STATUS = "status";
+  private static final String ENABLE = "enable";
+  private static final String ALIAS = "alias";
+  private static final String IS_LEADER = "isleader";
+  private static final String IS_LEADER_IN_DEPT = "is_leader_in_dept";
+  private static final String HIDE_MOBILE = "hide_mobile";
+  private static final String ENGLISH_NAME = "english_name";
+  private static final String TELEPHONE = "telephone";
+  private static final String QR_CODE = "qr_code";
+  private static final String TO_INVITE = "to_invite";
+  private static final String OPEN_USER_ID = "open_userid";
+  private static final String MAIN_DEPARTMENT = "main_department";
+  private static final String DIRECT_LEADER = "direct_leader";
+  private static final String TYPE = "type";
+  private static final String VALUE = "value";
+  private static final String TEXT = "text";
+  private static final String WEB = "web";
+  private static final String MINIPROGRAM = "miniprogram";
+  private static final String URL = "url";
+  private static final String TITLE = "title";
+  private static final String APPID = "appid";
+  private static final String PAGE_PATH = "pagepath";
+  private static final String ATTRS = "attrs";
+  private static final String NICKNAME = "nickname";
 
   @Override
   public WxCpUser deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
     JsonObject o = json.getAsJsonObject();
     WxCpUser user = new WxCpUser();
 
-    if (o.get(DEPARTMENT) != null) {
-      JsonArray departJsonArray = o.get(DEPARTMENT).getAsJsonArray();
-      Long[] departIds = new Long[departJsonArray.size()];
-      int i = 0;
-      for (JsonElement jsonElement : departJsonArray) {
-        departIds[i++] = jsonElement.getAsLong();
-      }
-      user.setDepartIds(departIds);
-    }
-
-    if (o.get("order") != null) {
-      JsonArray departJsonArray = o.get("order").getAsJsonArray();
-      Integer[] orders = new Integer[departJsonArray.size()];
-      int i = 0;
-      for (JsonElement jsonElement : departJsonArray) {
-        orders[i++] = jsonElement.getAsInt();
-      }
-      user.setOrders(orders);
-    }
-
-    if (o.get("positions") != null) {
-      JsonArray positionJsonArray = o.get("positions").getAsJsonArray();
-      String[] positions = new String[positionJsonArray.size()];
-      int i = 0;
-      for (JsonElement jsonElement : positionJsonArray) {
-        positions[i++] = jsonElement.getAsString();
-      }
-      user.setPositions(positions);
-    }
-
-    user.setUserId(GsonHelper.getString(o, "userid"));
-    user.setName(GsonHelper.getString(o, "name"));
-    user.setPosition(GsonHelper.getString(o, "position"));
-    user.setMobile(GsonHelper.getString(o, "mobile"));
-    user.setGender(Gender.fromCode(GsonHelper.getString(o, "gender")));
-    user.setEmail(GsonHelper.getString(o, "email"));
-    user.setBizMail(GsonHelper.getString(o, "biz_mail"));
-    user.setAvatar(GsonHelper.getString(o, "avatar"));
-    user.setThumbAvatar(GsonHelper.getString(o, "thumb_avatar"));
-    user.setAddress(GsonHelper.getString(o, "address"));
-    user.setAvatarMediaId(GsonHelper.getString(o, "avatar_mediaid"));
-    user.setStatus(GsonHelper.getInteger(o, "status"));
-    user.setEnable(GsonHelper.getInteger(o, "enable"));
-    user.setAlias(GsonHelper.getString(o, "alias"));
-    user.setIsLeader(GsonHelper.getInteger(o, "isleader"));
-    user.setIsLeaderInDept(GsonHelper.getIntArray(o, "is_leader_in_dept"));
-    user.setHideMobile(GsonHelper.getInteger(o, "hide_mobile"));
-    user.setEnglishName(GsonHelper.getString(o, "english_name"));
-    user.setTelephone(GsonHelper.getString(o, "telephone"));
-    user.setQrCode(GsonHelper.getString(o, "qr_code"));
-    user.setToInvite(GsonHelper.getBoolean(o, "to_invite"));
-    user.setOpenUserId(GsonHelper.getString(o, "open_userid"));
-    user.setMainDepartment(GsonHelper.getString(o, "main_department"));
-    user.setDirectLeader(GsonHelper.getStringArray(o, "direct_leader"));
+    user.setDepartIds(parseJsonArrayToLongArray(o, DEPARTMENT));
+    user.setOrders(parseJsonArrayToIntegerArray(o, ORDER));
+    user.setPositions(parseJsonArrayToStringArray(o, POSITIONS));
+
+    user.setUserId(GsonHelper.getString(o, USER_ID));
+    user.setName(GsonHelper.getString(o, NAME));
+    user.setPosition(GsonHelper.getString(o, POSITION));
+    user.setMobile(GsonHelper.getString(o, MOBILE));
+    user.setGender(Gender.fromCode(GsonHelper.getString(o, GENDER)));
+    user.setEmail(GsonHelper.getString(o, EMAIL));
+    user.setBizMail(GsonHelper.getString(o, BIZ_MAIL));
+    user.setAvatar(GsonHelper.getString(o, AVATAR));
+    user.setThumbAvatar(GsonHelper.getString(o, THUMB_AVATAR));
+    user.setAddress(GsonHelper.getString(o, ADDRESS));
+    user.setAvatarMediaId(GsonHelper.getString(o, AVATAR_MEDIAID));
+    user.setStatus(GsonHelper.getInteger(o, STATUS));
+    user.setEnable(GsonHelper.getInteger(o, ENABLE));
+    user.setAlias(GsonHelper.getString(o, ALIAS));
+    user.setIsLeader(GsonHelper.getInteger(o, IS_LEADER));
+    user.setIsLeaderInDept(GsonHelper.getIntArray(o, IS_LEADER_IN_DEPT));
+    user.setHideMobile(GsonHelper.getInteger(o, HIDE_MOBILE));
+    user.setEnglishName(GsonHelper.getString(o, ENGLISH_NAME));
+    user.setTelephone(GsonHelper.getString(o, TELEPHONE));
+    user.setQrCode(GsonHelper.getString(o, QR_CODE));
+    user.setToInvite(GsonHelper.getBoolean(o, TO_INVITE));
+    user.setOpenUserId(GsonHelper.getString(o, OPEN_USER_ID));
+    user.setMainDepartment(GsonHelper.getString(o, MAIN_DEPARTMENT));
+    user.setDirectLeader(GsonHelper.getStringArray(o, DIRECT_LEADER));
 
     if (GsonHelper.isNotNull(o.get(EXTRA_ATTR))) {
       this.buildExtraAttrs(o, user);
@@ -102,8 +107,9 @@ public WxCpUser deserialize(JsonElement json, Type typeOfT, JsonDeserializationC
       JsonElement jsonElement = o.get(EXTERNAL_PROFILE).getAsJsonObject().get(WECHAT_CHANNELS);
       if (jsonElement != null) {
         JsonObject asJsonObject = jsonElement.getAsJsonObject();
-        user.setWechatChannels(WechatChannels.builder().nickname(GsonHelper.getString(asJsonObject, "nickname")).status(GsonHelper.getInteger(asJsonObject, "status")).build());
+        user.setWechatChannels(WechatChannels.builder().nickname(GsonHelper.getString(asJsonObject, NICKNAME)).status(GsonHelper.getInteger(asJsonObject, STATUS)).build());
       }
+
       this.buildExternalAttrs(o, user);
     }
 
@@ -112,29 +118,66 @@ public WxCpUser deserialize(JsonElement json, Type typeOfT, JsonDeserializationC
     return user;
   }
 
+  private Long[] parseJsonArrayToLongArray(JsonObject o, String key) {
+    JsonElement element = o.get(key);
+    if (element == null || !element.isJsonArray()) {
+      return null;
+    }
+    JsonArray jsonArray = element.getAsJsonArray();
+    return IntStream.range(0, jsonArray.size())
+        .mapToObj(i -> jsonArray.get(i).getAsLong())
+        .toArray(Long[]::new);
+  }
+
+  private Integer[] parseJsonArrayToIntegerArray(JsonObject o, String key) {
+    JsonElement element = o.get(key);
+    if (element == null || !element.isJsonArray()) {
+      return null;
+    }
+    JsonArray jsonArray = element.getAsJsonArray();
+    return IntStream.range(0, jsonArray.size())
+        .mapToObj(i -> jsonArray.get(i).getAsInt())
+        .toArray(Integer[]::new);
+  }
+
+  private String[] parseJsonArrayToStringArray(JsonObject o, String key) {
+    JsonElement element = o.get(key);
+    if (element == null || !element.isJsonArray()) {
+      return null;
+    }
+    JsonArray jsonArray = element.getAsJsonArray();
+    return IntStream.range(0, jsonArray.size())
+        .mapToObj(i -> jsonArray.get(i).getAsString())
+        .toArray(String[]::new);
+  }
+
   private void buildExtraAttrs(JsonObject o, WxCpUser user) {
-    JsonArray attrJsonElements = o.get(EXTRA_ATTR).getAsJsonObject().get("attrs").getAsJsonArray();
+    JsonArray attrJsonElements = o.get(EXTRA_ATTR).getAsJsonObject().get(ATTRS).getAsJsonArray();
     for (JsonElement attrJsonElement : attrJsonElements) {
-      final Integer type = GsonHelper.getInteger(attrJsonElement.getAsJsonObject(), "type");
+      final Integer type = GsonHelper.getInteger(attrJsonElement.getAsJsonObject(), TYPE);
       final Attr attr = new Attr().setType(type)
-        .setName(GsonHelper.getString(attrJsonElement.getAsJsonObject(), "name"));
+        .setName(GsonHelper.getString(attrJsonElement.getAsJsonObject(), NAME));
       user.getExtAttrs().add(attr);
 
       if (type == null) {
-        attr.setTextValue(GsonHelper.getString(attrJsonElement.getAsJsonObject(), "value"));
+        attr.setTextValue(GsonHelper.getString(attrJsonElement.getAsJsonObject(), VALUE));
         continue;
       }
 
       switch (type) {
         case 0: {
-          attr.setTextValue(GsonHelper.getString(attrJsonElement.getAsJsonObject().get("text").getAsJsonObject(),
-            "value"));
+          JsonElement textJsonElement = attrJsonElement.getAsJsonObject().get(TEXT);
+          if (textJsonElement != null && !textJsonElement.isJsonNull() && textJsonElement.isJsonObject()) {
+            attr.setTextValue(GsonHelper.getString(textJsonElement.getAsJsonObject(), VALUE));
+          } else {
+            attr.setTextValue(null); // Clear or set a default value to avoid stale data
+          }
           break;
         }
         case 1: {
-          final JsonObject web = attrJsonElement.getAsJsonObject().get("web").getAsJsonObject();
-          attr.setWebTitle(GsonHelper.getString(web, "title"))
-            .setWebUrl(GsonHelper.getString(web, "url"));
+          final JsonObject web = attrJsonElement.getAsJsonObject().get(WEB).getAsJsonObject();
+          attr.setWebTitle(GsonHelper.getString(web, TITLE))
+            .setWebUrl(GsonHelper.getString(web, URL));
           break;
         }
         default://ignored
@@ -150,8 +193,8 @@ private void buildExternalAttrs(JsonObject o, WxCpUser user) {
 
     JsonArray attrJsonElements = jsonElement.getAsJsonArray();
     for (JsonElement element : attrJsonElements) {
-      final Integer type = GsonHelper.getInteger(element.getAsJsonObject(), "type");
-      final String name = GsonHelper.getString(element.getAsJsonObject(), "name");
+      final Integer type = GsonHelper.getInteger(element.getAsJsonObject(), TYPE);
+      final String name = GsonHelper.getString(element.getAsJsonObject(), NAME);
 
       if (type == null) {
         continue;
@@ -163,32 +206,32 @@ private void buildExternalAttrs(JsonObject o, WxCpUser user) {
             .add(ExternalAttribute.builder()
               .type(type)
               .name(name)
-              .value(GsonHelper.getString(element.getAsJsonObject().get("text").getAsJsonObject(), "value"))
+              .value(GsonHelper.getString(element.getAsJsonObject().get(TEXT).getAsJsonObject(), VALUE))
               .build()
             );
           break;
         }
         case 1: {
-          final JsonObject web = element.getAsJsonObject().get("web").getAsJsonObject();
+          final JsonObject web = element.getAsJsonObject().get(WEB).getAsJsonObject();
           user.getExternalAttrs()
             .add(ExternalAttribute.builder()
               .type(type)
               .name(name)
-              .url(GsonHelper.getString(web, "url"))
-              .title(GsonHelper.getString(web, "title"))
+              .url(GsonHelper.getString(web, URL))
+              .title(GsonHelper.getString(web, TITLE))
               .build()
             );
           break;
         }
         case 2: {
-          final JsonObject miniprogram = element.getAsJsonObject().get("miniprogram").getAsJsonObject();
+          final JsonObject miniprogram = element.getAsJsonObject().get(MINIPROGRAM).getAsJsonObject();
           user.getExternalAttrs()
             .add(ExternalAttribute.builder()
               .type(type)
               .name(name)
-              .appid(GsonHelper.getString(miniprogram, "appid"))
-              .pagePath(GsonHelper.getString(miniprogram, "pagepath"))
-              .title(GsonHelper.getString(miniprogram, "title"))
+              .appid(GsonHelper.getString(miniprogram, APPID))
+              .pagePath(GsonHelper.getString(miniprogram, PAGE_PATH))
+              .title(GsonHelper.getString(miniprogram, TITLE))
               .build()
             );
           break;
@@ -201,97 +244,75 @@ private void buildExternalAttrs(JsonObject o, WxCpUser user) {
   @Override
   public JsonElement serialize(WxCpUser user, Type typeOfSrc, JsonSerializationContext context) {
     JsonObject o = new JsonObject();
-    this.addProperty(o, "userid", user.getUserId());
-    this.addProperty(o, "new_userid", user.getNewUserId());
-    this.addProperty(o, "name", user.getName());
-    if (user.getDepartIds() != null) {
-      JsonArray jsonArray = new JsonArray();
-      for (Long departId : user.getDepartIds()) {
-        jsonArray.add(new JsonPrimitive(departId));
-      }
-      o.add("department", jsonArray);
-    }
-
-    if (user.getOrders() != null) {
-      JsonArray jsonArray = new JsonArray();
-      for (Integer order : user.getOrders()) {
-        jsonArray.add(new JsonPrimitive(order));
-      }
-      o.add("order", jsonArray);
-    }
+    addProperty(o, USER_ID, user.getUserId());
+    addProperty(o, NEW_USER_ID, user.getNewUserId());
+    addProperty(o, NAME, user.getName());
 
-    this.addProperty(o, "position", user.getPosition());
-
-    if (user.getPositions() != null) {
-      JsonArray jsonArray = new JsonArray();
-      for (String position : user.getPositions()) {
-        jsonArray.add(new JsonPrimitive(position));
-      }
-      o.add("positions", jsonArray);
-    }
+    addArrayProperty(o, DEPARTMENT, user.getDepartIds());
+    addArrayProperty(o, ORDER, user.getOrders());
+    addProperty(o, POSITION, user.getPosition());
+    addArrayProperty(o, POSITIONS, user.getPositions());
 
-    this.addProperty(o, "mobile", user.getMobile());
+    addProperty(o, MOBILE, user.getMobile());
     if (user.getGender() != null) {
-      o.addProperty("gender", user.getGender().getCode());
+      o.addProperty(GENDER, user.getGender().getCode());
     }
-    this.addProperty(o, "email", user.getEmail());
-    this.addProperty(o, "biz_mail", user.getBizMail());
-    this.addProperty(o, "avatar", user.getAvatar());
-    this.addProperty(o, "thumb_avatar", user.getThumbAvatar());
-    this.addProperty(o, "address", user.getAddress());
-    this.addProperty(o, "avatar_mediaid", user.getAvatarMediaId());
-    this.addProperty(o, "status", user.getStatus());
-    this.addProperty(o, "enable", user.getEnable());
-    this.addProperty(o, "alias", user.getAlias());
-    this.addProperty(o, "isleader", user.getIsLeader());
+    addProperty(o, EMAIL, user.getEmail());
+    addProperty(o, BIZ_MAIL, user.getBizMail());
+    addProperty(o, AVATAR, user.getAvatar());
+    addProperty(o, THUMB_AVATAR, user.getThumbAvatar());
+    addProperty(o, ADDRESS, user.getAddress());
+    addProperty(o, AVATAR_MEDIAID, user.getAvatarMediaId());
+    addProperty(o, STATUS, user.getStatus());
+    addProperty(o, ENABLE, user.getEnable());
+    addProperty(o, ALIAS, user.getAlias());
+    addProperty(o, IS_LEADER, user.getIsLeader());
     if (user.getIsLeaderInDept() != null && user.getIsLeaderInDept().length > 0) {
       JsonArray ary = new JsonArray();
-      for (int item : user.getIsLeaderInDept()) {
-        ary.add(item);
-      }
-      o.add("is_leader_in_dept", ary);
+      Arrays.stream(user.getIsLeaderInDept()).forEach(ary::add);
+      o.add(IS_LEADER_IN_DEPT, ary);
     }
-    this.addProperty(o, "hide_mobile", user.getHideMobile());
-    this.addProperty(o, "english_name", user.getEnglishName());
-    this.addProperty(o, "telephone", user.getTelephone());
-    this.addProperty(o, "qr_code", user.getQrCode());
+    addProperty(o, HIDE_MOBILE, user.getHideMobile());
+    addProperty(o, ENGLISH_NAME, user.getEnglishName());
+    addProperty(o, TELEPHONE, user.getTelephone());
+    addProperty(o, QR_CODE, user.getQrCode());
     if (user.getToInvite() != null) {
-      o.addProperty("to_invite", user.getToInvite());
+      o.addProperty(TO_INVITE, user.getToInvite());
     }
-    this.addProperty(o, "main_department", user.getMainDepartment());
+    addProperty(o, MAIN_DEPARTMENT, user.getMainDepartment());
 
-    if (user.getDirectLeader() != null && user.getDirectLeader().length > 0) {
-      JsonArray ary = new JsonArray();
-      for (String item : user.getDirectLeader()) {
-        ary.add(item);
-      }
-      o.add("direct_leader", ary);
+    // Special handling for directLeader: include empty arrays to support WeChat Work API reset functionality
+    if (user.getDirectLeader() != null) {
+      JsonArray directLeaderArray = new JsonArray();
+      Arrays.stream(user.getDirectLeader()).forEach(directLeaderArray::add);
+      o.add(DIRECT_LEADER, directLeaderArray);
     }
+
     if (!user.getExtAttrs().isEmpty()) {
       JsonArray attrsJsonArray = new JsonArray();
       for (Attr attr : user.getExtAttrs()) {
-        JsonObject attrJson = GsonHelper.buildJsonObject("type", attr.getType(),
-          "name", attr.getName());
+        JsonObject attrJson = GsonHelper.buildJsonObject(TYPE, attr.getType(),
+          NAME, attr.getName());
         attrsJsonArray.add(attrJson);
 
         if (attr.getType() == null) {
-          attrJson.addProperty("name", attr.getName());
-          attrJson.addProperty("value", attr.getTextValue());
+          attrJson.addProperty(NAME, attr.getName());
+          attrJson.addProperty(VALUE, attr.getTextValue());
           continue;
         }
 
         switch (attr.getType()) {
           case 0:
-            attrJson.add("text", GsonHelper.buildJsonObject("value", attr.getTextValue()));
+            attrJson.add(TEXT, GsonHelper.buildJsonObject(VALUE, attr.getTextValue()));
             break;
           case 1:
-            attrJson.add("web", GsonHelper.buildJsonObject("url", attr.getWebUrl(), "title", attr.getWebTitle()));
+            attrJson.add(WEB, GsonHelper.buildJsonObject(URL, attr.getWebUrl(), TITLE, attr.getWebTitle()));
             break;
           default: //ignored
         }
       }
       JsonObject attrsJson = new JsonObject();
-      attrsJson.add("attrs", attrsJsonArray);
+      attrsJson.add(ATTRS, attrsJsonArray);
       o.add(EXTRA_ATTR, attrsJson);
     }
 
@@ -303,15 +324,15 @@ public JsonElement serialize(WxCpUser user, Type typeOfSrc, JsonSerializationCon
     this.addProperty(attrsJson, EXTERNAL_CORP_NAME, user.getExternalCorpName());
 
     if (user.getWechatChannels() != null) {
-      attrsJson.add(WECHAT_CHANNELS, GsonHelper.buildJsonObject("nickname", user.getWechatChannels().getNickname(),
-        "status", user.getWechatChannels().getStatus()));
+      attrsJson.add(WECHAT_CHANNELS, GsonHelper.buildJsonObject(NICKNAME, user.getWechatChannels().getNickname(),
+        STATUS, user.getWechatChannels().getStatus()));
     }
 
     if (!user.getExternalAttrs().isEmpty()) {
       JsonArray attrsJsonArray = new JsonArray();
       for (ExternalAttribute attr : user.getExternalAttrs()) {
-        JsonObject attrJson = GsonHelper.buildJsonObject("type", attr.getType(),
-          "name", attr.getName());
+        JsonObject attrJson = GsonHelper.buildJsonObject(TYPE, attr.getType(),
+          NAME, attr.getName());
 
         attrsJsonArray.add(attrJson);
 
@@ -321,14 +342,14 @@ public JsonElement serialize(WxCpUser user, Type typeOfSrc, JsonSerializationCon
 
         switch (attr.getType()) {
           case 0:
-            attrJson.add("text", GsonHelper.buildJsonObject("value", attr.getValue()));
+            attrJson.add(TEXT, GsonHelper.buildJsonObject(VALUE, attr.getValue()));
             break;
           case 1:
-            attrJson.add("web", GsonHelper.buildJsonObject("url", attr.getUrl(), "title", attr.getTitle()));
+            attrJson.add(WEB, GsonHelper.buildJsonObject(URL, attr.getUrl(), TITLE, attr.getTitle()));
             break;
           case 2:
-            attrJson.add("miniprogram", GsonHelper.buildJsonObject("appid", attr.getAppid(),
-              "pagepath", attr.getPagePath(), "title", attr.getTitle()));
+            attrJson.add(MINIPROGRAM, GsonHelper.buildJsonObject(APPID, attr.getAppid(),
+              PAGE_PATH, attr.getPagePath(), TITLE, attr.getTitle()));
             break;
           default://忽略
         }
@@ -340,15 +361,29 @@ public JsonElement serialize(WxCpUser user, Type typeOfSrc, JsonSerializationCon
     return o;
   }
 
-  private void addProperty(JsonObject object, String property, Integer value) {
-    if (value != null) {
-      object.addProperty(property, value);
+  private void addArrayProperty(JsonObject o, String key, Object[] array) {
+    if (array != null && array.length > 0) {
+      JsonArray jsonArray = new JsonArray();
+      Arrays.stream(array).forEach(item -> {
+        if (item instanceof Number) {
+          jsonArray.add((Number) item);
+        } else {
+          jsonArray.add(item.toString());
+        }
+      });
+      o.add(key, jsonArray);
     }
   }
 
-  private void addProperty(JsonObject object, String property, String value) {
+  private void addProperty(JsonObject object, String property, Object value) {
     if (value != null) {
-      object.addProperty(property, value);
+      if (value instanceof Number) {
+        object.addProperty(property, (Number) value);
+      } else if (value instanceof Boolean) {
+        object.addProperty(property, (Boolean) value);
+      } else {
+        object.addProperty(property, value.toString());
+      }
     }
   }
 
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 c4753befd2..098a781c64 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
@@ -17,7 +17,7 @@ public class XStreamTransformer {
   /**
    * The constant CLASS_2_XSTREAM_INSTANCE.
    */
-  protected static final Map CLASS_2_XSTREAM_INSTANCE = configXStreamInstance();
+  protected static final Map, XStream> CLASS_2_XSTREAM_INSTANCE = configXStreamInstance();
 
   /**
    * xml -> pojo
@@ -53,7 +53,7 @@ public static  T fromXml(Class clazz, InputStream is) {
    * @param clz     类型
    * @param xStream xml解析器
    */
-  public static void register(Class clz, XStream xStream) {
+  public static void register(Class clz, XStream xStream) {
     CLASS_2_XSTREAM_INSTANCE.put(clz, xStream);
   }
 
@@ -69,8 +69,8 @@ public static  String toXml(Class clazz, T object) {
     return CLASS_2_XSTREAM_INSTANCE.get(clazz).toXML(object);
   }
 
-  private static Map configXStreamInstance() {
-    Map map = new HashMap<>();
+  private static Map, XStream> configXStreamInstance() {
+    Map, XStream> map = new HashMap<>();
     map.put(WxCpXmlMessage.class, configWxCpXmlMessage());
     map.put(WxCpXmlOutNewsMessage.class, configWxCpXmlOutNewsMessage());
     map.put(WxCpXmlOutTextMessage.class, configWxCpXmlOutTextMessage());
diff --git a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/WxCpOaWeDriveServiceTest.java b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/WxCpOaWeDriveServiceTest.java
index bd7599061d..1364ab5a13 100644
--- a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/WxCpOaWeDriveServiceTest.java
+++ b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/WxCpOaWeDriveServiceTest.java
@@ -59,13 +59,13 @@ public void test() throws Exception {
     /*
      * 获取分享链接
      */
-    WxCpFileShare fileShare = cpService.getOaWeDriveService().fileShare(uId, fileId2);
+    WxCpFileShare fileShare = cpService.getOaWeDriveService().fileShare(fileId2);
     log.info("获取分享链接返回结果为:{}", fileShare.toJson());
 
     /*
      * 分享设置
      */
-    WxCpBaseResp fileSetting = cpService.getOaWeDriveService().fileSetting(uId, fileId2, 2, 1);
+    WxCpBaseResp fileSetting = cpService.getOaWeDriveService().fileSetting(fileId2, 2, 1);
     log.info("分享设置返回结果为:{}", fileSetting.toJson());
 
     /*
@@ -200,13 +200,13 @@ public void test() throws Exception {
     /*
      * 获取邀请链接
      */
-    WxCpSpaceShare spaceShare = cpService.getOaWeDriveService().spaceShare(uId, spId);
+    WxCpSpaceShare spaceShare = cpService.getOaWeDriveService().spaceShare(spId);
     log.info("获取邀请链接信息为:{}", spaceShare.toJson());
 
     /*
      * 获取空间信息
      */
-    WxCpSpaceInfo data = cpService.getOaWeDriveService().spaceInfo(uId, spId);
+    WxCpSpaceInfo data = cpService.getOaWeDriveService().spaceInfo(spId);
     log.info("获取空间信息为:{}", data.toJson());
 
     /*
@@ -252,7 +252,7 @@ public void test() throws Exception {
     /*
      * 获取空间信息
      */
-    WxCpSpaceInfo spaceInfo = cpService.getOaWeDriveService().spaceInfo("WangKai", "s.ww45d3e188865aca30.652091685u4h");
+    WxCpSpaceInfo spaceInfo = cpService.getOaWeDriveService().spaceInfo("s.ww45d3e188865aca30.652091685u4h");
     log.info("获取空间信息,spaceInfo信息为:{}", spaceInfo.toJson());
 
     /*
@@ -279,7 +279,7 @@ public void test() throws Exception {
     /*
      * 解散空间
      */
-    WxCpBaseResp thisResp = cpService.getOaWeDriveService().spaceDismiss("WangKai", spaceCreateData.getSpaceId());
+    WxCpBaseResp thisResp = cpService.getOaWeDriveService().spaceDismiss(spaceCreateData.getSpaceId());
     log.info("解散成功:{}", thisResp.toJson());
 
   }
diff --git a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/impl/BaseWxCpServiceImplTest.java b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/impl/BaseWxCpServiceImplTest.java
index c2b1dad933..6b861cedec 100644
--- a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/impl/BaseWxCpServiceImplTest.java
+++ b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/impl/BaseWxCpServiceImplTest.java
@@ -4,7 +4,7 @@
 import me.chanjar.weixin.common.error.WxError;
 import me.chanjar.weixin.common.error.WxErrorException;
 import me.chanjar.weixin.common.error.WxMpErrorMsgEnum;
-import me.chanjar.weixin.common.util.http.HttpType;
+import me.chanjar.weixin.common.util.http.HttpClientType;
 import me.chanjar.weixin.common.util.http.RequestExecutor;
 import me.chanjar.weixin.cp.api.ApiTestModule;
 import me.chanjar.weixin.cp.api.WxCpService;
@@ -92,7 +92,7 @@ public Object getRequestHttpProxy() {
       }
 
       @Override
-      public HttpType getRequestType() {
+      public HttpClientType getRequestType() {
         return null;
       }
 
diff --git a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/impl/WxCpGroupRobotServiceImplTest.java b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/impl/WxCpGroupRobotServiceImplTest.java
index 8e0d87d82c..f66580cc94 100644
--- a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/impl/WxCpGroupRobotServiceImplTest.java
+++ b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/impl/WxCpGroupRobotServiceImplTest.java
@@ -64,6 +64,51 @@ public void testSendMarkDown() throws WxErrorException {
     robotService.sendMarkdown(content);
   }
 
+  /**
+   * Test send mark down v2.
+   *
+   * @throws WxErrorException the wx error exception
+   */
+  @Test
+  public void testSendMarkDownV2() throws WxErrorException {
+    String content = "# 一、标题\n" +
+      "## 二级标题\n" +
+      "### 三级标题\n" +
+      "# 二、字体\n" +
+      "*斜体*\n" +
+      "\n" +
+      "**加粗**\n" +
+      "# 三、列表 \n" +
+      "- 无序列表 1 \n" +
+      "- 无序列表 2\n" +
+      "  - 无序列表 2.1\n" +
+      "  - 无序列表 2.2\n" +
+      "1. 有序列表 1\n" +
+      "2. 有序列表 2\n" +
+      "# 四、引用\n" +
+      "> 一级引用\n" +
+      ">>二级引用\n" +
+      ">>>三级引用\n" +
+      "# 五、链接\n" +
+      "[这是一个链接](https://work.weixin.qq.com/api/doc)\n" +
+      "![](https://res.mail.qq.com/node/ww/wwopenmng/images/independent/doc/test_pic_msg1.png)\n" +
+      "# 六、分割线\n" +
+      "\n" +
+      "---\n" +
+      "# 七、代码\n" +
+      "`这是行内代码`\n" +
+      "```\n" +
+      "这是独立代码块\n" +
+      "```\n" +
+      "\n" +
+      "# 八、表格\n" +
+      "| 姓名 | 文化衫尺寸 | 收货地址 |\n" +
+      "| :----- | :----: | -------: |\n" +
+      "| 张三 | S | 广州 |\n" +
+      "| 李四 | L | 深圳 |";
+    robotService.sendMarkdownV2(content);
+  }
+
   /**
    * Test send image.
    *
diff --git a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/impl/WxCpMediaServiceImplTest.java b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/impl/WxCpMediaServiceImplTest.java
index b964aad513..381a4c1454 100644
--- a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/impl/WxCpMediaServiceImplTest.java
+++ b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/impl/WxCpMediaServiceImplTest.java
@@ -7,6 +7,8 @@
 import me.chanjar.weixin.cp.api.ApiTestModule;
 import me.chanjar.weixin.cp.api.TestConstants;
 import me.chanjar.weixin.cp.api.WxCpService;
+import me.chanjar.weixin.cp.bean.media.MediaUploadByUrlReq;
+import me.chanjar.weixin.cp.bean.media.MediaUploadByUrlResult;
 import org.testng.annotations.DataProvider;
 import org.testng.annotations.Guice;
 import org.testng.annotations.Test;
@@ -127,4 +129,38 @@ public void testGetJssdkFile() throws WxErrorException {
     assertThat(file).isNotNull();
     System.out.println(file);
   }
+
+  /**
+   * Test upload media by url.
+   *
+   * @throws WxErrorException the wx error exception
+   */
+  @Test
+  public void testUploadMediaByUrl() throws WxErrorException {
+    MediaUploadByUrlReq req = new MediaUploadByUrlReq();
+    req.setScene(1);
+    req.setType("video");
+    req.setFilename("mov_bbb");
+    req.setUrl("https://www.w3school.com.cn/example/html5/mov_bbb.mp4");
+    req.setMd5("198918f40ecc7cab0fc4231adaf67c96");
+    String jobId = this.wxService.getMediaService().uploadByUrl(req);
+    System.out.println(jobId);
+  }
+
+  /**
+   * Test upload media by url.
+   *
+   * @throws WxErrorException the wx error exception
+   */
+  @Test
+  public void testUploadMediaByUrlResult() throws WxErrorException, InterruptedException {
+    String jobId = "job1745801375_5GIKWuFF3M7hcIkeSNMqs_W26xy5VeSWjLaLFTEdSfQ";
+    MediaUploadByUrlResult result = this.wxService.getMediaService().uploadByUrl(jobId);
+    System.out.println(result);
+  }
+
+  @Test
+  public void testUploadMediaJobFinishEvent() throws WxErrorException {
+    File file = this.wxService.getMediaService().getJssdkFile("....");
+  }
 }
diff --git a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/impl/WxCpMessageServiceImplTest.java b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/impl/WxCpMessageServiceImplTest.java
index 708542f41d..860526bc68 100644
--- a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/impl/WxCpMessageServiceImplTest.java
+++ b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/impl/WxCpMessageServiceImplTest.java
@@ -80,6 +80,7 @@ public void testSendMessage() throws WxErrorException {
     System.out.println(messageSendResult.getInvalidPartyList());
     System.out.println(messageSendResult.getInvalidUserList());
     System.out.println(messageSendResult.getInvalidTagList());
+    System.out.println(messageSendResult.getUnlicensedUserList());
   }
 
   /**
diff --git a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/impl/WxCpOaServiceImplTest.java b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/impl/WxCpOaServiceImplTest.java
index a37a42ee68..f722a248d3 100644
--- a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/impl/WxCpOaServiceImplTest.java
+++ b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/impl/WxCpOaServiceImplTest.java
@@ -8,6 +8,7 @@
 import me.chanjar.weixin.cp.api.WxCpService;
 import me.chanjar.weixin.cp.bean.WxCpBaseResp;
 import me.chanjar.weixin.cp.bean.oa.*;
+import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder;
 import org.apache.commons.lang3.time.DateFormatUtils;
 import org.testng.annotations.Guice;
 import org.testng.annotations.Test;
@@ -162,14 +163,252 @@ public void testGetCheckinOption() throws WxErrorException {
    */
   @Test
   public void testGetCropCheckinOption() throws WxErrorException {
-
-    Date now = new Date();
     List results = wxService.getOaService().getCropCheckinOption();
     assertThat(results).isNotNull();
     System.out.println("results ");
     System.out.println(gson.toJson(results));
   }
 
+  /**
+   * Test new ot_info_v2 structure deserialization.
+   */
+  @Test
+  public void testOtInfoV2Deserialization() {
+    // Test JSON with ot_info_v2 structure based on the new API response format
+    String jsonWithOtInfoV2 = "{\n" +
+      "  \"groupid\": 1,\n" +
+      "  \"groupname\": \"test group\",\n" +
+      "  \"grouptype\": 0,\n" +
+      "  \"ot_info_v2\": {\n" +
+      "    \"workdayconf\": {\n" +
+      "      \"allow_ot\": true,\n" +
+      "      \"type\": 1\n" +
+      "    },\n" +
+      "    \"restdayconf\": {\n" +
+      "      \"allow_ot\": false,\n" +
+      "      \"type\": 0\n" +
+      "    },\n" +
+      "    \"holidayconf\": {\n" +
+      "      \"allow_ot\": true,\n" +
+      "      \"type\": 2\n" +
+      "    }\n" +
+      "  }\n" +
+      "}";
+
+    WxCpCropCheckinOption option = WxCpGsonBuilder.create().fromJson(jsonWithOtInfoV2, WxCpCropCheckinOption.class);
+    assertThat(option).isNotNull();
+    assertThat(option.getOtInfoV2()).isNotNull();
+    assertThat(option.getOtInfoV2().getWorkdayConf()).isNotNull();
+    assertThat(option.getOtInfoV2().getWorkdayConf().getAllowOt()).isTrue();
+    assertThat(option.getOtInfoV2().getWorkdayConf().getType()).isEqualTo(1);
+    assertThat(option.getOtInfoV2().getRestdayConf()).isNotNull();
+    assertThat(option.getOtInfoV2().getRestdayConf().getAllowOt()).isFalse();
+    assertThat(option.getOtInfoV2().getHolidayConf().getAllowOt()).isTrue();
+    
+    System.out.println("Parsed ot_info_v2 structure:");
+    System.out.println(gson.toJson(option.getOtInfoV2()));
+  }
+
+  /**
+   * Test late_rule field deserialization in getCropCheckinOption response.
+   */
+  @Test
+  public void testLateRuleDeserialization() {
+    // Test JSON with late_rule structure based on the issue #3323
+    String jsonWithLateRule = "{\n" +
+      "  \"grouptype\": 1,\n" +
+      "  \"groupid\": 1,\n" +
+      "  \"checkindate\": [\n" +
+      "    {\n" +
+      "      \"workdays\": [1, 2, 3, 4, 5],\n" +
+      "      \"checkintime\": [\n" +
+      "        {\n" +
+      "          \"time_id\": 1,\n" +
+      "          \"work_sec\": 32400,\n" +
+      "          \"off_work_sec\": 64800,\n" +
+      "          \"remind_work_sec\": 31800,\n" +
+      "          \"remind_off_work_sec\": 64800,\n" +
+      "          \"rest_begin_time\": 43200,\n" +
+      "          \"rest_end_time\": 48600,\n" +
+      "          \"allow_rest\": true,\n" +
+      "          \"earliest_work_sec\": 21600,\n" +
+      "          \"latest_work_sec\": 64740,\n" +
+      "          \"earliest_off_work_sec\": 32460,\n" +
+      "          \"latest_off_work_sec\": 107940,\n" +
+      "          \"no_need_checkon\": false,\n" +
+      "          \"no_need_checkoff\": false\n" +
+      "        }\n" +
+      "      ],\n" +
+      "      \"noneed_offwork\": false,\n" +
+      "      \"limit_aheadtime\": 0,\n" +
+      "      \"flex_on_duty_time\": 0,\n" +
+      "      \"flex_off_duty_time\": 0,\n" +
+      "      \"allow_flex\": false,\n" +
+      "      \"late_rule\": {\n" +
+      "        \"offwork_after_time\": 3600,\n" +
+      "        \"onwork_flex_time\": 3600,\n" +
+      "        \"allow_offwork_after_time\": true,\n" +
+      "        \"timerules\": [\n" +
+      "          {\n" +
+      "            \"offwork_after_time\": 18000,\n" +
+      "            \"onwork_flex_time\": 3600\n" +
+      "          },\n" +
+      "          {\n" +
+      "            \"offwork_after_time\": 21600,\n" +
+      "            \"onwork_flex_time\": 7200\n" +
+      "          }\n" +
+      "        ]\n" +
+      "      },\n" +
+      "      \"max_allow_arrive_early\": 0,\n" +
+      "      \"max_allow_arrive_late\": 0\n" +
+      "    }\n" +
+      "  ],\n" +
+      "  \"groupname\": \"打卡\",\n" +
+      "  \"need_photo\": false\n" +
+      "}";
+
+    WxCpCropCheckinOption option = WxCpGsonBuilder.create().fromJson(jsonWithLateRule, WxCpCropCheckinOption.class);
+    assertThat(option).isNotNull();
+    assertThat(option.getCheckinDate()).isNotNull();
+    assertThat(option.getCheckinDate().size()).isEqualTo(1);
+    
+    WxCpCheckinGroupBase.CheckinDate checkinDate = option.getCheckinDate().get(0);
+    assertThat(checkinDate).isNotNull();
+    assertThat(checkinDate.getAllowFlex()).isFalse();
+    assertThat(checkinDate.getMaxAllowArriveEarly()).isEqualTo(0);
+    assertThat(checkinDate.getMaxAllowArriveLate()).isEqualTo(0);
+    
+    // Test late_rule field
+    assertThat(checkinDate.getLateRule()).isNotNull();
+    assertThat(checkinDate.getLateRule().getOffWorkAfterTime()).isEqualTo(3600);
+    assertThat(checkinDate.getLateRule().getOnWorkFlexTime()).isEqualTo(3600);
+    assertThat(checkinDate.getLateRule().getAllowOffWorkAfterTime()).isTrue();
+    assertThat(checkinDate.getLateRule().getTimerules()).isNotNull();
+    assertThat(checkinDate.getLateRule().getTimerules().size()).isEqualTo(2);
+    
+    // Test timerules
+    WxCpCheckinGroupBase.TimeRule firstRule = checkinDate.getLateRule().getTimerules().get(0);
+    assertThat(firstRule.getOffWorkAfterTime()).isEqualTo(18000);
+    assertThat(firstRule.getOnWorkFlexTime()).isEqualTo(3600);
+    
+    // Test CheckinTime fields
+    assertThat(checkinDate.getCheckinTime()).isNotNull();
+    assertThat(checkinDate.getCheckinTime().size()).isEqualTo(1);
+    
+    WxCpCheckinGroupBase.CheckinTime checkinTime = checkinDate.getCheckinTime().get(0);
+    assertThat(checkinTime.getTimeId()).isEqualTo(1);
+    assertThat(checkinTime.getRestBeginTime()).isEqualTo(43200);
+    assertThat(checkinTime.getRestEndTime()).isEqualTo(48600);
+    assertThat(checkinTime.getAllowRest()).isTrue();
+    assertThat(checkinTime.getEarliestWorkSec()).isEqualTo(21600);
+    assertThat(checkinTime.getLatestWorkSec()).isEqualTo(64740);
+    assertThat(checkinTime.getEarliestOffWorkSec()).isEqualTo(32460);
+    assertThat(checkinTime.getLatestOffWorkSec()).isEqualTo(107940);
+    assertThat(checkinTime.getNoNeedCheckon()).isFalse();
+    assertThat(checkinTime.getNoNeedCheckoff()).isFalse();
+    
+    System.out.println("Successfully parsed late_rule and new checkintime fields:");
+    System.out.println(gson.toJson(option));
+  }
+
+  /**
+   * Test issue #3323 - full JSON from the issue report.
+   */
+  @Test
+  public void testIssue3323FullJson() {
+    // Full JSON from issue #3323
+    String issueJson = "{\n" +
+      "      \"grouptype\": 1,\n" +
+      "      \"groupid\": 1,\n" +
+      "      \"checkindate\": [\n" +
+      "        {\n" +
+      "          \"workdays\": [\n" +
+      "            1,\n" +
+      "            2,\n" +
+      "            3,\n" +
+      "            4,\n" +
+      "            5\n" +
+      "          ],\n" +
+      "          \"checkintime\": [\n" +
+      "            {\n" +
+      "              \"time_id\": 1,\n" +
+      "              \"work_sec\": 32400,\n" +
+      "              \"off_work_sec\": 64800,\n" +
+      "              \"remind_work_sec\": 31800,\n" +
+      "              \"remind_off_work_sec\": 64800,\n" +
+      "              \"rest_begin_time\": 43200,\n" +
+      "              \"rest_end_time\": 48600,\n" +
+      "              \"allow_rest\": true,\n" +
+      "              \"earliest_work_sec\": 21600,\n" +
+      "              \"latest_work_sec\": 64740,\n" +
+      "              \"earliest_off_work_sec\": 32460,\n" +
+      "              \"latest_off_work_sec\": 107940,\n" +
+      "              \"no_need_checkon\": false,\n" +
+      "              \"no_need_checkoff\": false\n" +
+      "            }\n" +
+      "          ],\n" +
+      "          \"noneed_offwork\": false,\n" +
+      "          \"limit_aheadtime\": 0,\n" +
+      "          \"flex_on_duty_time\": 0,\n" +
+      "          \"flex_off_duty_time\": 0,\n" +
+      "          \"allow_flex\": false,\n" +
+      "          \"late_rule\": {\n" +
+      "            \"offwork_after_time\": 3600,\n" +
+      "            \"onwork_flex_time\": 3600,\n" +
+      "            \"allow_offwork_after_time\": true,\n" +
+      "            \"timerules\": [\n" +
+      "              {\n" +
+      "                \"offwork_after_time\": 18000,\n" +
+      "                \"onwork_flex_time\": 3600\n" +
+      "              },\n" +
+      "              {\n" +
+      "                \"offwork_after_time\": 21600,\n" +
+      "                \"onwork_flex_time\": 7200\n" +
+      "              },\n" +
+      "              {\n" +
+      "                \"offwork_after_time\": 28800,\n" +
+      "                \"onwork_flex_time\": 10800\n" +
+      "              }\n" +
+      "            ]\n" +
+      "          },\n" +
+      "          \"max_allow_arrive_early\": 0,\n" +
+      "          \"max_allow_arrive_late\": 0\n" +
+      "        }\n" +
+      "      ],\n" +
+      "      \"spe_workdays\": [],\n" +
+      "      \"spe_offdays\": [],\n" +
+      "      \"sync_holidays\": true,\n" +
+      "      \"groupname\": \"打卡\",\n" +
+      "      \"need_photo\": false,\n" +
+      "      \"wifimac_infos\": [],\n" +
+      "      \"note_can_use_local_pic\": true,\n" +
+      "      \"allow_checkin_offworkday\": false,\n" +
+      "      \"allow_apply_offworkday\": false,\n" +
+      "      \"loc_infos\": []\n" +
+      "    }";
+
+    WxCpCropCheckinOption option = WxCpGsonBuilder.create().fromJson(issueJson, WxCpCropCheckinOption.class);
+    assertThat(option).isNotNull();
+    assertThat(option.getGroupId()).isEqualTo(1);
+    assertThat(option.getGroupName()).isEqualTo("打卡");
+    assertThat(option.getCheckinDate()).isNotNull();
+    assertThat(option.getCheckinDate().size()).isEqualTo(1);
+    
+    WxCpCheckinGroupBase.CheckinDate checkinDate = option.getCheckinDate().get(0);
+    assertThat(checkinDate.getLateRule()).isNotNull();
+    assertThat(checkinDate.getLateRule().getOffWorkAfterTime()).isEqualTo(3600);
+    assertThat(checkinDate.getLateRule().getOnWorkFlexTime()).isEqualTo(3600);
+    assertThat(checkinDate.getLateRule().getAllowOffWorkAfterTime()).isTrue();
+    assertThat(checkinDate.getLateRule().getTimerules()).isNotNull();
+    assertThat(checkinDate.getLateRule().getTimerules().size()).isEqualTo(3);
+    
+    System.out.println("✓ Successfully parsed full JSON from issue #3323");
+    System.out.println("✓ Late Rule offwork_after_time: " + checkinDate.getLateRule().getOffWorkAfterTime());
+    System.out.println("✓ Late Rule onwork_flex_time: " + checkinDate.getLateRule().getOnWorkFlexTime());
+    System.out.println("✓ Late Rule allow_offwork_after_time: " + checkinDate.getLateRule().getAllowOffWorkAfterTime());
+    System.out.println("✓ Late Rule timerules count: " + checkinDate.getLateRule().getTimerules().size());
+  }
+
   /**
    * Test get approval info.
    *
diff --git a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/bean/message/WxCpTpXmlMessageTest.java b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/bean/message/WxCpTpXmlMessageTest.java
index d6cd827630..28246cf00b 100644
--- a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/bean/message/WxCpTpXmlMessageTest.java
+++ b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/bean/message/WxCpTpXmlMessageTest.java
@@ -152,7 +152,7 @@ public void enterAppTest() {
     assertEquals(wxXmlMessage.getCreateTime(), Long.valueOf(1408091189));
     assertEquals(wxXmlMessage.getEvent(), "enter_agent");
     assertEquals(wxXmlMessage.getEventKey(), "");
-    assertEquals(wxXmlMessage.getAgentID(), Integer.valueOf(1));
+    assertEquals(wxXmlMessage.getAgentID(), 1);
   }
 
   /**
diff --git a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/bean/message/WxCpXmlMessageTest.java b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/bean/message/WxCpXmlMessageTest.java
index a760a17ff6..ae4fbba8f6 100644
--- a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/bean/message/WxCpXmlMessageTest.java
+++ b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/bean/message/WxCpXmlMessageTest.java
@@ -6,6 +6,7 @@
 import org.testng.annotations.Test;
 
 import static me.chanjar.weixin.cp.constant.WxCpConsts.EventType.TASKCARD_CLICK;
+import static me.chanjar.weixin.cp.constant.WxCpConsts.EventType.UPLOAD_MEDIA_JOB_FINISH;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.testng.Assert.assertEquals;
 import static org.testng.Assert.assertNotNull;
@@ -71,7 +72,7 @@ public void testFromXml() {
     assertEquals(wxMessage.getCreateTime(), Long.valueOf(1348831860));
     assertEquals(wxMessage.getMsgType(), WxConsts.XmlMsgType.TEXT);
     assertEquals(wxMessage.getContent(), "this is a test");
-    assertEquals(wxMessage.getMsgId(), Long.valueOf(1234567890123456L));
+    assertEquals(wxMessage.getMsgId(), "1234567890123456");
     assertEquals(wxMessage.getPicUrl(), "this is a url");
     assertEquals(wxMessage.getMediaId(), "media_id");
     assertEquals(wxMessage.getFormat(), "Format");
@@ -421,4 +422,53 @@ public void testOpenApprovalChange() {
     assertThat(wxCpXmlMessage.getApprovalInfo().getApprovalNodes().get(0).getItems().get(0).getItemName()).isNotEmpty();
     assertThat(wxCpXmlMessage.getApprovalInfo().getNotifyNodes().get(0).getItemName()).isNotEmpty();
   }
+
+  /**
+   * Test open approval change.
+   */
+  public void testUploadMediaJobFinishEvent() {
+    String xml = "\n" +
+      "\t\n" +
+      "\t\n" +
+      "\t1425284517\n" +
+      "\t\n" +
+      "\t\n" +
+      "\t\n" +
+      "";
+
+    WxCpXmlMessage wxCpXmlMessage = WxCpXmlMessage.fromXml(xml);
+    assertThat(wxCpXmlMessage).isNotNull();
+    assertThat(wxCpXmlMessage.getJobId()).isNotEmpty();
+    assertThat(wxCpXmlMessage.getJobId()).isEqualTo("jobid_S0MrnndvRG5fadSlLwiBqiDDbM143UqTmKP3152FZk4");
+    assertThat(wxCpXmlMessage.getEvent()).isEqualTo(UPLOAD_MEDIA_JOB_FINISH);
+  }
+
+  /**
+   * Test both numeric and string msgId formats to ensure backward compatibility
+   */
+  public void testMsgIdStringAndNumericFormats() {
+    // Test with numeric msgId (old format)
+    String xmlWithNumeric = ""
+      + ""
+      + ""
+      + "1348831860"
+      + ""
+      + ""
+      + "1234567890123456"
+      + "";
+    WxCpXmlMessage wxMessageNumeric = WxCpXmlMessage.fromXml(xmlWithNumeric);
+    assertEquals(wxMessageNumeric.getMsgId(), "1234567890123456");
+
+    // Test with string msgId (new format - the actual issue case)
+    String xmlWithString = ""
+      + ""
+      + ""
+      + "1348831860"
+      + ""
+      + ""
+      + "CAIQg/PKxgYY2sC9tpuAgAMg9/zKaw=="
+      + "";
+    WxCpXmlMessage wxMessageString = WxCpXmlMessage.fromXml(xmlWithString);
+    assertEquals(wxMessageString.getMsgId(), "CAIQg/PKxgYY2sC9tpuAgAMg9/zKaw==");
+  }
 }
diff --git a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/bean/oa/WxCpOaApprovalTemplateResultTest.java b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/bean/oa/WxCpOaApprovalTemplateResultTest.java
new file mode 100644
index 0000000000..db79a06b32
--- /dev/null
+++ b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/bean/oa/WxCpOaApprovalTemplateResultTest.java
@@ -0,0 +1,390 @@
+package me.chanjar.weixin.cp.bean.oa;
+
+import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder;
+import org.testng.annotations.Test;
+
+import static org.testng.Assert.*;
+
+public class WxCpOaApprovalTemplateResultTest {
+
+  @Test
+  public void testFromJson() {
+    String json = "{\n"
+      + "  \"errcode\": 0,\n"
+      + "  \"errmsg\": \"ok\",\n"
+      + "  \"template_names\": [\n"
+      + "    {\n"
+      + "      \"text\": \"智能印章\",\n"
+      + "      \"lang\": \"zh_CN\"\n"
+      + "    },\n"
+      + "    {\n"
+      + "      \"text\": \"Company Seal\",\n"
+      + "      \"lang\": \"en\"\n"
+      + "    }\n"
+      + "  ],\n"
+      + "  \"template_content\": {\n"
+      + "    \"controls\": [\n"
+      + "      {\n"
+      + "        \"property\": {\n"
+      + "          \"control\": \"Text\",\n"
+      + "          \"id\": \"Text-1747127819114\",\n"
+      + "          \"title\": [\n"
+      + "            {\n"
+      + "              \"text\": \"用印事由\",\n"
+      + "              \"lang\": \"zh_CN\"\n"
+      + "            }\n"
+      + "          ],\n"
+      + "          \"placeholder\": [\n"
+      + "            {\n"
+      + "              \"text\": \"\",\n"
+      + "              \"lang\": \"zh_CN\"\n"
+      + "            }\n"
+      + "          ],\n"
+      + "          \"require\": 1,\n"
+      + "          \"un_print\": 0,\n"
+      + "          \"inner_id\": \"\",\n"
+      + "          \"un_replace\": 0,\n"
+      + "          \"display\": 1\n"
+      + "        }\n"
+      + "      },\n"
+      + "      {\n"
+      + "        \"property\": {\n"
+      + "          \"control\": \"Selector\",\n"
+      + "          \"id\": \"Selector-1747123508806\",\n"
+      + "          \"title\": [\n"
+      + "            {\n"
+      + "              \"text\": \"用印类型\",\n"
+      + "              \"lang\": \"zh_CN\"\n"
+      + "            }\n"
+      + "          ],\n"
+      + "          \"placeholder\": [\n"
+      + "            {\n"
+      + "              \"text\": \"\",\n"
+      + "              \"lang\": \"zh_CN\"\n"
+      + "            }\n"
+      + "          ],\n"
+      + "          \"require\": 1,\n"
+      + "          \"un_print\": 0,\n"
+      + "          \"inner_id\": \"\",\n"
+      + "          \"un_replace\": 0,\n"
+      + "          \"display\": 1\n"
+      + "        },\n"
+      + "        \"config\": {\n"
+      + "          \"selector\": {\n"
+      + "            \"type\": \"single\",\n"
+      + "            \"options\": [\n"
+      + "              {\n"
+      + "                \"key\": \"option-1747123508806\",\n"
+      + "                \"value\": [\n"
+      + "                  {\n"
+      + "                    \"text\": \"一般事务性用印\",\n"
+      + "                    \"lang\": \"zh_CN\"\n"
+      + "                  }\n"
+      + "                ]\n"
+      + "              },\n"
+      + "              {\n"
+      + "                \"key\": \"option-1747123508807\",\n"
+      + "                \"value\": [\n"
+      + "                  {\n"
+      + "                    \"text\": \"对外事务性用印\",\n"
+      + "                    \"lang\": \"zh_CN\"\n"
+      + "                  }\n"
+      + "                ]\n"
+      + "              },\n"
+      + "              {\n"
+      + "                \"key\": \"option-1747123530814\",\n"
+      + "                \"value\": [\n"
+      + "                  {\n"
+      + "                    \"text\": \"重大事务性用印\",\n"
+      + "                    \"lang\": \"zh_CN\"\n"
+      + "                  }\n"
+      + "                ]\n"
+      + "              }\n"
+      + "            ],\n"
+      + "            \"op_relations\": [],\n"
+      + "            \"external_option\": {\n"
+      + "              \"use_external_option\": false,\n"
+      + "              \"external_url\": \"\"\n"
+      + "            }\n"
+      + "          }\n"
+      + "        }\n"
+      + "      },\n"
+      + "      {\n"
+      + "        \"property\": {\n"
+      + "          \"control\": \"Tips\",\n"
+      + "          \"id\": \"Tips-1747123397470\",\n"
+      + "          \"title\": [\n"
+      + "            {\n"
+      + "              \"text\": \"说明\",\n"
+      + "              \"lang\": \"zh_CN\"\n"
+      + "            }\n"
+      + "          ],\n"
+      + "          \"placeholder\": [],\n"
+      + "          \"require\": 0,\n"
+      + "          \"un_print\": 0,\n"
+      + "          \"inner_id\": \"\",\n"
+      + "          \"un_replace\": 0,\n"
+      + "          \"display\": 1\n"
+      + "        },\n"
+      + "        \"config\": {\n"
+      + "          \"tips\": {\n"
+      + "            \"tips_content\": [\n"
+      + "              {\n"
+      + "                \"text\": {\n"
+      + "                  \"sub_text\": [\n"
+      + "                    {\n"
+      + "                      \"type\": 1,\n"
+      + "                      \"content\": {\n"
+      + "                        \"plain_text\": {\n"
+      + "                          \"content\": \"用印类型说明:1. 一般事务性用印:内部日常材料流转、常规业务报表报送、非对外承诺性质的证明文件,用印文件内容不得涉及经济、法律责任条款 \"\n"
+      + "                        }\n"
+      + "                      }\n"
+      + "                    }\n"
+      + "                  ]\n"
+      + "                },\n"
+      + "                \"lang\": \"zh_CN\"\n"
+      + "              }\n"
+      + "            ]\n"
+      + "          }\n"
+      + "        }\n"
+      + "      },\n"
+      + "      {\n"
+      + "        \"property\": {\n"
+      + "          \"control\": \"Table\",\n"
+      + "          \"id\": \"Table-1746005041962\",\n"
+      + "          \"title\": [\n"
+      + "            {\n"
+      + "              \"text\": \"印章明细\",\n"
+      + "              \"lang\": \"zh_CN\"\n"
+      + "            }\n"
+      + "          ],\n"
+      + "          \"placeholder\": [\n"
+      + "            {\n"
+      + "              \"text\": \"\",\n"
+      + "              \"lang\": \"zh_CN\"\n"
+      + "            }\n"
+      + "          ],\n"
+      + "          \"require\": 0,\n"
+      + "          \"un_print\": 0,\n"
+      + "          \"inner_id\": \"\",\n"
+      + "          \"un_replace\": 0,\n"
+      + "          \"display\": 1\n"
+      + "        },\n"
+      + "        \"config\": {\n"
+      + "          \"table\": {\n"
+      + "            \"children\": [\n"
+      + "              {\n"
+      + "                \"property\": {\n"
+      + "                  \"control\": \"Text\",\n"
+      + "                  \"id\": \"Text-1747127691499\",\n"
+      + "                  \"title\": [\n"
+      + "                    {\n"
+      + "                      \"text\": \"印章名称\",\n"
+      + "                      \"lang\": \"zh_CN\"\n"
+      + "                    }\n"
+      + "                  ],\n"
+      + "                  \"placeholder\": [\n"
+      + "                    {\n"
+      + "                      \"text\": \"请输入“公章”\",\n"
+      + "                      \"lang\": \"zh_CN\"\n"
+      + "                    }\n"
+      + "                  ],\n"
+      + "                  \"require\": 1,\n"
+      + "                  \"un_print\": 0,\n"
+      + "                  \"un_replace\": 0,\n"
+      + "                  \"display\": 1\n"
+      + "                }\n"
+      + "              },\n"
+      + "              {\n"
+      + "                \"property\": {\n"
+      + "                  \"control\": \"Number\",\n"
+      + "                  \"id\": \"Number-1746006598992\",\n"
+      + "                  \"title\": [\n"
+      + "                    {\n"
+      + "                      \"text\": \"普通用印\",\n"
+      + "                      \"lang\": \"zh_CN\"\n"
+      + "                    }\n"
+      + "                  ],\n"
+      + "                  \"placeholder\": [\n"
+      + "                    {\n"
+      + "                      \"text\": \"请填写正文用印次数\",\n"
+      + "                      \"lang\": \"zh_CN\"\n"
+      + "                    }\n"
+      + "                  ],\n"
+      + "                  \"require\": 1,\n"
+      + "                  \"un_print\": 0,\n"
+      + "                  \"un_replace\": 0,\n"
+      + "                  \"display\": 1\n"
+      + "                }\n"
+      + "              },\n"
+      + "              {\n"
+      + "                \"property\": {\n"
+      + "                  \"control\": \"Number\",\n"
+      + "                  \"id\": \"Number-1746006601002\",\n"
+      + "                  \"title\": [\n"
+      + "                    {\n"
+      + "                      \"text\": \"骑缝用印\",\n"
+      + "                      \"lang\": \"zh_CN\"\n"
+      + "                    }\n"
+      + "                  ],\n"
+      + "                  \"placeholder\": [\n"
+      + "                    {\n"
+      + "                      \"text\": \"请填写骑缝用印次数\",\n"
+      + "                      \"lang\": \"zh_CN\"\n"
+      + "                    }\n"
+      + "                  ],\n"
+      + "                  \"require\": 1,\n"
+      + "                  \"un_print\": 0,\n"
+      + "                  \"un_replace\": 0,\n"
+      + "                  \"display\": 1\n"
+      + "                }\n"
+      + "              },\n"
+      + "              {\n"
+      + "                \"property\": {\n"
+      + "                  \"control\": \"Selector\",\n"
+      + "                  \"id\": \"Selector-1746005136537\",\n"
+      + "                  \"title\": [\n"
+      + "                    {\n"
+      + "                      \"text\": \"是否外借\",\n"
+      + "                      \"lang\": \"zh_CN\"\n"
+      + "                    }\n"
+      + "                  ],\n"
+      + "                  \"placeholder\": [\n"
+      + "                    {\n"
+      + "                      \"text\": \"\",\n"
+      + "                      \"lang\": \"zh_CN\"\n"
+      + "                    }\n"
+      + "                  ],\n"
+      + "                  \"require\": 0,\n"
+      + "                  \"un_print\": 0,\n"
+      + "                  \"un_replace\": 0,\n"
+      + "                  \"display\": 1\n"
+      + "                },\n"
+      + "                \"config\": {\n"
+      + "                  \"selector\": {\n"
+      + "                    \"type\": \"single\",\n"
+      + "                    \"exp_type\": 0,\n"
+      + "                    \"options\": [\n"
+      + "                      {\n"
+      + "                        \"key\": \"option-1746005136537\",\n"
+      + "                        \"value\": [\n"
+      + "                          {\n"
+      + "                            \"text\": \"是\",\n"
+      + "                            \"lang\": \"zh_CN\"\n"
+      + "                          }\n"
+      + "                        ]\n"
+      + "                      },\n"
+      + "                      {\n"
+      + "                        \"key\": \"option-1746005136538\",\n"
+      + "                        \"value\": [\n"
+      + "                          {\n"
+      + "                            \"text\": \"否\",\n"
+      + "                            \"lang\": \"zh_CN\"\n"
+      + "                          }\n"
+      + "                        ]\n"
+      + "                      }\n"
+      + "                    ],\n"
+      + "                    \"op_relations\": [],\n"
+      + "                    \"external_option\": {\n"
+      + "                      \"use_external_option\": false,\n"
+      + "                      \"external_url\": \"\"\n"
+      + "                    }\n"
+      + "                  }\n"
+      + "                }\n"
+      + "              },\n"
+      + "              {\n"
+      + "                \"property\": {\n"
+      + "                  \"control\": \"Date\",\n"
+      + "                  \"id\": \"Date-1746005165574\",\n"
+      + "                  \"title\": [\n"
+      + "                    {\n"
+      + "                      \"text\": \"外借开始时间\",\n"
+      + "                      \"lang\": \"zh_CN\"\n"
+      + "                    }\n"
+      + "                  ],\n"
+      + "                  \"placeholder\": [\n"
+      + "                    {\n"
+      + "                      \"text\": \"\",\n"
+      + "                      \"lang\": \"zh_CN\"\n"
+      + "                    }\n"
+      + "                  ],\n"
+      + "                  \"require\": 0,\n"
+      + "                  \"un_print\": 0,\n"
+      + "                  \"un_replace\": 0,\n"
+      + "                  \"display\": 1\n"
+      + "                },\n"
+      + "                \"config\": {\n"
+      + "                  \"date\": {\n"
+      + "                    \"type\": \"day\"\n"
+      + "                  }\n"
+      + "                }\n"
+      + "              },\n"
+      + "              {\n"
+      + "                \"property\": {\n"
+      + "                  \"control\": \"Date\",\n"
+      + "                  \"id\": \"Date-1746005173386\",\n"
+      + "                  \"title\": [\n"
+      + "                    {\n"
+      + "                      \"text\": \"外借结束时间\",\n"
+      + "                      \"lang\": \"zh_CN\"\n"
+      + "                    }\n"
+      + "                  ],\n"
+      + "                  \"placeholder\": [\n"
+      + "                    {\n"
+      + "                      \"text\": \"\",\n"
+      + "                      \"lang\": \"zh_CN\"\n"
+      + "                    }\n"
+      + "                  ],\n"
+      + "                  \"require\": 0,\n"
+      + "                  \"un_print\": 0,\n"
+      + "                  \"un_replace\": 0,\n"
+      + "                  \"display\": 1\n"
+      + "                },\n"
+      + "                \"config\": {\n"
+      + "                  \"date\": {\n"
+      + "                    \"type\": \"day\"\n"
+      + "                  }\n"
+      + "                }\n"
+      + "              }\n"
+      + "            ],\n"
+      + "            \"stat_field\": [],\n"
+      + "            \"sum_field\": [],\n"
+      + "            \"print_format\": 0\n"
+      + "          }\n"
+      + "        }\n"
+      + "      },\n"
+      + "      {\n"
+      + "        \"property\": {\n"
+      + "          \"control\": \"File\",\n"
+      + "          \"id\": \"item-1494250388062\",\n"
+      + "          \"title\": [\n"
+      + "            {\n"
+      + "              \"text\": \"用印文件\",\n"
+      + "              \"lang\": \"zh_CN\"\n"
+      + "            },\n"
+      + "            {\n"
+      + "              \"text\": \"Attachment\",\n"
+      + "              \"lang\": \"en\"\n"
+      + "            }\n"
+      + "          ],\n"
+      + "          \"placeholder\": [\n"
+      + "            {\n"
+      + "              \"text\": \"\",\n"
+      + "              \"lang\": \"zh_CN\"\n"
+      + "            }\n"
+      + "          ],\n"
+      + "          \"require\": 1,\n"
+      + "          \"un_print\": 0,\n"
+      + "          \"inner_id\": \"\",\n"
+      + "          \"un_replace\": 0,\n"
+      + "          \"display\": 1\n"
+      + "        }\n"
+      + "      }\n"
+      + "    ]\n"
+      + "  }\n"
+      + "}";
+
+    WxCpOaApprovalTemplateResult templateDetail = WxCpGsonBuilder.create().fromJson(json, WxCpOaApprovalTemplateResult.class);
+    System.out.println(templateDetail);
+  }
+}
diff --git a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/config/impl/DemoToStringFix.java b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/config/impl/DemoToStringFix.java
new file mode 100644
index 0000000000..48fe9a6639
--- /dev/null
+++ b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/config/impl/DemoToStringFix.java
@@ -0,0 +1,66 @@
+package me.chanjar.weixin.cp.config.impl;
+
+import me.chanjar.weixin.common.redis.WxRedisOps;
+
+/**
+ * Demonstration of the fix for toString() StackOverflowError issue
+ */
+public class DemoToStringFix {
+    
+    public static void main(String[] args) {
+        System.out.println("=== Demonstrating toString() Fix for WxCp Redis Config ===");
+        
+        // Create a simple stub WxRedisOps implementation for testing
+        WxRedisOps stubRedisOps = new WxRedisOps() {
+            @Override
+            public String getValue(String key) { return null; }
+            @Override
+            public void setValue(String key, String value, int expire, java.util.concurrent.TimeUnit timeUnit) {}
+            @Override
+            public Long getExpire(String key) { return null; }
+            @Override
+            public void expire(String key, int expire, java.util.concurrent.TimeUnit timeUnit) {}
+            @Override
+            public java.util.concurrent.locks.Lock getLock(String key) { return null; }
+        };
+        
+        // Test AbstractWxCpInRedisConfigImpl directly with our stub
+        AbstractWxCpInRedisConfigImpl config = new AbstractWxCpInRedisConfigImpl(stubRedisOps, "demo:") {
+            // Anonymous class to test the abstract parent
+        };
+        
+        config.setCorpId("demoCorpId");
+        config.setAgentId(1001);
+        
+        System.out.println("Testing toString() method:");
+        try {
+            String result = config.toString();
+            System.out.println("✓ Success! toString() returned: " + result);
+            System.out.println("✓ No StackOverflowError occurred");
+            
+            // Verify the result contains expected information and excludes redisOps
+            boolean containsCorpId = result.contains("demoCorpId");
+            boolean containsAgentId = result.contains("1001");
+            boolean containsKeyPrefix = result.contains("demo:");
+            boolean excludesRedisOps = !result.contains("redisOps") && !result.contains("WxRedisOps");
+            
+            System.out.println("✓ Contains corpId: " + containsCorpId);
+            System.out.println("✓ Contains agentId: " + containsAgentId);
+            System.out.println("✓ Contains keyPrefix: " + containsKeyPrefix);
+            System.out.println("✓ Excludes redisOps: " + excludesRedisOps);
+            
+            if (containsCorpId && containsAgentId && containsKeyPrefix && excludesRedisOps) {
+                System.out.println("✓ All validations passed!");
+            } else {
+                System.out.println("✗ Some validations failed");
+            }
+            
+        } catch (StackOverflowError e) {
+            System.out.println("✗ StackOverflowError still occurred - fix failed");
+        } catch (Exception e) {
+            System.out.println("✗ Unexpected error: " + e.getMessage());
+        }
+        
+        System.out.println("\n=== Demo completed ===");
+    }
+}
\ No newline at end of file
diff --git a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/tp/service/impl/BaseWxCpTpServiceImplTest.java b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/tp/service/impl/BaseWxCpTpServiceImplTest.java
index 6f2639c890..c0fc2d614b 100644
--- a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/tp/service/impl/BaseWxCpTpServiceImplTest.java
+++ b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/tp/service/impl/BaseWxCpTpServiceImplTest.java
@@ -9,6 +9,8 @@
 import me.chanjar.weixin.cp.bean.WxTpCustomizedAuthUrl;
 import me.chanjar.weixin.cp.config.WxCpTpConfigStorage;
 import me.chanjar.weixin.cp.config.impl.WxCpTpDefaultConfigImpl;
+import me.chanjar.weixin.cp.config.impl.AbstractWxCpTpInRedisConfigImpl;
+import me.chanjar.weixin.cp.config.impl.WxCpTpRedisTemplateConfigImpl;
 import me.chanjar.weixin.cp.config.impl.WxCpTpRedissonConfigImpl;
 import me.chanjar.weixin.cp.tp.service.WxCpTpService;
 import org.mockito.Mockito;
@@ -69,7 +71,10 @@ public void setUp() {
    * @return the wx cp tp config storage
    */
   public WxCpTpConfigStorage wxCpTpConfigStorage() {
-    return WxCpTpRedissonConfigImpl.builder().corpId(PROVIDER_CORP_ID).providerSecret(PROVIDER_SECRET).wxRedisOps(new RedissonWxRedisOps(redissonClient())).build();
+    WxCpTpRedissonConfigImpl wxCpTpRedissonConfig=new WxCpTpRedissonConfigImpl(redissonClient(),"");
+    wxCpTpRedissonConfig.setCorpId(PROVIDER_CORP_ID);
+    wxCpTpRedissonConfig.setProviderSecret(PROVIDER_SECRET);
+    return wxCpTpRedissonConfig;
   }
 
   /**
diff --git a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/tp/service/impl/WxCpTpServiceApacheHttpClientImplTest.java b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/tp/service/impl/WxCpTpServiceApacheHttpClientImplTest.java
index 75927af4d9..b46ddf003b 100644
--- a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/tp/service/impl/WxCpTpServiceApacheHttpClientImplTest.java
+++ b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/tp/service/impl/WxCpTpServiceApacheHttpClientImplTest.java
@@ -6,6 +6,8 @@
 import me.chanjar.weixin.cp.bean.WxCpProviderToken;
 import me.chanjar.weixin.cp.bean.WxCpTpCorpId2OpenCorpId;
 import me.chanjar.weixin.cp.config.WxCpTpConfigStorage;
+import me.chanjar.weixin.cp.config.impl.AbstractWxCpTpInRedisConfigImpl;
+import me.chanjar.weixin.cp.config.impl.WxCpTpRedisTemplateConfigImpl;
 import me.chanjar.weixin.cp.config.impl.WxCpTpRedissonConfigImpl;
 import me.chanjar.weixin.cp.tp.service.WxCpTpService;
 import org.apache.commons.lang3.StringUtils;
@@ -48,14 +50,10 @@ public class WxCpTpServiceApacheHttpClientImplTest {
    * The constant PROVIDER_CORP_ID.
    */
   public static final String PROVIDER_CORP_ID = "xxxxxx";
-  /**
-   * The constant CORP_SECRET.
-   */
-  public static final String CORP_SECRET = "xxxxxx";
   /**
    * The constant PROVIDER_SECRET.
    */
-  public static final String PROVIDER_SECRET = CORP_SECRET;
+  public static final String PROVIDER_SECRET = "xxxxxx";
   /**
    * The constant REDIS_ADDR.
    */
@@ -85,9 +83,15 @@ public void setUp() {
    * @return the wx cp tp config storage
    */
   public WxCpTpConfigStorage wxCpTpConfigStorage() {
-    return WxCpTpRedissonConfigImpl.builder().baseApiUrl(API_URL).suiteId(SUITE_ID).suiteSecret(SUITE_SECRET)
-      .token(TOKEN).aesKey(AES_KEY).corpId(PROVIDER_CORP_ID).corpSecret(CORP_SECRET).providerSecret(PROVIDER_SECRET)
-      .wxRedisOps(new RedissonWxRedisOps(redissonClient())).build();
+    WxCpTpRedissonConfigImpl wxCpTpRedissonConfig=new WxCpTpRedissonConfigImpl(redissonClient(),"");
+    wxCpTpRedissonConfig.setBaseApiUrl(API_URL);
+    wxCpTpRedissonConfig.setSuiteId(SUITE_ID);
+    wxCpTpRedissonConfig.setSuiteSecret(SUITE_SECRET);
+    wxCpTpRedissonConfig.setToken(TOKEN);
+    wxCpTpRedissonConfig.setEncodingAESKey(AES_KEY);
+    wxCpTpRedissonConfig.setCorpId(PROVIDER_CORP_ID);
+    wxCpTpRedissonConfig.setProviderSecret(PROVIDER_SECRET);
+    return wxCpTpRedissonConfig;
   }
 
   /**
diff --git a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/util/json/WxCpUserGsonAdapterTest.java b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/util/json/WxCpUserGsonAdapterTest.java
index 9b62a8d580..66be5c66a2 100644
--- a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/util/json/WxCpUserGsonAdapterTest.java
+++ b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/util/json/WxCpUserGsonAdapterTest.java
@@ -180,4 +180,31 @@ public void testSerialize() {
       "{\"type\":2,\"name\":\"测试app\"," +
       "\"miniprogram\":{\"appid\":\"wx8bd80126147df384\",\"pagepath\":\"/index\",\"title\":\"my miniprogram\"}}]}}");
   }
+
+  /**
+   * Test directLeader empty array serialization.
+   * This test verifies that empty directLeader arrays are included in JSON as "direct_leader":[]
+   * instead of being omitted, which is required for WeChat Work API to reset user direct leaders.
+   */
+  @Test
+  public void testDirectLeaderEmptyArraySerialization() {
+    WxCpUser user = new WxCpUser();
+    user.setUserId("testuser");
+    user.setName("Test User");
+    
+    // Test with empty array - should be serialized as "direct_leader":[]
+    user.setDirectLeader(new String[]{});
+    String json = user.toJson();
+    assertThat(json).contains("\"direct_leader\":[]");
+    
+    // Test with null - should not include direct_leader field
+    user.setDirectLeader(null);
+    json = user.toJson();
+    assertThat(json).doesNotContain("direct_leader");
+    
+    // Test with non-empty array - should be serialized normally
+    user.setDirectLeader(new String[]{"leader1", "leader2"});
+    json = user.toJson();
+    assertThat(json).contains("\"direct_leader\":[\"leader1\",\"leader2\"]");
+  }
 }
diff --git a/weixin-java-miniapp/pom.xml b/weixin-java-miniapp/pom.xml
index cfe52b9686..8a9cb02dc0 100644
--- a/weixin-java-miniapp/pom.xml
+++ b/weixin-java-miniapp/pom.xml
@@ -7,7 +7,7 @@
   
     com.github.binarywang
     wx-java
-    4.7.2.B
+    4.7.8.B
   
 
   weixin-java-miniapp
@@ -31,6 +31,11 @@
       okhttp
       provided
     
+    
+      org.apache.httpcomponents.client5
+      httpclient5
+      provided
+    
 
     
       org.testng
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaAnalysisService.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaAnalysisService.java
index fa6d444406..d6d13431d5 100644
--- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaAnalysisService.java
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaAnalysisService.java
@@ -7,7 +7,7 @@
 import java.util.List;
 
 /**
- * 小程序数据分析相关接口
+ * 小程序数据分析相关接口。
  * 文档:https://mp.weixin.qq.com/debug/wxadoc/dev/api/analysis.html
  *
  * @author Charming
@@ -16,114 +16,114 @@
 public interface WxMaAnalysisService {
 
   /**
-   * 查询概况趋势
+   * 查询概况趋势。
    * 温馨提示:小程序接口目前只能查询一天的数据,即 beginDate 和 endDate 一样
    *
-   * @param beginDate 开始日期
-   * @param endDate   结束日期,限定查询1天数据,end_date允许设置的最大值为昨日
-   * @return 概况趋势
+   * @param beginDate        开始日期
+   * @param endDate          结束日期,限定查询1天数据,end_date允许设置的最大值为昨日
+   * @return                 概况趋势列表
    * @throws WxErrorException 获取失败时抛出,具体错误码请看文档
    */
   List getDailySummaryTrend(Date beginDate, Date endDate) throws WxErrorException;
 
   /**
-   * 获取日访问趋势
+   * 获取日访问趋势。
    * 温馨提示:小程序接口目前只能查询一天的数据,即 beginDate 和 endDate 一样
    *
-   * @param beginDate 开始日期
-   * @param endDate   结束日期,限定查询1天数据,end_date允许设置的最大值为昨日
-   * @return 日访问趋势
+   * @param beginDate        开始日期
+   * @param endDate          结束日期,限定查询1天数据,end_date允许设置的最大值为昨日
+   * @return                 日访问趋势列表
    * @throws WxErrorException 获取失败时抛出,具体错误码请看文档
    */
   List getDailyVisitTrend(Date beginDate, Date endDate) throws WxErrorException;
 
   /**
-   * 获取周访问趋势
+   * 获取周访问趋势。
    * 限定查询一个自然周的数据,时间必须按照自然周的方式输入: 如:20170306(周一), 20170312(周日)
    *
-   * @param beginDate 开始日期,为周一日期
-   * @param endDate   结束日期,为周日日期,限定查询一周数据
-   * @return 周访问趋势(每项数据都是一个自然周汇总)
+   * @param beginDate        开始日期,为周一日期
+   * @param endDate          结束日期,为周日日期,限定查询一周数据
+   * @return                 周访问趋势列表(每项数据都是一个自然周汇总)
    * @throws WxErrorException 获取失败时抛出,具体错误码请看文档
    */
   List getWeeklyVisitTrend(Date beginDate, Date endDate) throws WxErrorException;
 
   /**
-   * 获取月访问趋势
+   * 获取月访问趋势。
    * 限定查询一个自然月的数据,时间必须按照自然月的方式输入: 如:20170201(月初), 20170228(月末)
    *
-   * @param beginDate 开始日期,为自然月第一天
-   * @param endDate   结束日期,为自然月最后一天,限定查询一个月数据
-   * @return 月访问趋势(每项数据都是一个自然月汇总)
+   * @param beginDate        开始日期,为自然月第一天
+   * @param endDate          结束日期,为自然月最后一天,限定查询一个月数据
+   * @return                 月访问趋势列表(每项数据都是一个自然月汇总)
    * @throws WxErrorException 获取失败时抛出,具体错误码请看文档
    */
   List getMonthlyVisitTrend(Date beginDate, Date endDate) throws WxErrorException;
 
   /**
-   * 获取访问分布
+   * 获取访问分布。
    * (此接口目前只能查询一天的数据,即 beginDate 和 endDate 一样)
    *
-   * @param beginDate 开始日期,为周一日期
-   * @param endDate   结束日期,限定查询1天数据,end_date允许设置的最大值为昨日
-   * @return 访问分布
+   * @param beginDate        开始日期
+   * @param endDate          结束日期,限定查询1天数据,end_date允许设置的最大值为昨日
+   * @return                 访问分布对象
    * @throws WxErrorException 获取失败时抛出,具体错误码请看文档
    */
   WxMaVisitDistribution getVisitDistribution(Date beginDate, Date endDate) throws WxErrorException;
 
   /**
-   * 日留存
+   * 获取日留存数据。
    * (此接口目前只能查询一天的数据,即 beginDate 和 endDate 一样)
    *
-   * @param beginDate 开始日期,为周一日期
-   * @param endDate   结束日期,限定查询 1 天数据,endDate 允许设置的最大值为昨日
-   * @return 日留存
+   * @param beginDate        开始日期
+   * @param endDate          结束日期,限定查询 1 天数据,endDate 允许设置的最大值为昨日
+   * @return                 日留存信息对象
    * @throws WxErrorException 获取失败时抛出,具体错误码请看文档
    */
   WxMaRetainInfo getDailyRetainInfo(Date beginDate, Date endDate) throws WxErrorException;
 
   /**
-   * 周留存
+   * 获取周留存数据。
    * 限定查询一个自然周的数据,时间必须按照自然周的方式输入: 如:20170306(周一), 20170312(周日)
    *
-   * @param beginDate 开始日期,为周一日期
-   * @param endDate   结束日期,为周日日期,限定查询一周数据
-   * @return 周留存
+   * @param beginDate        开始日期,为周一日期
+   * @param endDate          结束日期,为周日日期,限定查询一周数据
+   * @return                 周留存信息对象
    * @throws WxErrorException 获取失败时抛出,具体错误码请看文档
    */
   WxMaRetainInfo getWeeklyRetainInfo(Date beginDate, Date endDate) throws WxErrorException;
 
   /**
-   * 月留存
+   * 获取月留存数据。
    * 限定查询一个自然月的数据,时间必须按照自然月的方式输入: 如:20170201(月初), 20170228(月末)
    *
-   * @param beginDate 开始日期,为自然月第一天
-   * @param endDate   结束日期,为自然月最后一天,限定查询一个月数据
-   * @return 月留存
+   * @param beginDate        开始日期,为自然月第一天
+   * @param endDate          结束日期,为自然月最后一天,限定查询一个月数据
+   * @return                 月留存信息对象
    * @throws WxErrorException 获取失败时抛出,具体错误码请看文档
    */
   WxMaRetainInfo getMonthlyRetainInfo(Date beginDate, Date endDate) throws WxErrorException;
 
   /**
-   * 获取访问页面数据
+   * 获取访问页面数据。
    * 温馨提示:此接口目前只能查询一天的数据,即 beginDate 和 endDate 一样
    *
-   * @param beginDate 开始日期
-   * @param endDate   结束日期,限定查询1天数据,end_date允许设置的最大值为昨日
-   * @return 访问页面数据
+   * @param beginDate        开始日期
+   * @param endDate          结束日期,限定查询1天数据,end_date允许设置的最大值为昨日
+   * @return                 访问页面数据列表
    * @throws WxErrorException 获取失败时抛出,具体错误码请看文档
    */
   List getVisitPage(Date beginDate, Date endDate) throws WxErrorException;
 
   /**
-   * 获取小程序新增或活跃用户的画像分布数据
+   * 获取小程序新增或活跃用户的画像分布数据。
    * 时间范围支持昨天、最近7天、最近30天。
    * 其中,新增用户数为时间范围内首次访问小程序的去重用户数,
    * 活跃用户数为时间范围内访问过小程序的去重用户数。
    * 画像属性包括用户年龄、性别、省份、城市、终端类型、机型。
    *
-   * @param beginDate 开始日期
-   * @param endDate   结束日期,开始日期与结束日期相差的天数限定为0/6/29,分别表示查询最近1/7/30天数据,end_date允许设置的最大值为昨日
-   * @return 小程序新增或活跃用户的画像分布数据
+   * @param beginDate        开始日期
+   * @param endDate          结束日期,开始日期与结束日期相差的天数限定为0/6/29,分别表示查询最近1/7/30天数据,end_date允许设置的最大值为昨日
+   * @return                 小程序新增或活跃用户的画像分布数据对象
    * @throws WxErrorException 获取失败时抛出,具体错误码请看文档
    */
   WxMaUserPortrait getUserPortrait(Date beginDate, Date endDate) throws WxErrorException;
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaCloudService.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaCloudService.java
index 4e18fec5c4..ea7e9ab20f 100644
--- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaCloudService.java
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaCloudService.java
@@ -16,539 +16,457 @@
  */
 public interface WxMaCloudService {
   /**
-   * Invoke cloud function string.
+   * 触发云函数。注意:HTTP API 途径触发云函数不包含用户信息。
    *
-   * @param name the name
-   * @param body the body
-   * @return the string
-   * @throws WxErrorException the wx error exception
+   * @param name             云函数名称
+   * @param body             云函数的传入参数,具体结构由开发者定义
+   * @return                 云函数返回的buffer
+   * @throws WxErrorException 调用失败时抛出
    */
   String invokeCloudFunction(String name, String body) throws WxErrorException;
 
   /**
-   * 
    * 触发云函数。注意:HTTP API 途径触发云函数不包含用户信息。
    * 文档地址:https://developers.weixin.qq.com/miniprogram/dev/wxcloud/reference-http-api/functions/invokeCloudFunction.html
    *
-   * 请求地址
-   * POST https://api.weixin.qq.com/tcb/invokecloudfunction?access_token=ACCESS_TOKEN&env=ENV&name=FUNCTION_NAME
-   *
-   * 
- * - * @param env string 是 云开发环境ID - * @param name string 是 云函数名称 - * @param body string 是 云函数的传入参数,具体结构由开发者定义。 - * @return resp_data string 云函数返回的buffer - * @throws WxErrorException . + * @param env 云开发环境ID + * @param name 云函数名称 + * @param body 云函数的传入参数,具体结构由开发者定义 + * @return 云函数返回的buffer + * @throws WxErrorException 调用失败时抛出 */ String invokeCloudFunction(String env, String name, String body) throws WxErrorException; /** - * Add list. + * 批量添加记录到集合。 * - * @param collection the collection - * @param list the list - * @return the list - * @throws WxErrorException the wx error exception + * @param collection 集合名称 + * @param list 要添加的记录列表 + * @return 插入成功的记录ID列表 + * @throws WxErrorException 添加失败时抛出 */ - List add(String collection, List list) throws WxErrorException; + List add(String collection, List list) throws WxErrorException; /** - * Add string. + * 添加单条记录到集合。 * - * @param collection the collection - * @param obj the obj - * @return the string - * @throws WxErrorException the wx error exception + * @param collection 集合名称 + * @param obj 要添加的记录对象 + * @return 插入成功的记录ID + * @throws WxErrorException 添加失败时抛出 */ String add(String collection, Object obj) throws WxErrorException; /** - * Database add json array. + * 数据库插入记录。 * - * @param query the query - * @return the json array - * @throws WxErrorException the wx error exception + * @param query 数据库操作语句 + * @return 插入成功的数据集合主键_id列表 + * @throws WxErrorException 插入失败时抛出 */ JsonArray databaseAdd(String query) throws WxErrorException; /** - *
-   * 数据库插入记录
-   *
+   * 数据库插入记录。
    * 文档地址:https://developers.weixin.qq.com/miniprogram/dev/wxcloud/reference-http-api/database/databaseAdd.html
-   * 请求地址:POST https://api.weixin.qq.com/tcb/databaseadd?access_token=ACCESS_TOKEN
-   * 
* - * @param env 云环境ID - * @param query 数据库操作语句 - * @return 插入成功的数据集合主键_id json array - * @throws WxErrorException . + * @param env 云环境ID + * @param query 数据库操作语句 + * @return 插入成功的数据集合主键_id列表 + * @throws WxErrorException 插入失败时抛出 */ JsonArray databaseAdd(String env, String query) throws WxErrorException; /** - * Delete integer. + * 删除集合中符合条件的记录。 * - * @param collection the collection - * @param whereJson the where json - * @return the integer - * @throws WxErrorException the wx error exception + * @param collection 集合名称 + * @param whereJson 查询条件JSON字符串 + * @return 删除的记录数量 + * @throws WxErrorException 删除失败时抛出 */ Integer delete(String collection, String whereJson) throws WxErrorException; /** - * Database delete int. + * 数据库删除记录。 * - * @param query the query - * @return the int - * @throws WxErrorException the wx error exception + * @param query 数据库操作语句 + * @return 删除记录数量 + * @throws WxErrorException 删除失败时抛出 */ int databaseDelete(String query) throws WxErrorException; /** - *
-   * 数据库删除记录
-   *
+   * 数据库删除记录。
    * 文档地址:https://developers.weixin.qq.com/miniprogram/dev/wxcloud/reference-http-api/database/databaseDelete.html
-   * 请求地址:POST https://api.weixin.qq.com/tcb/databasedelete?access_token=ACCESS_TOKEN
-   * 
* - * @param env 云环境ID - * @param query 数据库操作语句 - * @return 删除记录数量 int - * @throws WxErrorException . + * @param env 云环境ID + * @param query 数据库操作语句 + * @return 删除记录数量 + * @throws WxErrorException 删除失败时抛出 */ int databaseDelete(String env, String query) throws WxErrorException; /** - * Update wx cloud database update result. + * 更新集合中符合条件的记录。 * - * @param collection the collection - * @param whereJson the where json - * @param updateJson the update json - * @return the wx cloud database update result - * @throws WxErrorException the wx error exception + * @param collection 集合名称 + * @param whereJson 查询条件JSON字符串 + * @param updateJson 更新内容JSON字符串 + * @return 更新结果对象 + * @throws WxErrorException 更新失败时抛出 */ WxCloudDatabaseUpdateResult update(String collection, String whereJson, String updateJson) throws WxErrorException; /** - * Database update wx cloud database update result. + * 数据库更新记录。 * - * @param query the query - * @return the wx cloud database update result - * @throws WxErrorException the wx error exception + * @param query 数据库操作语句 + * @return 更新结果对象 + * @throws WxErrorException 更新失败时抛出 */ WxCloudDatabaseUpdateResult databaseUpdate(String query) throws WxErrorException; /** - *
-   * 数据库更新记录
-   *
+   * 数据库更新记录。
    * 文档地址:https://developers.weixin.qq.com/miniprogram/dev/wxcloud/reference-http-api/database/databaseUpdate.html
-   * 请求地址:POST https://api.weixin.qq.com/tcb/databaseupdate?access_token=ACCESS_TOKEN
-   * 
* - * @param env 云环境ID - * @param query 数据库操作语句 - * @return . wx cloud database update result - * @throws WxErrorException . + * @param env 云环境ID + * @param query 数据库操作语句 + * @return 更新结果对象 + * @throws WxErrorException 更新失败时抛出 */ WxCloudDatabaseUpdateResult databaseUpdate(String env, String query) throws WxErrorException; /** + * 查询集合中的记录。 + * 示例: * db.collection('geo') - * .where({ - * price: _.gt(10) - * }) - * .orderBy('_id', 'asc') - * .orderBy('price', 'desc') - * .skip(1) - * .limit(10) - * .get() - * - * @param collection the collection - * @param whereJson the where json - * @param orderBy the order by - * @param skip the skip - * @param limit the limit - * @return wx cloud database query result - * @throws WxErrorException the wx error exception + * .where({price: _.gt(10)}) + * .orderBy('_id', 'asc') + * .orderBy('price', 'desc') + * .skip(1) + * .limit(10) + * .get() + * + * @param collection 集合名称 + * @param whereJson 查询条件JSON字符串 + * @param orderBy 排序条件Map + * @param skip 跳过记录数 + * @param limit 限制返回记录数 + * @return 查询结果对象 + * @throws WxErrorException 查询失败时抛出 */ WxCloudDatabaseQueryResult query(String collection, String whereJson, Map orderBy, Integer skip, Integer limit) throws WxErrorException; /** - * Database query wx cloud database query result. + * 数据库查询记录。 * - * @param query the query - * @return the wx cloud database query result - * @throws WxErrorException the wx error exception + * @param query 数据库操作语句 + * @return 查询结果对象 + * @throws WxErrorException 查询失败时抛出 */ WxCloudDatabaseQueryResult databaseQuery(String query) throws WxErrorException; /** - *
-   * 数据库查询记录
-   *
+   * 数据库查询记录。
    * 文档地址:https://developers.weixin.qq.com/miniprogram/dev/wxcloud/reference-http-api/database/databaseQuery.html
-   * 请求地址:POST https://api.weixin.qq.com/tcb/databasequery?access_token=ACCESS_TOKEN
-   * 
* - * @param env 云环境ID - * @param query 数据库操作语句 - * @return . wx cloud database query result - * @throws WxErrorException . + * @param env 云环境ID + * @param query 数据库操作语句 + * @return 查询结果对象 + * @throws WxErrorException 查询失败时抛出 */ WxCloudDatabaseQueryResult databaseQuery(String env, String query) throws WxErrorException; /** - * Database aggregate json array. + * 数据库聚合记录。 * - * @param query the query - * @return the json array - * @throws WxErrorException the wx error exception + * @param query 数据库操作语句 + * @return 聚合结果JSON数组 + * @throws WxErrorException 聚合失败时抛出 */ JsonArray databaseAggregate(String query) throws WxErrorException; /** - *
-   * 数据库聚合记录
-   *
+   * 数据库聚合记录。
    * 文档地址:https://developers.weixin.qq.com/miniprogram/dev/wxcloud/reference-http-api/database/databaseAggregate.html
-   * 请求地址:POST https://api.weixin.qq.com/tcb/databaseaggregate?access_token=ACCESS_TOKEN
-   * 
* - * @param env 云环境ID - * @param query 数据库操作语句 - * @return . json array - * @throws WxErrorException . + * @param env 云环境ID + * @param query 数据库操作语句 + * @return 聚合结果JSON数组 + * @throws WxErrorException 聚合失败时抛出 */ JsonArray databaseAggregate(String env, String query) throws WxErrorException; /** - * Count long. + * 统计集合中符合条件的记录数。 * - * @param collection the collection - * @param whereJson the where json - * @return the long - * @throws WxErrorException the wx error exception + * @param collection 集合名称 + * @param whereJson 查询条件JSON字符串 + * @return 记录数量 + * @throws WxErrorException 统计失败时抛出 */ Long count(String collection, String whereJson) throws WxErrorException; /** - * Database count long. + * 统计集合记录数或统计查询语句对应的结果记录数。 * - * @param query the query - * @return the long - * @throws WxErrorException the wx error exception + * @param query 数据库操作语句 + * @return 记录数量 + * @throws WxErrorException 统计失败时抛出 */ Long databaseCount(String query) throws WxErrorException; /** - *
-   * 统计集合记录数或统计查询语句对应的结果记录数
-   *
+   * 统计集合记录数或统计查询语句对应的结果记录数。
    * 文档地址:https://developers.weixin.qq.com/miniprogram/dev/wxcloud/reference-http-api/database/databaseCount.html
-   * 请求地址:POST https://api.weixin.qq.com/tcb/databasecount?access_token=ACCESS_TOKEN
-   * 
* - * @param env 云环境ID - * @param query 数据库操作语句 - * @return 记录数量 long - * @throws WxErrorException . + * @param env 云环境ID + * @param query 数据库操作语句 + * @return 记录数量 + * @throws WxErrorException 统计失败时抛出 */ Long databaseCount(String env, String query) throws WxErrorException; /** - * Update index. + * 变更数据库索引。 * - * @param collectionName the collection name - * @param createIndexes the create indexes - * @param dropIndexNames the drop index names - * @throws WxErrorException the wx error exception + * @param collectionName 集合名称 + * @param createIndexes 新增索引对象列表 + * @param dropIndexNames 要删除的索引名称列表 + * @throws WxErrorException 更新失败时抛出 */ void updateIndex(String collectionName, List createIndexes, List dropIndexNames) throws WxErrorException; /** - *
-   * 变更数据库索引
-   *
+   * 变更数据库索引。
    * 文档地址:https://developers.weixin.qq.com/miniprogram/dev/wxcloud/reference-http-api/database/updateIndex.html
-   * 请求地址:POST https://api.weixin.qq.com/tcb/updateindex?access_token=ACCESS_TOKEN
-   * 
* - * @param env 云环境ID - * @param collectionName 集合名称 - * @param createIndexes 新增索引对象 - * @param dropIndexNames 要删除的索引的名字 - * @throws WxErrorException . + * @param env 云环境ID + * @param collectionName 集合名称 + * @param createIndexes 新增索引对象列表 + * @param dropIndexNames 要删除的索引名称列表 + * @throws WxErrorException 更新失败时抛出 */ void updateIndex(String env, String collectionName, List createIndexes, List dropIndexNames) throws WxErrorException; /** - * Database migrate import long. + * 数据库导入。 * - * @param collectionName the collection name - * @param filePath the file path - * @param fileType the file type - * @param stopOnError the stop on error - * @param conflictMode the conflict mode - * @return the long - * @throws WxErrorException the wx error exception + * @param collectionName 导入collection名 + * @param filePath 导入文件路径 + * @param fileType 导入文件类型,1:JSON,2:CSV + * @param stopOnError 是否在遇到错误时停止导入 + * @param conflictMode 冲突处理模式,1:INSERT,2:UPSERT + * @return 任务ID + * @throws WxErrorException 导入失败时抛出 */ Long databaseMigrateImport(String collectionName, String filePath, int fileType, boolean stopOnError, int conflictMode) throws WxErrorException; /** - *
-   * 数据库导入
+   * 数据库导入。
+   * 文档地址:https://developers.weixin.qq.com/miniprogram/dev/wxcloud/reference-http-api/database/databaseMigrateImport.html
+   * 注意:导入文件需先上传到同环境的存储中,可使用开发者工具或HTTP API的上传文件API上传
    *
-   * 文档地址:https://developers.weixin.qq.com/miniprogram/dev/wxcloud/reference-http-api/database/databaseMigrateImport
-   * .html
-   * 请求地址: POST https://api.weixin.qq.com/tcb/databasemigrateimport?access_token=ACCESS_TOKEN
-   * 
- * - * @param env 云环境ID - * @param collectionName 导入collection名 - * @param filePath 导入文件路径(导入文件需先上传到同环境的存储中,可使用开发者工具或 HTTP API的上传文件 API上传) - * @param fileType 导入文件类型, 1 JSON, 2 CSV - * @param stopOnError 是否在遇到错误时停止导入 - * @param conflictMode 冲突处理模式 : 1 INSERT , 2 UPSERT - * @return jobId long - * @throws WxErrorException . + * @param env 云环境ID + * @param collectionName 导入collection名 + * @param filePath 导入文件路径 + * @param fileType 导入文件类型,1:JSON,2:CSV + * @param stopOnError 是否在遇到错误时停止导入 + * @param conflictMode 冲突处理模式,1:INSERT,2:UPSERT + * @return 任务ID + * @throws WxErrorException 导入失败时抛出 */ Long databaseMigrateImport(String env, String collectionName, String filePath, int fileType, boolean stopOnError, int conflictMode) throws WxErrorException; /** - * Database migrate export long. + * 数据库导出。 * - * @param filePath the file path - * @param fileType the file type - * @param query the query - * @return the long - * @throws WxErrorException the wx error exception + * @param filePath 导出文件路径 + * @param fileType 导出文件类型,1:JSON,2:CSV + * @param query 导出条件 + * @return 任务ID + * @throws WxErrorException 导出失败时抛出 */ Long databaseMigrateExport(String filePath, int fileType, String query) throws WxErrorException; /** - *
-   * 数据库导出
-   *
-   * 文档地址:https://developers.weixin.qq.com/miniprogram/dev/wxcloud/reference-http-api/database/databaseMigrateExport
-   * .html
-   * 请求地址: POST https://api.weixin.qq.com/tcb/databasemigrateexport?access_token=ACCESS_TOKEN
-   * 
+ * 数据库导出。 + * 文档地址:https://developers.weixin.qq.com/miniprogram/dev/wxcloud/reference-http-api/database/databaseMigrateExport.html + * 注意:文件会导出到同环境的云存储中,可使用获取下载链接API获取下载链接 * - * @param env 云环境ID - * @param filePath 导出文件路径(文件会导出到同环境的云存储中,可使用获取下载链接 API 获取下载链接) - * @param fileType 导出文件类型, 1 JSON, 2 CSV - * @param query 导出条件 - * @return jobId long - * @throws WxErrorException . + * @param env 云环境ID + * @param filePath 导出文件路径 + * @param fileType 导出文件类型,1:JSON,2:CSV + * @param query 导出条件 + * @return 任务ID + * @throws WxErrorException 导出失败时抛出 */ Long databaseMigrateExport(String env, String filePath, int fileType, String query) throws WxErrorException; /** - * Database migrate query info wx cloud cloud database migrate query info result. + * 数据库迁移状态查询。 * - * @param jobId the job id - * @return the wx cloud cloud database migrate query info result - * @throws WxErrorException the wx error exception + * @param jobId 迁移任务ID + * @return 迁移状态查询结果 + * @throws WxErrorException 查询失败时抛出 */ WxCloudCloudDatabaseMigrateQueryInfoResult databaseMigrateQueryInfo(Long jobId) throws WxErrorException; /** - *
-   *   数据库迁移状态查询
-   *
-   *  文档地址:https://developers.weixin.qq.com/miniprogram/dev/wxcloud/reference-http-api/database
-   *  /databaseMigrateQueryInfo.html
-   *  请求地址:POST https://api.weixin.qq.com/tcb/databasemigratequeryinfo?access_token=ACCESS_TOKEN
-   * 
+ * 数据库迁移状态查询。 + * 文档地址:https://developers.weixin.qq.com/miniprogram/dev/wxcloud/reference-http-api/database/databaseMigrateQueryInfo.html * - * @param env 云环境ID - * @param jobId 迁移任务ID - * @return . wx cloud cloud database migrate query info result - * @throws WxErrorException . + * @param env 云环境ID + * @param jobId 迁移任务ID + * @return 迁移状态查询结果 + * @throws WxErrorException 查询失败时抛出 */ WxCloudCloudDatabaseMigrateQueryInfoResult databaseMigrateQueryInfo(String env, Long jobId) throws WxErrorException; /** - * Upload file wx cloud upload file result. + * 获取文件上传链接。 * - * @param path the path - * @return the wx cloud upload file result - * @throws WxErrorException the wx error exception + * @param path 上传路径 + * @return 上传结果对象 + * @throws WxErrorException 获取失败时抛出 */ WxCloudUploadFileResult uploadFile(String path) throws WxErrorException; /** - *
-   * 获取文件上传链接
-   *
+   * 获取文件上传链接。
    * 文档地址:https://developers.weixin.qq.com/miniprogram/dev/wxcloud/reference-http-api/storage/uploadFile.html
-   * 请求地址:POST https://api.weixin.qq.com/tcb/uploadfile?access_token=ACCESS_TOKEN
-   *
-   * 
* - * @param env 云环境ID - * @param path 上传路径 - * @return 上传结果 wx cloud upload file result - * @throws WxErrorException . + * @param env 云环境ID + * @param path 上传路径 + * @return 上传结果对象 + * @throws WxErrorException 获取失败时抛出 */ WxCloudUploadFileResult uploadFile(String env, String path) throws WxErrorException; /** - * Batch download file wx cloud batch download file result. + * 获取文件下载链接。 * - * @param fileIds the file ids - * @param maxAges the max ages - * @return the wx cloud batch download file result - * @throws WxErrorException the wx error exception + * @param fileIds 文件ID数组 + * @param maxAges 下载链接有效期数组,对应文件id列表 + * @return 下载链接信息对象 + * @throws WxErrorException 获取失败时抛出 */ WxCloudBatchDownloadFileResult batchDownloadFile(String[] fileIds, long[] maxAges) throws WxErrorException; /** - *
-   * 获取文件下载链接
-   *
+   * 获取文件下载链接。
    * 文档地址:https://developers.weixin.qq.com/miniprogram/dev/wxcloud/reference-http-api/storage/batchDownloadFile.html
-   * 请求地址:POST https://api.weixin.qq.com/tcb/batchdownloadfile?access_token=ACCESS_TOKEN
-   *
-   * 
* - * @param env 云环境ID - * @param fileIds 文件ID列表 - * @param maxAges 下载链接有效期列表,对应文件id列表 - * @return 下载链接信息 wx cloud batch download file result - * @throws WxErrorException . + * @param env 云环境ID + * @param fileIds 文件ID数组 + * @param maxAges 下载链接有效期数组,对应文件id列表 + * @return 下载链接信息对象 + * @throws WxErrorException 获取失败时抛出 */ WxCloudBatchDownloadFileResult batchDownloadFile(String env, String[] fileIds, long[] maxAges) throws WxErrorException; /** - * Batch delete file wx cloud batch delete file result. + * 删除文件。 * - * @param fileIds the file ids - * @return the wx cloud batch delete file result - * @throws WxErrorException the wx error exception + * @param fileIds 文件ID数组 + * @return 删除结果对象 + * @throws WxErrorException 删除失败时抛出 */ WxCloudBatchDeleteFileResult batchDeleteFile(String[] fileIds) throws WxErrorException; /** - *
-   * 删除文件
-   *
+   * 删除文件。
    * 文档地址:https://developers.weixin.qq.com/miniprogram/dev/wxcloud/reference-http-api/storage/batchDeleteFile.html
-   * 请求地址:POST https://api.weixin.qq.com/tcb/batchdeletefile?access_token=ACCESS_TOKEN
-   *
-   * 
* - * @param env 云环境ID - * @param fileIds 文件ID列表 - * @return 下载链接信息 wx cloud batch delete file result - * @throws WxErrorException . + * @param env 云环境ID + * @param fileIds 文件ID数组 + * @return 删除结果对象 + * @throws WxErrorException 删除失败时抛出 */ WxCloudBatchDeleteFileResult batchDeleteFile(String env, String[] fileIds) throws WxErrorException; /** - *
-   *  获取腾讯云API调用凭证
-   *
-   *  文档地址:https://developers.weixin.qq.com/miniprogram/dev/wxcloud/reference-http-api/utils/getQcloudToken.html
-   *  请求地址:POST https://api.weixin.qq.com/tcb/getqcloudtoken?access_token=ACCESS_TOKEN
-   * 
+ * 获取腾讯云API调用凭证。 + * 文档地址:https://developers.weixin.qq.com/miniprogram/dev/wxcloud/reference-http-api/utils/getQcloudToken.html * - * @param lifeSpan 有效期(单位为秒,最大7200) - * @return . qcloud token - * @throws WxErrorException . + * @param lifeSpan 有效期(单位为秒,最大7200) + * @return 腾讯云Token结果对象 + * @throws WxErrorException 获取失败时抛出 */ WxCloudGetQcloudTokenResult getQcloudToken(long lifeSpan) throws WxErrorException; /** - * Database collection add. + * 新增集合。 * - * @param collectionName the collection name - * @throws WxErrorException the wx error exception + * @param collectionName 集合名称 + * @throws WxErrorException 新增失败时抛出 */ void databaseCollectionAdd(String collectionName) throws WxErrorException; /** - *
-   * 新增集合
+   * 新增集合。
+   * 文档地址:https://developers.weixin.qq.com/miniprogram/dev/wxcloud/reference-http-api/database/databaseCollectionAdd.html
    *
-   * 文档地址:https://developers.weixin.qq.com/miniprogram/dev/wxcloud/reference-http-api/database/databaseCollectionAdd
-   * .html
-   * 请求地址:POST https://api.weixin.qq.com/tcb/databasecollectionadd?access_token=ACCESS_TOKEN
-   * 
- * - * @param env 云环境ID - * @param collectionName 集合名称 - * @throws WxErrorException . + * @param env 云环境ID + * @param collectionName 集合名称 + * @throws WxErrorException 新增失败时抛出 */ void databaseCollectionAdd(String env, String collectionName) throws WxErrorException; /** - * Database collection delete. + * 删除集合。 * - * @param collectionName the collection name - * @throws WxErrorException the wx error exception + * @param collectionName 集合名称 + * @throws WxErrorException 删除失败时抛出 */ void databaseCollectionDelete(String collectionName) throws WxErrorException; /** - *
-   * 删除集合
-   *
-   * 文档地址:https://developers.weixin.qq.com/miniprogram/dev/wxcloud/reference-http-api/database
-   * /databaseCollectionDelete.html
-   * 请求地址:POST https://api.weixin.qq.com/tcb/databasecollectionadd?access_token=ACCESS_TOKEN
-   * 
+ * 删除集合。 + * 文档地址:https://developers.weixin.qq.com/miniprogram/dev/wxcloud/reference-http-api/database/databaseCollectionDelete.html * - * @param env 云环境ID - * @param collectionName 集合名称 - * @throws WxErrorException . + * @param env 云环境ID + * @param collectionName 集合名称 + * @throws WxErrorException 删除失败时抛出 */ void databaseCollectionDelete(String env, String collectionName) throws WxErrorException; /** - * Database collection get wx cloud database collection get result. + * 获取特定云环境下集合信息。 * - * @param limit the limit - * @param offset the offset - * @return the wx cloud database collection get result - * @throws WxErrorException the wx error exception + * @param limit 获取数量限制,默认值:10 + * @param offset 偏移量,默认值:0 + * @return 集合信息获取结果对象 + * @throws WxErrorException 获取失败时抛出 */ WxCloudDatabaseCollectionGetResult databaseCollectionGet(Long limit, Long offset) throws WxErrorException; /** - *
-   * 获取特定云环境下集合信息
-   *
-   * 文档地址:https://developers.weixin.qq.com/miniprogram/dev/wxcloud/reference-http-api/database/databaseCollectionGet
-   * .html
-   * 请求地址:POST https://api.weixin.qq.com/tcb/databasecollectionget?access_token=ACCESS_TOKEN
-   * 
+ * 获取特定云环境下集合信息。 + * 文档地址:https://developers.weixin.qq.com/miniprogram/dev/wxcloud/reference-http-api/database/databaseCollectionGet.html * - * @param env 云环境ID - * @param limit 获取数量限制,默认值:10 - * @param offset 偏移量,默认值:0 - * @return . wx cloud database collection get result - * @throws WxErrorException . + * @param env 云环境ID + * @param limit 获取数量限制,默认值:10 + * @param offset 偏移量,默认值:0 + * @return 集合信息获取结果对象 + * @throws WxErrorException 获取失败时抛出 */ WxCloudDatabaseCollectionGetResult databaseCollectionGet(String env, Long limit, Long offset) throws WxErrorException; /** - * 发送携带 URL Link 的短信 - * + * 发送携带 URL Link 的短信。 * 文档地址:https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/cloudbase/cloudbase.sendSmsV2.html - * @param request - * @return WxCloudSendSmsV2Result - * @throws WxErrorException + * + * @param request 短信发送请求对象 + * @return 短信发送结果对象 + * @throws WxErrorException 发送失败时抛出 */ WxCloudSendSmsV2Result sendSmsV2(WxCloudSendSmsV2Request request) throws WxErrorException; - } diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaCustomserviceWorkService.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaCustomserviceWorkService.java new file mode 100644 index 0000000000..bf119bc596 --- /dev/null +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaCustomserviceWorkService.java @@ -0,0 +1,57 @@ +package cn.binarywang.wx.miniapp.api; + +import cn.binarywang.wx.miniapp.bean.customservice.WxMaCustomserviceResult; +import me.chanjar.weixin.common.error.WxErrorException; + + +/** + *
+ *  小程序 - 微信客服 相关接口
+ *  负责处理 https://api.weixin.qq.com/customservice/work/**
+ *  文档:https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/kf-work/getKfWorkBound.html
+ *  绑定的企业ID,需和小程序主体一致。
+ *  目前仅支持绑定非个人小程序。
+ *  Created by tryking123 on 2025/8/18.
+ * 
+ * + * @author tryking123 + */ +public interface WxMaCustomserviceWorkService { + + /** + * 查询小程序的微信客服绑定情况 + */ + String GET_CUSTOMSERVICE_URL = "https://api.weixin.qq.com/customservice/work/get"; + /** + * 为小程序绑定微信客服 注:此接口绑定的企业ID需完成企业认证 + */ + String BIND_CUSTOMSERVICE_URL = "https://api.weixin.qq.com/customservice/work/bind"; + /** + * 为小程序解除绑定微信客服 + */ + String UNBIND_CUSTOMSERVICE_URL = "https://api.weixin.qq.com/customservice/work/unbind"; + + /** + * 查询小程序的微信客服绑定情况 + * + * @return 成功示例json { "errcode": 0,"entityName": "XXXXX有限公司","corpid": "wwee11111xxxxxxx","bindTime": 1694611289 } + * @throws WxErrorException + */ + WxMaCustomserviceResult getCustomservice() throws WxErrorException; + + /** + * 绑定微信客服 + * @param corpid 企业ID,获取方式参考:https://developer.work.weixin.qq.com/document/path/90665#corpid + * @return 成功示例json { "errcode": 0 } + * @throws WxErrorException + */ + WxMaCustomserviceResult bindCustomservice(String corpid) throws WxErrorException; + + /** + * 解除绑定微信客服 + * @param corpid 企业ID,获取方式参考:https://developer.work.weixin.qq.com/document/path/90665#corpid + * @return 成功示例json { "errcode": 0 } + * @throws WxErrorException + */ + WxMaCustomserviceResult unbindCustomservice(String corpid) throws WxErrorException; +} diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaExpressDeliveryReturnService.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaExpressDeliveryReturnService.java index 4254d18dfe..6d950f6801 100644 --- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaExpressDeliveryReturnService.java +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaExpressDeliveryReturnService.java @@ -5,18 +5,47 @@ import me.chanjar.weixin.common.error.WxErrorException; /** - * 退货组件 + * 微信小程序物流退货组件接口。 + * 用于处理退货单相关操作,包括新增、查询和取消退货单。 + * 文档:https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/express/express.html + * */ public interface WxMaExpressDeliveryReturnService { - /** - * 获取支持的快递公司列表 - */ + /** 新增退货单接口地址 */ String ADD_DELIVERY_RETURN_URL = "https://api.weixin.qq.com/cgi-bin/express/delivery/return/add"; + /** 获取退货单接口地址 */ String GET_DELIVERY_RETURN_URL = "https://api.weixin.qq.com/cgi-bin/express/delivery/return/get"; + /** 取消退货单接口地址 */ String UNBIND_DELIVERY_RETURN_URL = "https://api.weixin.qq.com/cgi-bin/express/delivery/return/unbind"; + /** + * 新增退货单。 + * 用于创建新的退货单,返回退货单信息。 + * + * @param wxMaExpressDeliveryReturnAddRequest 退货单新增请求对象 + * @return 退货单信息结果 + * @throws WxErrorException 新增失败时抛出 + */ WxMaExpressReturnInfoResult addDeliveryReturn(WxMaExpressDeliveryReturnAddRequest wxMaExpressDeliveryReturnAddRequest) throws WxErrorException; + + /** + * 获取退货单信息。 + * 根据退货单ID查询退货单的详细信息。 + * + * @param returnId 退货单ID + * @return 退货单信息结果 + * @throws WxErrorException 获取失败时抛出 + */ WxMaExpressReturnInfoResult getDeliveryReturn(String returnId) throws WxErrorException; + + /** + * 取消退货单。 + * 取消指定的退货单,取消后的退货单将无法继续使用。 + * + * @param returnId 退货单ID + * @return 操作结果 + * @throws WxErrorException 取消失败时抛出 + */ WxMaExpressReturnInfoResult unbindDeliveryReturn(String returnId) throws WxErrorException; } diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaOrderManagementService.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaOrderManagementService.java index d82cfd19cc..91980e9427 100644 --- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaOrderManagementService.java +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaOrderManagementService.java @@ -2,7 +2,6 @@ import cn.binarywang.wx.miniapp.bean.order.WxMaOrderManagementGetOrderDetailPath; import cn.binarywang.wx.miniapp.bean.order.WxMaOrderManagementResult; -import cn.binarywang.wx.miniapp.bean.shop.response.WxMaOrderShippingInfoBaseResponse; import me.chanjar.weixin.common.error.WxErrorException; /** diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaProductService.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaProductService.java index b629772a27..1c4bbb56c9 100644 --- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaProductService.java +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaProductService.java @@ -5,18 +5,16 @@ import cn.binarywang.wx.miniapp.bean.product.WxMinishopGetBrandResponse; import cn.binarywang.wx.miniapp.bean.product.WxMinishopGetCategoryResponse; import cn.binarywang.wx.miniapp.bean.product.WxMinishopGetFrightTemplateResponse; -import cn.binarywang.wx.miniapp.bean.product.WxMinishopOrderListResponse; import cn.binarywang.wx.miniapp.bean.product.WxMinishopResult; import cn.binarywang.wx.miniapp.bean.product.WxMinishopSku; import cn.binarywang.wx.miniapp.bean.product.WxMinishopSkuListResponse; import cn.binarywang.wx.miniapp.bean.product.WxMinishopSpu; -import cn.binarywang.wx.miniapp.bean.product.WxMinishopSpuGet; import cn.binarywang.wx.miniapp.bean.product.WxMinishopSpuGetResponse; import cn.binarywang.wx.miniapp.bean.product.WxMinishopSpuListResponse; import cn.binarywang.wx.miniapp.bean.product.WxMinishopUpdateGoodsSkuData; import cn.binarywang.wx.miniapp.bean.shop.request.WxMaShopSpuPageRequest; import cn.binarywang.wx.miniapp.bean.shop.response.WxMaShopBaseResponse; -import cn.binarywang.wx.miniapp.bean.shop.response.WxMaShopGetSpuListResponse; + import java.io.File; import java.util.List; import me.chanjar.weixin.common.bean.result.WxMinishopImageUploadResult; diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaService.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaService.java index a5446361a3..ef3a46bad9 100644 --- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaService.java +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaService.java @@ -16,7 +16,7 @@ import me.chanjar.weixin.common.util.http.RequestHttp; /** - * The interface Wx ma service. + * 微信小程序主服务接口,定义了所有小程序相关的核心操作方法。 * * @author Binary Wang */ @@ -37,563 +37,572 @@ public interface WxMaService extends WxService { String SET_DYNAMIC_DATA_URL = "https://api.weixin.qq.com/wxa/setdynamicdata"; /** - * 获取登录后的session信息. + * 获取登录后的 session 信息。 * - * @param jsCode 登录时获取的 code - * @return the wx ma jscode 2 session result - * @throws WxErrorException the wx error exception + * @param jsCode 登录时获取的 code + * @return 登录 session 结果对象 + * @throws WxErrorException 调用微信接口失败时抛出 */ WxMaJscode2SessionResult jsCode2SessionInfo(String jsCode) throws WxErrorException; /** - * 导入抽样数据 - * - *
+   * 导入抽样数据到微信后台,用于流量分配。
    * 第三方通过调用微信API,将数据写入到setdynamicdata这个API。每个Post数据包不超过5K,若数据过多可开多进(线)程并发导入数据(例如:数据量为十万量级可以开50个线程并行导数据)。
    * 文档地址:https://wsad.weixin.qq.com/wsad/zh_CN/htmledition/widget-docs-v3/html/custom/quickstart/implement/import/index.html
    * http请求方式:POST http(s)://api.weixin.qq.com/wxa/setdynamicdata?access_token=ACCESS_TOKEN
-   * 
* - * @param lifespan 数据有效时间,秒为单位,一般为86400,一天一次导入的频率 - * @param type 用于标识数据所属的服务类目 - * @param scene 1代表用于搜索的数据 - * @param data 推送到微信后台的数据列表,该数据被微信用于流量分配,注意该字段为string类型而不是object - * @throws WxErrorException . + * @param lifespan 数据有效时间(秒),如 86400 表示一天 + * @param type 数据所属服务类目标识 + * @param scene 场景值,1 代表用于搜索的数据 + * @param data 推送到微信后台的数据列表(字符串类型) + * @throws WxErrorException 调用微信接口失败时抛出 */ void setDynamicData(int lifespan, String type, int scene, String data) throws WxErrorException; /** - * - * - *
-   * 验证消息的确来自微信服务器.
+   * 校验消息是否来自微信服务器。
    * 详情请见: http://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421135319&token=&lang=zh_CN
-   * 
* - * @param timestamp the timestamp - * @param nonce the nonce - * @param signature the signature - * @return the boolean + * @param timestamp 时间戳 + * @param nonce 随机数 + * @param signature 签名字符串 + * @return 校验通过返回 true,否则返回 false */ boolean checkSignature(String timestamp, String nonce, String signature); /** - * 获取access_token, 不强制刷新access_token. + * 获取 access_token,不强制刷新。 * - * @return the access token - * @throws WxErrorException the wx error exception - * @see #getAccessToken(boolean) #getAccessToken(boolean) + * @return access_token 字符串 + * @throws WxErrorException 调用微信接口失败时抛出 + * @see #getAccessToken(boolean) */ String getAccessToken() throws WxErrorException; /** + * 获取 access_token,本方法线程安全。多线程同时刷新时只刷新一次,避免超出调用次数上限。 + * 一般无需主动调用,所有接口会自动处理 token 过期。 + * 详情见:http://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140183&token=&lang=zh_CN * - * - *
-   * 获取access_token,本方法线程安全.
-   * 且在多线程同时刷新时只刷新一次,避免超出2000次/日的调用次数上限
-   *
-   * 另:本service的所有方法都会在access_token过期是调用此方法
-   *
-   * 程序员在非必要情况下尽量不要主动调用此方法
-   *
-   * 详情请见: http://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140183&token=&lang=zh_CN
-   * 
- * - * @param forceRefresh 强制刷新 - * @return the access token - * @throws WxErrorException the wx error exception + * @param forceRefresh 是否强制刷新 + * @return access_token 字符串 + * @throws WxErrorException 调用微信接口失败时抛出 */ String getAccessToken(boolean forceRefresh) throws WxErrorException; /** - * - * - *
    * 用户支付完成后,获取该用户的 UnionId,无需用户授权。本接口支持第三方平台代理查询。
-   *
    * 注意:调用前需要用户完成支付,且在支付后的五分钟内有效。
    * 请求地址: GET https://api.weixin.qq.com/wxa/getpaidunionid?access_token=ACCESS_TOKEN&openid=OPENID
-   * 文档地址:https://developers.weixin.qq.com/miniprogram/dev/api/getPaidUnionId.html
-   * 
+ * 文档:https://developers.weixin.qq.com/miniprogram/dev/api/getPaidUnionId.html * - * @param openid 必填 支付用户唯一标识 - * @param transactionId 非必填 微信支付订单号 - * @param mchId 非必填 微信支付分配的商户号,和商户订单号配合使用 - * @param outTradeNo 非必填 微信支付商户订单号,和商户号配合使用 - * @return UnionId. paid union id - * @throws WxErrorException . + * @param openid 支付用户唯一标识(必填) + * @param transactionId 微信支付订单号(可选) + * @param mchId 微信支付分配的商户号,与商户订单号配合使用(可选) + * @param outTradeNo 微信支付商户订单号,与商户号配合使用(可选) + * @return 用户的 UnionId + * @throws WxErrorException 调用微信接口失败时抛出 */ String getPaidUnionId(String openid, String transactionId, String mchId, String outTradeNo) throws WxErrorException; /** + * 执行自定义的微信API请求。 + *
+ * Service没有实现某个API的时候,可以用这个方法,比{@link #get}和{@link #post}方法更灵活,可以自己构造RequestExecutor用来处理不同的参数和不同的返回类型。 + * 可以参考 {@link MediaUploadRequestExecutor} 的实现方法。 * - * - *
-   * Service没有实现某个API的时候,可以用这个,
-   * 比{@link #get}和{@link #post}方法更灵活,可以自己构造RequestExecutor用来处理不同的参数和不同的返回类型。
-   * 可以参考,{@link MediaUploadRequestExecutor}的实现方法
-   * 
- * - * @param . - * @param . - * @param executor 执行器 - * @param uri 接口请求地址 - * @param data 参数或请求数据 - * @return . t - * @throws WxErrorException the wx error exception + * @param 返回的数据类型 + * @param 请求参数的数据类型 + * @param executor 执行器对象 + * @param uri 接口请求地址 + * @param data 请求参数或数据 + * @return 微信接口返回的数据对象 + * @throws WxErrorException 微信接口调用异常 */ T execute(RequestExecutor executor, String uri, E data) throws WxErrorException; + /** + * 执行带有签名的微信API请求。 + * + * @param executor 签名请求执行器 + * @param uri 接口请求地址 + * @param headers 请求头信息 + * @param data 请求数据 + * @return 微信接口响应对象 + * @throws WxErrorException 微信接口调用异常 + */ WxMaApiResponse execute( - ApiSignaturePostRequestExecutor executor, + ApiSignaturePostRequestExecutor executor, String uri, Map headers, String data) throws WxErrorException; /** + * 设置微信系统繁忙时的重试等待时间(毫秒)。 * - * - *
-   * 设置当微信系统响应系统繁忙时,要等待多少 retrySleepMillis(ms) * 2^(重试次数 - 1) 再发起重试.
-   * 默认:1000ms
-   * 
- * - * @param retrySleepMillis 重试等待毫秒数 + * @param retrySleepMillis 重试等待的毫秒数,默认1000ms */ void setRetrySleepMillis(int retrySleepMillis); /** + * 设置微信系统繁忙时的最大重试次数。 * - * - *
-   * 设置当微信系统响应系统繁忙时,最大重试次数.
-   * 默认:5次
-   * 
- * - * @param maxRetryTimes 最大重试次数 + * @param maxRetryTimes 最大重试次数,默认5次 */ void setMaxRetryTimes(int maxRetryTimes); /** - * 获取WxMaConfig 对象. + * 获取当前小程序的配置信息对象。 * - * @return WxMaConfig wx ma config + * @return 当前小程序的WxMaConfig配置对象 */ WxMaConfig getWxMaConfig(); /** - * 注入 {@link WxMaConfig} 的实现. + * 注入小程序配置信息。 * - * @param maConfig config + * @param maConfig 小程序配置信息对象 */ void setWxMaConfig(WxMaConfig maConfig); /** - * Map里 加入新的 {@link WxMaConfig},适用于动态添加新的微信公众号配置. + * 动态添加新的小程序配置信息。 * - * @param miniappId 小程序标识 - * @param configStorage 新的微信配置 + * @param miniappId 小程序唯一标识 + * @param configStorage 新的小程序配置信息 */ void addConfig(String miniappId, WxMaConfig configStorage); /** - * 从 Map中 移除 {@link String miniappId} 所对应的 {@link WxMaConfig},适用于动态移除小程序配置. + * 动态移除指定小程序的配置信息。 * - * @param miniappId 对应小程序的标识 + * @param miniappId 小程序唯一标识 */ void removeConfig(String miniappId); /** - * 注入多个 {@link WxMaConfig} 的实现. 并为每个 {@link WxMaConfig} 赋予不同的 {@link String mpId} 值 随机采用一个{@link - * String mpId}进行Http初始化操作 + * 批量注入多个小程序配置信息。 * - * @param configs WxMaConfig map + * @param configs 小程序配置Map,key为小程序标识 */ void setMultiConfigs(Map configs); /** - * 注入多个 {@link WxMaConfig} 的实现. 并为每个 {@link WxMaConfig} 赋予不同的 {@link String label} 值 + * 批量注入多个小程序配置信息,并指定默认小程序。 * - * @param configs WxMaConfig map - * @param defaultMiniappId 设置一个{@link WxMaConfig} 所对应的{@link String defaultMiniappId}进行Http初始化 + * @param configs 小程序配置Map,key为小程序标识 + * @param defaultMiniappId 默认小程序标识 */ void setMultiConfigs(Map configs, String defaultMiniappId); /** - * 进行相应的公众号切换. + * 切换到指定公众号。 * - * @param mpId 公众号标识 - * @return 切换是否成功 boolean + * @param mpId 公众号标识 + * @return 切换是否成功,true为成功,false为失败 */ boolean switchover(String mpId); /** - * 进行相应的小程序切换. + * 切换到指定小程序。 * - * @param miniAppId 小程序标识 - * @return 切换成功 ,则返回当前对象,方便链式调用,否则抛出异常 + * @param miniAppId 小程序标识 + * @return 切换成功则返回当前对象,方便链式调用,否则抛出异常 */ WxMaService switchoverTo(String miniAppId); /** - * 进行相应的小程序切换. + * 切换到指定小程序,并在配置不存在时通过函数获取配置。 * - * @param miniAppId 小程序标识 - * @param func 当对应的小程序配置不存在时,允许通过函数的方式进行调用获取 - * @return 切换成功 ,则返回当前对象,方便链式调用,否则抛出异常 + * @param miniAppId 小程序标识 + * @param func 获取配置的函数 + * @return 切换成功则返回当前对象,方便链式调用,否则抛出异常 */ WxMaService switchoverTo(String miniAppId, Function func); /** - * 返回消息(客服消息和模版消息)发送接口方法实现类,以方便调用其各个接口. + * 获取消息(客服消息和模板消息)发送服务对象。 * - * @return WxMaMsgService msg service + * @return 消息服务对象WxMaMsgService */ WxMaMsgService getMsgService(); /** - * 返回素材相关接口方法的实现类对象,以方便调用其各个接口. + * 获取素材相关服务对象。 * - * @return WxMaMediaService media service + * @return 素材服务对象WxMaMediaService */ WxMaMediaService getMediaService(); /** - * 返回用户相关接口方法的实现类对象,以方便调用其各个接口. + * 获取用户相关服务对象。 * - * @return WxMaUserService user service + * @return 用户服务对象WxMaUserService */ WxMaUserService getUserService(); /** - * 返回二维码相关接口方法的实现类对象,以方便调用其各个接口. + * 获取二维码相关服务对象。 * - * @return WxMaQrcodeService qrcode service + * @return 二维码服务对象WxMaQrcodeService */ WxMaQrcodeService getQrcodeService(); /** - * 返回获取小程序scheme码实现对象,以方便调用其各个接口. + * 获取小程序scheme码服务对象。 * - * @return WxMaSchemeService wx ma scheme service + * @return scheme码服务对象WxMaSchemeService */ WxMaSchemeService getWxMaSchemeService(); /** - * 返回订阅消息配置相关接口方法的实现类对象, 以方便调用其各个接口. + * 获取订阅消息配置相关服务对象。 * - * @return WxMaSubscribeService subscribe service + * @return 订阅消息服务对象WxMaSubscribeService */ WxMaSubscribeService getSubscribeService(); /** - * 数据分析相关查询服务. + * 获取数据分析相关服务对象。 * - * @return WxMaAnalysisService analysis service + * @return 数据分析服务对象WxMaAnalysisService */ WxMaAnalysisService getAnalysisService(); /** - * 返回代码操作相关的 API. + * 获取代码操作相关服务对象。 * - * @return WxMaCodeService code service + * @return 代码服务对象WxMaCodeService */ WxMaCodeService getCodeService(); /** - * 返回jsapi操作相关的 API服务类对象. + * 获取小程序 - 微信客服。 + * + * @return 微信客服服务对象WxMaCustomserviceWorkService + */ + WxMaCustomserviceWorkService getCustomserviceWorkService(); + + /** + * 获取jsapi操作相关服务对象。 * - * @return WxMaJsapiService jsapi service + * @return jsapi服务对象WxMaJsapiService */ WxMaJsapiService getJsapiService(); /** - * 小程序修改服务器地址、成员管理 API. + * 获取小程序服务器地址、成员管理相关服务对象。 * - * @return WxMaSettingService setting service + * @return 设置服务对象WxMaSettingService */ WxMaSettingService getSettingService(); /** - * 返回分享相关查询服务. + * 获取分享相关服务对象。 * - * @return WxMaShareService share service + * @return 分享服务对象WxMaShareService */ WxMaShareService getShareService(); /** - * 返回微信运动相关接口服务对象. + * 获取微信运动相关服务对象。 * - * @return WxMaShareService run service + * @return 微信运动服务对象WxMaRunService */ WxMaRunService getRunService(); /** - * 返回小程序安全相关接口服务对象. + * 获取小程序安全相关服务对象。 * - * @return WxMaShareService sec check service + * @return 安全服务对象WxMaSecurityService */ WxMaSecurityService getSecurityService(); /** - * 返回插件相关接口服务对象. + * 获取插件相关服务对象。 * - * @return WxMaPluginService plugin service + * @return 插件服务对象WxMaPluginService */ WxMaPluginService getPluginService(); - /** 初始化http请求对象. */ + /** + * 初始化http请求对象。 + */ void initHttp(); /** - * 请求http请求相关信息. + * 获取http请求相关信息。 * - * @return . request http + * @return http请求对象RequestHttp */ - RequestHttp getRequestHttp(); + RequestHttp getRequestHttp(); /** - * 获取物流助手接口服务对象 + * 获取物流助手接口服务对象。 * - * @return . express service + * @return 物流助手服务对象WxMaExpressService */ WxMaExpressService getExpressService(); /** - * 获取云开发接口服务对象 + * 获取云开发接口服务对象。 * - * @return . cloud service + * @return 云开发服务对象WxMaCloudService */ WxMaCloudService getCloudService(); /** - * 获取服务端网络接口服务对象 + * 获取服务端网络接口服务对象。 * - * @return 。internet service + * @return 网络服务对象WxMaInternetService */ WxMaInternetService getInternetService(); /** - * 获取直播接口服务对象 + * 获取直播接口服务对象。 * - * @return . live service + * @return 直播服务对象WxMaLiveService */ WxMaLiveService getLiveService(); /** - * 获取直播间商品服务对象 + * 获取直播间商品服务对象。 * - * @return . live goods service + * @return 直播商品服务对象WxMaLiveGoodsService */ WxMaLiveGoodsService getLiveGoodsService(); /** - * 获取直播成员管理接口服务对象 + * 获取直播成员管理接口服务对象。 * - * @return . live service + * @return 直播成员服务对象WxMaLiveMemberService */ WxMaLiveMemberService getLiveMemberService(); /** - * 获取ocr实现接口服务对象 + * 获取OCR实现接口服务对象。 * - * @return 。 + * @return OCR服务对象WxOcrService */ WxOcrService getOcrService(); /** - * 返回图像处理接口的实现类对象,以方便调用其各个接口. + * 获取图像处理接口服务对象。 * - * @return WxImgProcService img proc service + * @return 图像处理服务对象WxImgProcService */ WxImgProcService getImgProcService(); /** - * 返回小程序交易组件-售后服务接口 + * 获取小程序交易组件-售后服务接口服务对象。 * - * @return + * @return 售后服务对象WxMaShopAfterSaleService */ WxMaShopAfterSaleService getShopAfterSaleService(); /** - * 返回小程序交易组件-物流服务接口 + * 获取小程序交易组件-物流服务接口服务对象。 * - * @return + * @return 物流服务对象WxMaShopDeliveryService */ WxMaShopDeliveryService getShopDeliveryService(); /** - * 返回小程序交易组件-订单服务接口 + * 获取小程序交易组件-订单服务接口服务对象。 * - * @return + * @return 订单服务对象WxMaShopOrderService */ WxMaShopOrderService getShopOrderService(); /** - * 返回小程序交易组件-spu商品服务接口 + * 获取小程序交易组件-spu商品服务接口服务对象。 * - * @return + * @return spu商品服务对象WxMaShopSpuService */ WxMaShopSpuService getShopSpuService(); /** - * 返回小程序交易组件-接入申请接口 + * 获取小程序交易组件-接入申请接口服务对象。 * - * @return + * @return 接入申请服务对象WxMaShopRegisterService */ WxMaShopRegisterService getShopRegisterService(); /** - * 返回小程序交易组件-商户入驻接口 + * 获取小程序交易组件-商户入驻接口服务对象。 * - * @return + * @return 商户入驻服务对象WxMaShopAccountService */ WxMaShopAccountService getShopAccountService(); /** - * 小程序交易组件-接入商品前必需接口-类目相关 + * 获取小程序交易组件-类目相关接口服务对象。 * - * @return + * @return 类目服务对象WxMaShopCatService */ WxMaShopCatService getShopCatService(); /** - * 小程序交易组件-接入商品前必需接口-上传图片 + * 获取小程序交易组件-上传图片接口服务对象。 * - * @return + * @return 图片服务对象WxMaShopImgService */ WxMaShopImgService getShopImgService(); /** - * 小程序交易组件-接入商品前必需接口-审核相关接口 + * 获取小程序交易组件-审核相关接口服务对象。 * - * @return + * @return 审核服务对象WxMaShopAuditService */ WxMaShopAuditService getShopAuditService(); /** - * 获取小程序Link服务接口 + * 获取小程序Link服务接口服务对象。 * - * @return + * @return Link服务对象WxMaLinkService */ WxMaLinkService getLinkService(); /** - * 获取电子发票报销方服务接口 + * 获取电子发票报销方服务接口服务对象。 * - * @return + * @return 电子发票报销方服务对象WxMaReimburseInvoiceService */ WxMaReimburseInvoiceService getReimburseInvoiceService(); /** - * 返回设备订阅消息相关接口服务对象 + * 获取设备订阅消息相关接口服务对象。 * - * @return WxMaDeviceSubscribeService plugin service + * @return 设备订阅消息服务对象WxMaDeviceSubscribeService */ WxMaDeviceSubscribeService getDeviceSubscribeService(); /** - * 返回小程序广告接入相关接口服务对象 + * 获取小程序广告接入相关接口服务对象。 * - * @return WxMaDeviceSubscribeService plugin service + * @return 广告服务对象WxMaMarketingService */ WxMaMarketingService getMarketingService(); /** - * 返回微信小程序即时配送服务接口. + * 获取微信小程序即时配送服务接口服务对象。 * - * @return WxMaImmediateDeliveryService + * @return 即时配送服务对象WxMaImmediateDeliveryService */ WxMaImmediateDeliveryService getWxMaImmediateDeliveryService(); /** - * 分享人接口 + * 获取小程序分享人接口服务对象。 * - * @return WxMaShopSharerService + * @return 分享人服务对象WxMaShopSharerService */ WxMaShopSharerService getShopSharerService(); /** - * 标准交易组件接口 + * 获取标准交易组件接口服务对象。 * - * @return WxMaProductService + * @return 标准交易组件服务对象WxMaProductService */ WxMaProductService getProductService(); /** - * 小商店-标准交易组件-订单服务 + * 获取小商店-标准交易组件-订单服务对象。 * - * @return getProductOrderService + * @return 订单服务对象WxMaProductOrderService */ WxMaProductOrderService getProductOrderService(); /** - * 小商店-标准交易组件-优惠券 + * 获取小商店-标准交易组件-优惠券服务对象。 * - * @return getWxMaShopCouponService + * @return 优惠券服务对象WxMaShopCouponService */ WxMaShopCouponService getWxMaShopCouponService(); /** - * 小程序支付管理-订单支付 + * 获取小程序支付管理-订单支付服务对象。 * - * @return getWxMaShopPayService + * @return 订单支付服务对象WxMaShopPayService */ WxMaShopPayService getWxMaShopPayService(); /** - * 小程序发货信息管理服务 + * 获取小程序发货信息管理服务对象。 * - * @return getWxMaOrderShippingService + * @return 发货信息管理服务对象WxMaOrderShippingService */ WxMaOrderShippingService getWxMaOrderShippingService(); + /** - * 小程序订单管理服务 + * 获取小程序订单管理服务对象。 * - * @return WxMaOrderManagementService + * @return 订单管理服务对象WxMaOrderManagementService */ WxMaOrderManagementService getWxMaOrderManagementService(); /** - * 小程序openApi管理 + * 获取小程序openApi管理服务对象。 * - * @return getWxMaOpenApiService + * @return openApi管理服务对象WxMaOpenApiService */ WxMaOpenApiService getWxMaOpenApiService(); /** - * 小程序短剧管理 + * 获取小程序短剧管理服务对象。 * - * @return getWxMaVodService + * @return 短剧管理服务对象WxMaVodService */ WxMaVodService getWxMaVodService(); /** - * 小程序虚拟支付 + * 获取小程序虚拟支付服务对象。 * - * @return getWxMaXPayService + * @return 虚拟支付服务对象WxMaXPayService */ WxMaXPayService getWxMaXPayService(); + /** + * 获取小程序物流退货服务对象。 + * + * @return 物流退货服务对象WxMaExpressDeliveryReturnService + */ WxMaExpressDeliveryReturnService getWxMaExpressDeliveryReturnService(); /** - * 小程序推广员 + * 获取小程序推广员服务对象。 * - * @return WxMaPromotionService + * @return 推广员服务对象WxMaPromotionService */ WxMaPromotionService getWxMaPromotionService(); + /** + * 以签名方式POST数据到指定URL。 + * + * @param url 请求地址 + * @param obj 请求对象 + * @return 微信接口响应字符串 + * @throws WxErrorException 微信接口调用异常 + */ String postWithSignature(String url, Object obj) throws WxErrorException; + /** + * 以签名方式POST数据到指定URL。 + * + * @param url 请求地址 + * @param jsonObject 请求的Json对象 + * @return 微信接口响应字符串 + * @throws WxErrorException 微信接口调用异常 + */ String postWithSignature(String url, JsonObject jsonObject) throws WxErrorException; /** - * 微信物流服务 -- 同城配送 - * https://developers.weixin.qq.com/miniprogram/dev/platform-capabilities/industry/express/business/intracity_service.html + * 获取微信物流服务--同城配送服务对象。 + *
+ * 文档:https://developers.weixin.qq.com/miniprogram/dev/platform-capabilities/industry/express/business/intracity_service.html + * + * @return 同城配送服务对象WxMaIntracityService */ WxMaIntracityService getIntracityService(); } diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaVodService.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaVodService.java index 547d280962..217a699c5a 100644 --- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaVodService.java +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaVodService.java @@ -6,39 +6,181 @@ import java.io.File; import java.util.List; +/** + * 小程序短剧管理服务接口。 + * 提供短剧视频上传、管理、审核、播放等相关功能。 + * 文档:https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/vod.html + * + */ public interface WxMaVodService { + + /** + * 获取媒体列表。 + * 分页获取已上传的媒体文件列表。 + * + * @param request 获取媒体列表请求对象 + * @return 媒体信息列表 + * @throws WxErrorException 获取失败时抛出 + */ List listMedia(WxMaVodListMediaRequest request) throws WxErrorException; + /** + * 获取剧集列表。 + * 分页获取已创建的剧集列表。 + * + * @param request 获取剧集列表请求对象 + * @return 剧集信息列表 + * @throws WxErrorException 获取失败时抛出 + */ List listDrama(WxMaVodListDramaRequest request) throws WxErrorException; + /** + * 获取媒体播放链接。 + * 获取指定媒体文件的播放地址和相关信息。 + * + * @param request 获取媒体播放链接请求对象 + * @return 媒体播放信息对象 + * @throws WxErrorException 获取失败时抛出 + */ WxMaVodMediaPlaybackInfo getMediaLink(WxMaVodGetMediaLinkRequest request) throws WxErrorException; + /** + * 获取媒体详情。 + * 获取指定媒体文件的详细信息。 + * + * @param request 获取媒体详情请求对象 + * @return 媒体信息对象 + * @throws WxErrorException 获取失败时抛出 + */ WxMaVodMediaInfo getMedia(WxMaVodGetMediaRequest request) throws WxErrorException; + /** + * 删除媒体文件。 + * 删除指定的媒体文件,删除后无法恢复。 + * + * @param request 删除媒体请求对象 + * @return 删除是否成功 + * @throws WxErrorException 删除失败时抛出 + */ boolean deleteMedia(WxMaVodDeleteMediaRequest request) throws WxErrorException; + /** + * 获取剧集详情。 + * 获取指定剧集的详细信息。 + * + * @param request 获取剧集详情请求对象 + * @return 剧集信息对象 + * @throws WxErrorException 获取失败时抛出 + */ WxMaVodDramaInfo getDrama(WxMaVodGetDramaRequest request) throws WxErrorException; + /** + * 审核剧集。 + * 提交剧集进行内容审核。 + * + * @param request 审核剧集请求对象 + * @return 审核任务ID + * @throws WxErrorException 审核提交失败时抛出 + */ Integer auditDrama(WxMaVodAuditDramaRequest request) throws WxErrorException; + /** + * 获取CDN用量数据。 + * 查询指定时间段内的CDN流量使用情况。 + * + * @param request 获取CDN用量请求对象 + * @return CDN用量数据响应对象 + * @throws WxErrorException 获取失败时抛出 + */ WxMaVodGetCdnUsageResponse getCdnUsageData(WxMaVodGetCdnUsageRequest request) throws WxErrorException; + /** + * 获取CDN日志。 + * 获取指定时间段内的CDN访问日志。 + * + * @param request 获取CDN日志请求对象 + * @return CDN日志响应对象 + * @throws WxErrorException 获取失败时抛出 + */ WxMaVodGetCdnLogResponse getCdnLogs(WxMaVodGetCdnLogRequest request) throws WxErrorException; - + /** + * 拉取上传。 + * 通过URL拉取视频文件到平台进行上传。 + * + * @param request 拉取上传请求对象 + * @return 拉取上传响应对象 + * @throws WxErrorException 拉取失败时抛出 + */ WxMaVodPullUploadResponse pullUpload(WxMaVodPullUploadRequest request) throws WxErrorException; + /** + * 获取任务状态。 + * 查询异步任务的执行状态和结果。 + * + * @param request 获取任务状态请求对象 + * @return 任务状态响应对象 + * @throws WxErrorException 获取失败时抛出 + */ WxMaVodGetTaskResponse getTask(WxMaVodGetTaskRequest request) throws WxErrorException; - + /** + * 单文件上传(简化版)。 + * 直接上传单个视频文件到平台。 + * + * @param file 要上传的文件 + * @param mediaName 媒体文件名称 + * @param mediaType 媒体文件类型 + * @return 单文件上传结果 + * @throws WxErrorException 上传失败时抛出 + */ WxMaVodSingleFileUploadResult uploadSingleFile(File file, String mediaName, String mediaType) throws WxErrorException; + /** + * 单文件上传(完整版)。 + * 上传视频文件和封面图片到平台。 + * + * @param file 要上传的视频文件 + * @param mediaName 媒体文件名称 + * @param mediaType 媒体文件类型 + * @param coverType 封面图片类型 + * @param coverData 封面图片文件 + * @param sourceContext 来源上下文信息 + * @return 单文件上传结果 + * @throws WxErrorException 上传失败时抛出 + */ WxMaVodSingleFileUploadResult uploadSingleFile(File file, String mediaName, String mediaType, String coverType, File coverData, String sourceContext) throws WxErrorException; + /** + * 申请上传。 + * 申请分片上传的上传凭证和上传地址。 + * + * @param request 申请上传请求对象 + * @return 申请上传响应对象 + * @throws WxErrorException 申请失败时抛出 + */ WxMaVodApplyUploadResponse applyUpload(WxMaVodApplyUploadRequest request) throws WxErrorException; + /** + * 确认上传。 + * 确认分片上传完成,合并所有分片文件。 + * + * @param request 确认上传请求对象 + * @return 确认上传响应对象 + * @throws WxErrorException 确认失败时抛出 + */ WxMaVodCommitUploadResponse commitUpload(WxMaVodCommitUploadRequest request) throws WxErrorException; + /** + * 上传分片。 + * 上传文件的一个分片。 + * + * @param file 分片文件 + * @param uploadId 上传ID + * @param partNumber 分片编号 + * @param resourceType 资源类型 + * @return 分片上传结果 + * @throws WxErrorException 上传失败时抛出 + */ WxMaVodUploadPartResult uploadPart(File file, String uploadId, Integer partNumber, Integer resourceType) throws WxErrorException; - } diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaXPayService.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaXPayService.java index a099cd6dd7..a633c93de6 100644 --- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaXPayService.java +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaXPayService.java @@ -1,37 +1,305 @@ package cn.binarywang.wx.miniapp.api; +import cn.binarywang.wx.miniapp.bean.WxMaBaseResponse; import cn.binarywang.wx.miniapp.bean.xpay.*; import me.chanjar.weixin.common.error.WxErrorException; +/** + * 小程序虚拟支付相关接口。 + * 文档:https://developers.weixin.qq.com/miniprogram/dev/platform-capabilities/industry/virtual-payment.html + * + */ public interface WxMaXPayService { + /** + * 查询用户虚拟币余额。 + * + * @param request 查询用户余额请求对象 + * @param sigParams 签名参数对象 + * @return 用户余额查询结果 + * @throws WxErrorException 查询失败时抛出 + */ WxMaXPayQueryUserBalanceResponse queryUserBalance(WxMaXPayQueryUserBalanceRequest request, WxMaXPaySigParams sigParams) throws WxErrorException; + /** + * 虚拟币充值下单。 + * + * @param request 虚拟币充值请求对象 + * @param sigParams 签名参数对象 + * @return 虚拟币充值结果 + * @throws WxErrorException 充值失败时抛出 + */ WxMaXPayCurrencyPayResponse currencyPay(WxMaXPayCurrencyPayRequest request, WxMaXPaySigParams sigParams) throws WxErrorException; + /** + * 查询订单信息。 + * + * @param request 查询订单请求对象 + * @param sigParams 签名参数对象 + * @return 订单查询结果 + * @throws WxErrorException 查询失败时抛出 + */ WxMaXPayQueryOrderResponse queryOrder(WxMaXPayQueryOrderRequest request, WxMaXPaySigParams sigParams) throws WxErrorException; + /** + * 取消虚拟币充值订单。 + * + * @param request 取消充值订单请求对象 + * @param sigParams 签名参数对象 + * @return 取消充值订单结果 + * @throws WxErrorException 取消失败时抛出 + */ WxMaXPayCancelCurrencyPayResponse cancelCurrencyPay(WxMaXPayCancelCurrencyPayRequest request, WxMaXPaySigParams sigParams) throws WxErrorException; + /** + * 通知发货。 + * + * @param request 通知发货请求对象 + * @param sigParams 签名参数对象 + * @return 通知发货是否成功 + * @throws WxErrorException 通知失败时抛出 + */ boolean notifyProvideGoods(WxMaXPayNotifyProvideGoodsRequest request, WxMaXPaySigParams sigParams) throws WxErrorException; + /** + * 赠送虚拟币。 + * + * @param request 赠送虚拟币请求对象 + * @param sigParams 签名参数对象 + * @return 赠送虚拟币结果 + * @throws WxErrorException 赠送失败时抛出 + */ WxMaXPayPresentCurrencyResponse presentCurrency(WxMaXPayPresentCurrencyRequest request, WxMaXPaySigParams sigParams) throws WxErrorException; - + /** + * 下载对账单。 + * + * @param request 下载对账单请求对象 + * @param sigParams 签名参数对象 + * @return 对账单下载结果 + * @throws WxErrorException 下载失败时抛出 + */ WxMaXPayDownloadBillResponse downloadBill(WxMaXPayDownloadBillRequest request, WxMaXPaySigParams sigParams) throws WxErrorException; + /** + * 退款申请。 + * + * @param request 退款申请请求对象 + * @param sigParams 签名参数对象 + * @return 退款申请结果 + * @throws WxErrorException 退款失败时抛出 + */ WxMaXPayRefundOrderResponse refundOrder(WxMaXPayRefundOrderRequest request, WxMaXPaySigParams sigParams) throws WxErrorException; + /** + * 创建提现订单。 + * + * @param request 创建提现订单请求对象 + * @param sigParams 签名参数对象 + * @return 创建提现订单结果 + * @throws WxErrorException 创建失败时抛出 + */ WxMaXPayCreateWithdrawOrderResponse createWithdrawOrder(WxMaXPayCreateWithdrawOrderRequest request, WxMaXPaySigParams sigParams) throws WxErrorException; + /** + * 查询提现订单。 + * + * @param request 查询提现订单请求对象 + * @param sigParams 签名参数对象 + * @return 提现订单查询结果 + * @throws WxErrorException 查询失败时抛出 + */ WxMaXPayQueryWithdrawOrderResponse queryWithdrawOrder(WxMaXPayQueryWithdrawOrderRequest request, WxMaXPaySigParams sigParams) throws WxErrorException; + /** + * 启动道具上传。 + * + * @param request 启动道具上传请求对象 + * @param sigParams 签名参数对象 + * @return 启动道具上传是否成功 + * @throws WxErrorException 启动失败时抛出 + */ boolean startUploadGoods(WxMaXPayStartUploadGoodsRequest request, WxMaXPaySigParams sigParams) throws WxErrorException; + /** + * 查询道具上传状态。 + * + * @param request 查询道具上传状态请求对象 + * @param sigParams 签名参数对象 + * @return 道具上传状态查询结果 + * @throws WxErrorException 查询失败时抛出 + */ WxMaXPayQueryUploadGoodsResponse queryUploadGoods(WxMaXPayQueryUploadGoodsRequest request, WxMaXPaySigParams sigParams) throws WxErrorException; + /** + * 启动道具发布。 + * + * @param request 启动道具发布请求对象 + * @param sigParams 签名参数对象 + * @return 启动道具发布是否成功 + * @throws WxErrorException 启动失败时抛出 + */ boolean startPublishGoods(WxMaXPayStartPublishGoodsRequest request, WxMaXPaySigParams sigParams) throws WxErrorException; + /** + * 查询道具发布状态。 + * + * @param request 查询道具发布状态请求对象 + * @param sigParams 签名参数对象 + * @return 道具发布状态查询结果 + * @throws WxErrorException 查询失败时抛出 + */ WxMaXPayQueryPublishGoodsResponse queryPublishGoods(WxMaXPayQueryPublishGoodsRequest request, WxMaXPaySigParams sigParams) throws WxErrorException; + /** + * 查询商家账户里的可提现余额。 + * + * @param request 查询商家账户里的可提现余额请求对象 + * @param sigParams 签名参数对象 + * @return 商家账户里的可提现余额查询结果 + * @throws WxErrorException 查询失败时抛出 + */ + WxMaXPayQueryBizBalanceResponse queryBizBalance(WxMaXPayQueryBizBalanceRequest request, WxMaXPaySigParams sigParams) throws WxErrorException; + + /** + * 查询广告金充值账户。 + * + * @param request 查询广告金充值账户请求对象 + * @param sigParams 签名参数对象 + * @return 广告金充值账户查询结果 + * @throws WxErrorException 查询失败时抛出 + */ + WxMaXPayQueryTransferAccountResponse queryTransferAccount(WxMaXPayQueryTransferAccountRequest request, WxMaXPaySigParams sigParams) throws WxErrorException; + + /** + * 查询广告金发放记录。 + * + * @param request 查询广告金发放记录请求对象 + * @param sigParams 签名参数对象 + * @return 查询广告金发放记录结果 + * @throws WxErrorException 查询失败时抛出 + */ + WxMaXPayQueryAdverFundsResponse queryAdverFunds(WxMaXPayQueryAdverFundsRequest request, WxMaXPaySigParams sigParams) throws WxErrorException; + + /** + * 充值广告金。 + * + * @param request 充值广告金请求对象 + * @param sigParams 签名参数对象 + * @return 充值广告金结果 + * @throws WxErrorException 查询失败时抛出 + */ + WxMaXPayCreateFundsBillResponse createFundsBill(WxMaXPayCreateFundsBillRequest request, WxMaXPaySigParams sigParams) throws WxErrorException; + + /** + * 绑定广告金充值账户。 + * + * @param request 绑定广告金充值账户请求对象 + * @param sigParams 签名参数对象 + * @return 绑定广告金充值账户结果 + * @throws WxErrorException 查询失败时抛出 + */ + WxMaBaseResponse bindTransferAccount(WxMaXPayBindTransferAccountRequest request, WxMaXPaySigParams sigParams) throws WxErrorException; + + /** + * 查询广告金充值记录。 + * + * @param request 查询广告金充值记录请求对象 + * @param sigParams 签名参数对象 + * @return 查询广告金充值记录结果 + * @throws WxErrorException 查询失败时抛出 + */ + WxMaXPayQueryFundsBillResponse queryFundsBill(WxMaXPayQueryFundsBillRequest request, WxMaXPaySigParams sigParams) throws WxErrorException; + + /** + * 查询广告金回收记录。 + * + * @param request 查询广告金回收记录请求对象 + * @param sigParams 签名参数对象 + * @return 查询广告金回收记录结果 + * @throws WxErrorException 查询失败时抛出 + */ + WxMaXPayQueryRecoverBillResponse queryRecoverBill(WxMaXPayQueryRecoverBillRequest request, WxMaXPaySigParams sigParams) throws WxErrorException; + + + /** + * 获取投诉列表。 + * + * @param request 获取投诉列表请求对象 + * @param sigParams 签名参数对象 + * @return 获取投诉列表结果 + * @throws WxErrorException 查询失败时抛出 + */ + WxMaXPayGetComplaintListResponse getComplaintList(WxMaXPayGetComplaintListRequest request, WxMaXPaySigParams sigParams) throws WxErrorException; + + /** + * 获取投诉详情。 + * + * @param request 获取投诉详情请求对象 + * @param sigParams 签名参数对象 + * @return 获取投诉详情结果 + * @throws WxErrorException 查询失败时抛出 + */ + WxMaXPayGetComplaintDetailResponse getComplaintDetail(WxMaXPayGetComplaintDetailRequest request, WxMaXPaySigParams sigParams) throws WxErrorException; + + /** + * 获取协商历史。 + * + * @param request 获取协商历史请求对象 + * @param sigParams 签名参数对象 + * @return 获取协商历史结果 + * @throws WxErrorException 查询失败时抛出 + */ + WxMaXPayGetNegotiationHistoryResponse getNegotiationHistory(WxMaXPayGetNegotiationHistoryRequest request, WxMaXPaySigParams sigParams) throws WxErrorException; + + /** + * 回复用户。 + * + * @param request 回复用户请求对象 + * @param sigParams 签名参数对象 + * @return 回复用户结果 + * @throws WxErrorException 查询失败时抛出 + */ + WxMaBaseResponse responseComplaint(WxMaXPayResponseComplaintRequest request, WxMaXPaySigParams sigParams) throws WxErrorException; + + /** + * 完成投诉处理。 + * + * @param request 完成投诉处理请求对象 + * @param sigParams 签名参数对象 + * @return 完成投诉处理结果 + * @throws WxErrorException 查询失败时抛出 + */ + WxMaBaseResponse completeComplaint(WxMaXPayCompleteComplaintRequest request, WxMaXPaySigParams sigParams) throws WxErrorException; + + /** + * 上传媒体文件(如图片,凭证等)。 + * + * @param request 上传媒体文件(如图片,凭证等)请求对象 + * @param sigParams 签名参数对象 + * @return 上传媒体文件(如图片,凭证等)结果 + * @throws WxErrorException 查询失败时抛出 + */ + WxMaXPayUploadVpFileResponse uploadVpFile(WxMaXPayUploadVpFileRequest request, WxMaXPaySigParams sigParams) throws WxErrorException; + + /** + * 获取微信支付反馈投诉图片的签名头部。 + * + * @param request 获取微信支付反馈投诉图片的签名头部请求对象 + * @param sigParams 签名参数对象 + * @return 获取微信支付反馈投诉图片的签名头部结果 + * @throws WxErrorException 查询失败时抛出 + */ + WxMaXPayGetUploadFileSignResponse getUploadFileSign(WxMaXPayGetUploadFileSignRequest request, WxMaXPaySigParams sigParams) throws WxErrorException; + + /** + * 下载广告金对应的商户订单信息。 + * + * @param request 下载广告金对应的商户订单信息请求对象 + * @param sigParams 签名参数对象 + * @return 下载广告金对应的商户订单信息结果 + * @throws WxErrorException 查询失败时抛出 + */ + WxMaXPayDownloadAdverfundsOrderResponse downloadAdverfundsOrder(WxMaXPayDownloadAdverfundsOrderRequest request, WxMaXPaySigParams sigParams) throws WxErrorException; + } diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/BaseWxMaServiceImpl.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/BaseWxMaServiceImpl.java index aa7b061fb1..ec33dede0c 100644 --- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/BaseWxMaServiceImpl.java +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/BaseWxMaServiceImpl.java @@ -38,6 +38,7 @@ import me.chanjar.weixin.common.enums.WxType; import me.chanjar.weixin.common.error.WxError; import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.common.error.WxMaErrorMsgEnum; import me.chanjar.weixin.common.error.WxRuntimeException; import me.chanjar.weixin.common.executor.CommonUploadRequestExecutor; import me.chanjar.weixin.common.service.WxImgProcService; @@ -111,6 +112,7 @@ public abstract class BaseWxMaServiceImpl implements WxMaService, RequestH private final WxMaSchemeService schemeService = new WxMaSchemeServiceImpl(this); private final WxMaAnalysisService analysisService = new WxMaAnalysisServiceImpl(this); private final WxMaCodeService codeService = new WxMaCodeServiceImpl(this); + private final WxMaCustomserviceWorkService customserviceWorkService = new WxMaCustomserviceWorkServiceImpl(this); private final WxMaInternetService internetService = new WxMaInternetServiceImpl(this); private final WxMaSettingService settingService = new WxMaSettingServiceImpl(this); private final WxMaJsapiService jsapiService = new WxMaJsapiServiceImpl(this); @@ -169,7 +171,7 @@ public abstract class BaseWxMaServiceImpl implements WxMaService, RequestH private int maxRetryTimes = 5; @Override - public RequestHttp getRequestHttp() { + public RequestHttp getRequestHttp() { return this; } @@ -232,7 +234,7 @@ public boolean checkSignature(String timestamp, String nonce, String signature) try { return SHA1.gen(this.getWxMaConfig().getToken(), timestamp, nonce).equals(signature); } catch (Exception e) { - log.error("Checking signature failed, and the reason is :" + e.getMessage()); + log.error("Checking signature failed, and the reason is :{}", e.getMessage()); return false; } } @@ -297,7 +299,7 @@ public String get(String url, String queryParam) throws WxErrorException { private boolean isApiSignatureRequired(String url) { return this.getWxMaConfig().getApiSignatureAesKey() != null - && Arrays.stream(urlPathSupportApiSignature).anyMatch(part -> url.contains(part)); + && Arrays.stream(urlPathSupportApiSignature).anyMatch(url::contains); } @Override @@ -361,7 +363,7 @@ public R execute(RequestExecutor executor, String uri, T data) @Override public WxMaApiResponse execute( - ApiSignaturePostRequestExecutor executor, + ApiSignaturePostRequestExecutor executor, String uri, Map headers, String data) @@ -458,7 +460,12 @@ private R executeInternal( } if (error.getErrorCode() != 0) { - log.warn("\n【请求地址】: {}\n【请求参数】:{}\n【错误信息】:{}", uriWithAccessToken, dataForLog, error); + if (error.getErrorCode() == WxMaErrorMsgEnum.CODE_43101.getCode()) { + // 43101 日志太多, 打印为debug, 其他情况打印为warn + log.debug("\n【请求地址】: {}\n【请求参数】:{}\n【错误信息】:{}", uriWithAccessToken, dataForLog, error); + } else { + log.warn("\n【请求地址】: {}\n【请求参数】:{}\n【错误信息】:{}", uriWithAccessToken, dataForLog, error); + } throw new WxErrorException(error, e); } return null; @@ -645,6 +652,11 @@ public WxMaCodeService getCodeService() { return this.codeService; } + @Override + public WxMaCustomserviceWorkService getCustomserviceWorkService() { + return this.customserviceWorkService; + } + @Override public WxMaJsapiService getJsapiService() { return this.jsapiService; diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaCloudServiceImpl.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaCloudServiceImpl.java index 3e16814479..45c7339bc9 100644 --- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaCloudServiceImpl.java +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaCloudServiceImpl.java @@ -55,7 +55,7 @@ public String invokeCloudFunction(String env, String name, String body) throws W } @Override - public List add(String collection, List list) throws WxErrorException { + public List add(String collection, List list) throws WxErrorException { String jsonData = WxMaGsonBuilder.create().toJson(list); String query = blankJoiner.join( "db.collection('", collection, "')", diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaCustomserviceWorkServiceImpl.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaCustomserviceWorkServiceImpl.java new file mode 100644 index 0000000000..0a30f86df3 --- /dev/null +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaCustomserviceWorkServiceImpl.java @@ -0,0 +1,51 @@ +package cn.binarywang.wx.miniapp.api.impl; + +import cn.binarywang.wx.miniapp.api.WxMaCustomserviceWorkService; +import cn.binarywang.wx.miniapp.api.WxMaService; +import cn.binarywang.wx.miniapp.bean.customservice.WxMaCustomserviceResult; +import com.google.gson.JsonObject; +import lombok.RequiredArgsConstructor; +import me.chanjar.weixin.common.error.WxErrorException; + + + +/** + *
+ *  小程序 - 微信客服 相关接口
+ *  负责处理 https://api.weixin.qq.com/customservice/work/**
+ *  文档:https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/kf-work/getKfWorkBound.html
+ *  绑定的企业ID,需和小程序主体一致。
+ *  目前仅支持绑定非个人小程序。
+ *  Created by tryking123 on 2025/8/18.
+ * 
+ * + * @author tryking123 + */ +@RequiredArgsConstructor +public class WxMaCustomserviceWorkServiceImpl implements WxMaCustomserviceWorkService { + private static final String CORPID = "corpid"; + + private final WxMaService service; + + @Override + public WxMaCustomserviceResult getCustomservice() throws WxErrorException { + String responseContent = this.service.get(GET_CUSTOMSERVICE_URL, null); + return WxMaCustomserviceResult.fromJson(responseContent); + } + + @Override + public WxMaCustomserviceResult bindCustomservice(String corpid) throws WxErrorException { + JsonObject paramJson = new JsonObject(); + paramJson.addProperty(CORPID, corpid); + String response = this.service.post(BIND_CUSTOMSERVICE_URL, paramJson); + return WxMaCustomserviceResult.fromJson(response); + } + + @Override + public WxMaCustomserviceResult unbindCustomservice(String corpid) throws WxErrorException { + JsonObject paramJson = new JsonObject(); + paramJson.addProperty(CORPID, corpid); + String response = this.service.post(UNBIND_CUSTOMSERVICE_URL, paramJson); + return WxMaCustomserviceResult.fromJson(response); + } +} diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaDeviceSubscribeServiceImpl.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaDeviceSubscribeServiceImpl.java index 0943a1feeb..7f8dce1df8 100644 --- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaDeviceSubscribeServiceImpl.java +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaDeviceSubscribeServiceImpl.java @@ -4,7 +4,6 @@ import cn.binarywang.wx.miniapp.api.WxMaService; import cn.binarywang.wx.miniapp.bean.device.WxMaDeviceSubscribeMessageRequest; import cn.binarywang.wx.miniapp.bean.device.WxMaDeviceTicketRequest; -import cn.binarywang.wx.miniapp.constant.WxMaConstants; import com.google.gson.JsonObject; import lombok.RequiredArgsConstructor; import me.chanjar.weixin.common.api.WxConsts; diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaImgProcServiceImpl.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaImgProcServiceImpl.java index 03f46fb31e..bfa03bd501 100644 --- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaImgProcServiceImpl.java +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaImgProcServiceImpl.java @@ -82,7 +82,7 @@ public WxImgProcAiCropResult aiCrop(String imgUrl, String ratios) throws WxError ratios = ""; } - final String result = this.service.get(String.format(AI_CROP, imgUrl, ratios), null); + final String result = this.service.post(String.format(AI_CROP, imgUrl, ratios), ""); return WxImgProcAiCropResult.fromJson(result); } diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaImmediateDeliveryServiceImpl.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaImmediateDeliveryServiceImpl.java index 05e8f2e0a7..910eb19d22 100644 --- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaImmediateDeliveryServiceImpl.java +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaImmediateDeliveryServiceImpl.java @@ -5,7 +5,6 @@ import cn.binarywang.wx.miniapp.bean.WxMaBaseResponse; import cn.binarywang.wx.miniapp.bean.delivery.*; import cn.binarywang.wx.miniapp.bean.delivery.base.WxMaDeliveryBaseResponse; -import cn.binarywang.wx.miniapp.constant.WxMaApiUrlConstants; import cn.binarywang.wx.miniapp.constant.WxMaApiUrlConstants.InstantDelivery; import cn.binarywang.wx.miniapp.json.WxMaGsonBuilder; import com.google.gson.JsonElement; @@ -207,7 +206,7 @@ public GetDeliveryListResponse getDeliveryList() throws WxErrorException { @Override public WxMaBaseResponse updateWaybillGoods(UpdateWaybillGoodsRequest request) throws WxErrorException { - String responseContent = this.wxMaService.post(InstantDelivery.GET_DELIVERY_LIST_URL,request); + String responseContent = this.wxMaService.post(InstantDelivery.UPDATE_WAYBILL_GOODS_URL,request); WxMaBaseResponse response = WxMaGsonBuilder.create().fromJson(responseContent, WxMaBaseResponse.class); if (response.getErrcode() == -1) { throw new WxErrorException(WxError.fromJson(responseContent, WxType.MiniApp)); diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaInternetServiceImpl.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaInternetServiceImpl.java index f42564279a..7da44ddaba 100644 --- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaInternetServiceImpl.java +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaInternetServiceImpl.java @@ -12,6 +12,7 @@ import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; +import java.nio.charset.StandardCharsets; /** * 服务端网络相关接口 @@ -25,9 +26,9 @@ public class WxMaInternetServiceImpl implements WxMaInternetService { private String sha256(String data, String sessionKey) throws Exception { Mac sha256_HMAC = Mac.getInstance("HmacSHA256"); - SecretKeySpec secret_key = new SecretKeySpec(sessionKey.getBytes("UTF-8"), "HmacSHA256"); + SecretKeySpec secret_key = new SecretKeySpec(sessionKey.getBytes(StandardCharsets.UTF_8), "HmacSHA256"); sha256_HMAC.init(secret_key); - byte[] array = sha256_HMAC.doFinal(data.getBytes("UTF-8")); + byte[] array = sha256_HMAC.doFinal(data.getBytes(StandardCharsets.UTF_8)); StringBuilder sb = new StringBuilder(); for (byte item : array) { sb.append(Integer.toHexString((item & 0xFF) | 0x100).substring(1, 3)); diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaLiveGoodsServiceImpl.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaLiveGoodsServiceImpl.java index cfd8428673..4f9d3be175 100644 --- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaLiveGoodsServiceImpl.java +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaLiveGoodsServiceImpl.java @@ -21,7 +21,6 @@ import java.util.Map; import static cn.binarywang.wx.miniapp.constant.WxMaApiUrlConstants.Broadcast.Goods.*; -import static cn.binarywang.wx.miniapp.constant.WxMaApiUrlConstants.Code.GET_PAGE_URL; /** *
@@ -82,7 +81,7 @@ public WxMaLiveResult getApprovedGoods(Integer offset, Integer limit, Integer st
     String responseContent = wxMaService.get(GET_APPROVED_GOODS, Joiner.on("&").withKeyValueSeparator("=").join(params));
     JsonObject jsonObject = GsonParser.parse(responseContent);
     JsonArray goodsArr = jsonObject.getAsJsonArray("goods");
-    if (goodsArr.size() > 0) {
+    if (!goodsArr.isEmpty()) {
       for (int i = 0; i < goodsArr.size(); i++) {
         // 接口返回key是驼峰
         JsonObject goods = (JsonObject) goodsArr.get(i);
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaMsgServiceImpl.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaMsgServiceImpl.java
index eaf23f11e9..d84603a53b 100644
--- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaMsgServiceImpl.java
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaMsgServiceImpl.java
@@ -6,7 +6,6 @@
 import cn.binarywang.wx.miniapp.bean.WxMaSubscribeMessage;
 import cn.binarywang.wx.miniapp.bean.WxMaUniformMessage;
 import cn.binarywang.wx.miniapp.bean.WxMaUpdatableMsg;
-import cn.binarywang.wx.miniapp.constant.WxMaConstants;
 import cn.binarywang.wx.miniapp.json.WxMaGsonBuilder;
 import com.google.gson.JsonObject;
 import lombok.RequiredArgsConstructor;
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaOrderManagementServiceImpl.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaOrderManagementServiceImpl.java
index 7fcf73f5a3..27d7c01487 100644
--- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaOrderManagementServiceImpl.java
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaOrderManagementServiceImpl.java
@@ -4,7 +4,6 @@
 import cn.binarywang.wx.miniapp.api.WxMaService;
 import cn.binarywang.wx.miniapp.bean.order.WxMaOrderManagementGetOrderDetailPath;
 import cn.binarywang.wx.miniapp.bean.order.WxMaOrderManagementResult;
-import cn.binarywang.wx.miniapp.bean.shop.response.WxMaOrderShippingInfoBaseResponse;
 import cn.binarywang.wx.miniapp.json.WxMaGsonBuilder;
 import com.google.gson.JsonObject;
 import lombok.RequiredArgsConstructor;
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaOrderShippingServiceImpl.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaOrderShippingServiceImpl.java
index 98135cb466..1627a27cd0 100644
--- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaOrderShippingServiceImpl.java
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaOrderShippingServiceImpl.java
@@ -17,7 +17,6 @@
 import me.chanjar.weixin.common.util.json.GsonParser;
 
 import static cn.binarywang.wx.miniapp.constant.WxMaApiUrlConstants.OrderShipping.*;
-import static cn.binarywang.wx.miniapp.constant.WxMaApiUrlConstants.Product.OTHER.GET_CATEGORY;
 
 
 /**
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaProductServiceImpl.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaProductServiceImpl.java
index 6e6ee05e38..d3c1eb2c3f 100644
--- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaProductServiceImpl.java
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaProductServiceImpl.java
@@ -4,11 +4,7 @@
 import static cn.binarywang.wx.miniapp.constant.WxMaApiUrlConstants.Product.OTHER.GET_CATEGORY;
 import static cn.binarywang.wx.miniapp.constant.WxMaApiUrlConstants.Product.OTHER.GET_FREIGHT_TEMPLATE;
 import static cn.binarywang.wx.miniapp.constant.WxMaApiUrlConstants.Product.OTHER.IMG_UPLOAD;
-import static cn.binarywang.wx.miniapp.constant.WxMaApiUrlConstants.Product.Sku.PRODUCT_ADD_SKU_URL;
-import static cn.binarywang.wx.miniapp.constant.WxMaApiUrlConstants.Product.Sku.PRODUCT_BATCH_ADD_SKU_URL;
-import static cn.binarywang.wx.miniapp.constant.WxMaApiUrlConstants.Product.Sku.PRODUCT_DEL_SKU_URL;
 import static cn.binarywang.wx.miniapp.constant.WxMaApiUrlConstants.Product.Sku.PRODUCT_SKU_LIST;
-import static cn.binarywang.wx.miniapp.constant.WxMaApiUrlConstants.Product.Order.PRODUCT_ORDER_GET_LIST;
 import static cn.binarywang.wx.miniapp.constant.WxMaApiUrlConstants.Product.Sku.PRODUCT_ADD_SKU_URL;
 import static cn.binarywang.wx.miniapp.constant.WxMaApiUrlConstants.Product.Sku.PRODUCT_BATCH_ADD_SKU_URL;
 import static cn.binarywang.wx.miniapp.constant.WxMaApiUrlConstants.Product.Sku.PRODUCT_DEL_SKU_URL;
@@ -28,9 +24,6 @@
 import cn.binarywang.wx.miniapp.api.WxMaService;
 import cn.binarywang.wx.miniapp.bean.product.WxMinishopAddGoodsSkuData;
 import cn.binarywang.wx.miniapp.bean.product.WxMinishopAddGoodsSpuData;
-import cn.binarywang.wx.miniapp.bean.product.WxMinishopOrderListResponse;
-import cn.binarywang.wx.miniapp.bean.product.WxMinishopResult;
-import cn.binarywang.wx.miniapp.bean.product.WxMinishopSku;
 import cn.binarywang.wx.miniapp.bean.product.WxMinishopGetBrandResponse;
 import cn.binarywang.wx.miniapp.bean.product.WxMinishopGetCategoryResponse;
 import cn.binarywang.wx.miniapp.bean.product.WxMinishopGetFrightTemplateResponse;
@@ -52,11 +45,7 @@
 import java.util.List;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
-import me.chanjar.weixin.common.api.WxConsts;
 import me.chanjar.weixin.common.bean.result.WxMinishopImageUploadResult;
-import me.chanjar.weixin.common.enums.WxType;
-import me.chanjar.weixin.common.error.WxError;
-import me.chanjar.weixin.common.error.WxErrorException;
 import me.chanjar.weixin.common.util.http.MinishopUploadRequestExecutor;
 import me.chanjar.weixin.common.enums.WxType;
 import me.chanjar.weixin.common.error.WxError;
@@ -142,7 +131,7 @@ public WxMinishopResult addSpu(WxMinishopSpu spu) thr
     if (respObj.get(ERR_CODE).getAsInt() != 0) {
       throw new WxErrorException(WxError.fromJson(response, WxType.MiniApp));
     }
-    WxMinishopResult result = new WxMinishopResult();
+    WxMinishopResult result = new WxMinishopResult<>();
     result.setErrcode(respObj.get(ERR_CODE).getAsInt());
     JsonObject dataObj = respObj.get("data").getAsJsonObject();
     WxMinishopAddGoodsSpuData resultData = new WxMinishopAddGoodsSpuData();
@@ -200,7 +189,7 @@ public WxMinishopResult updateSpu(WxMinishopSpu spu)
     if (respObj.get(ERR_CODE).getAsInt() != 0) {
       throw new WxErrorException(WxError.fromJson(response, WxType.MiniApp));
     }
-    WxMinishopResult result = new WxMinishopResult();
+    WxMinishopResult result = new WxMinishopResult<>();
     result.setErrcode(respObj.get(ERR_CODE).getAsInt());
     JsonObject dataObj = respObj.get("data").getAsJsonObject();
     WxMinishopAddGoodsSpuData resultData = new WxMinishopAddGoodsSpuData();
@@ -259,7 +248,7 @@ public WxMinishopResult minishiopGoodsAddSku(
     if (jsonObject.get(ERR_CODE).getAsInt() != 0) {
       throw new WxErrorException(WxError.fromJson(response, WxType.MiniApp));
     }
-    WxMinishopResult result = new WxMinishopResult();
+    WxMinishopResult result = new WxMinishopResult<>();
     result.setErrcode(jsonObject.get(ERR_CODE).getAsInt());
     JsonObject dataObj = jsonObject.get("data").getAsJsonObject();
     WxMinishopAddGoodsSkuData resultData = new WxMinishopAddGoodsSkuData();
@@ -279,7 +268,7 @@ public WxMinishopResult> minishopGoodsBatchAddSk
       throw new WxErrorException(WxError.fromJson(response, WxType.MiniApp));
     }
 
-    WxMinishopResult result = new WxMinishopResult();
+    WxMinishopResult> result = new WxMinishopResult<>();
     result.setErrcode(jsonObject.get(ERR_CODE).getAsInt());
     JsonArray jsonArray = jsonObject.get("data").getAsJsonArray();
     List skuData = new ArrayList<>();
@@ -317,7 +306,7 @@ public WxMinishopResult minishopGoodsUpdateSku(
     if (jsonObject.get(ERR_CODE).getAsInt() != 0) {
       throw new WxErrorException(WxError.fromJson(response, WxType.MiniApp));
     }
-    WxMinishopResult result = new WxMinishopResult();
+    WxMinishopResult result = new WxMinishopResult<>();
     result.setErrcode(jsonObject.get(ERR_CODE).getAsInt());
     JsonObject dataObj = jsonObject.get("data").getAsJsonObject();
     WxMinishopUpdateGoodsSkuData resultData = new WxMinishopUpdateGoodsSkuData();
@@ -339,7 +328,7 @@ public WxMinishopResult minishopGoodsUpdateSkuPric
       throw new WxErrorException(WxError.fromJson(response, WxType.MiniApp));
     }
 
-    WxMinishopResult result = new WxMinishopResult();
+    WxMinishopResult result = new WxMinishopResult<>();
     result.setErrcode(jsonObject.get(ERR_CODE).getAsInt());
     JsonObject dataObj = jsonObject.get("data").getAsJsonObject();
     WxMinishopUpdateGoodsSkuData resultData = new WxMinishopUpdateGoodsSkuData();
@@ -361,7 +350,7 @@ public WxMinishopResult minishopGoodsUpdateSkuStoc
       throw new WxErrorException(WxError.fromJson(response, WxType.MiniApp));
     }
 
-    WxMinishopResult result = new WxMinishopResult();
+    WxMinishopResult result = new WxMinishopResult<>();
     result.setErrcode(jsonObject.get(ERR_CODE).getAsInt());
     JsonObject dataObj = jsonObject.get("data").getAsJsonObject();
     WxMinishopUpdateGoodsSkuData resultData = new WxMinishopUpdateGoodsSkuData();
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaServiceHttpClientImpl.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaServiceHttpClientImpl.java
index 7b1ea3e96b..9734e25933 100644
--- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaServiceHttpClientImpl.java
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaServiceHttpClientImpl.java
@@ -4,18 +4,17 @@
 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.HttpType;
+import me.chanjar.weixin.common.util.http.HttpClientType;
+import me.chanjar.weixin.common.util.http.apache.ApacheBasicResponseHandler;
 import me.chanjar.weixin.common.util.http.apache.ApacheHttpClientBuilder;
 import me.chanjar.weixin.common.util.http.apache.DefaultApacheHttpClientBuilder;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.http.HttpHost;
 import org.apache.http.client.config.RequestConfig;
-import org.apache.http.client.methods.CloseableHttpResponse;
 import org.apache.http.client.methods.HttpGet;
 import org.apache.http.client.methods.HttpPost;
 import org.apache.http.entity.ContentType;
 import org.apache.http.entity.StringEntity;
-import org.apache.http.impl.client.BasicResponseHandler;
 import org.apache.http.impl.client.CloseableHttpClient;
 
 import java.io.IOException;
@@ -59,8 +58,8 @@ public HttpHost getRequestHttpProxy() {
   }
 
   @Override
-  public HttpType getRequestType() {
-    return HttpType.APACHE_HTTP;
+  public HttpClientType getRequestType() {
+    return HttpClientType.APACHE_HTTP;
   }
 
   @Override
@@ -74,27 +73,12 @@ protected String doGetAccessTokenRequest() throws IOException {
 
     url = String.format(url, this.getWxMaConfig().getAppid(), this.getWxMaConfig().getSecret());
 
-    HttpGet httpGet = null;
-    CloseableHttpResponse response = null;
-    try {
-      httpGet = new HttpGet(url);
-      if (this.getRequestHttpProxy() != null) {
-        RequestConfig config = RequestConfig.custom().setProxy(this.getRequestHttpProxy()).build();
-        httpGet.setConfig(config);
-      }
-      response = getRequestHttpClient().execute(httpGet);
-      return new BasicResponseHandler().handleResponse(response);
-    } finally {
-      if (httpGet != null) {
-        httpGet.releaseConnection();
-      }
-      if (response != null) {
-        try {
-          response.close();
-        } catch (IOException e) {
-        }
-      }
+    HttpGet httpGet = new HttpGet(url);
+    if (this.getRequestHttpProxy() != null) {
+      RequestConfig config = RequestConfig.custom().setProxy(this.getRequestHttpProxy()).build();
+      httpGet.setConfig(config);
     }
+    return getRequestHttpClient().execute(httpGet, ApacheBasicResponseHandler.INSTANCE);
   }
 
   @Override
@@ -104,33 +88,18 @@ protected String doGetStableAccessTokenRequest(boolean forceRefresh) throws IOEx
       GET_STABLE_ACCESS_TOKEN.replace("https://api.weixin.qq.com", this.getWxMaConfig().getApiHostUrl()) :
       GET_STABLE_ACCESS_TOKEN;
 
-    HttpPost httpPost = null;
-    CloseableHttpResponse response = null;
-    try {
-      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));
-      response = getRequestHttpClient().execute(httpPost);
-      return new BasicResponseHandler().handleResponse(response);
-    } finally {
-      if (httpPost != null) {
-        httpPost.releaseConnection();
-      }
-      if (response != null) {
-        try {
-          response.close();
-        } catch (IOException e) {
-        }
-      }
+    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, ApacheBasicResponseHandler.INSTANCE);
   }
 
 }
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaServiceJoddHttpImpl.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaServiceJoddHttpImpl.java
index d2037a0732..d23d865cf9 100644
--- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaServiceJoddHttpImpl.java
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaServiceJoddHttpImpl.java
@@ -8,7 +8,7 @@
 import jodd.http.ProxyInfo;
 import jodd.http.net.SocketHttpConnectionProvider;
 import jodd.net.MimeTypes;
-import me.chanjar.weixin.common.util.http.HttpType;
+import me.chanjar.weixin.common.util.http.HttpClientType;
 import org.apache.commons.lang3.StringUtils;
 
 import java.io.IOException;
@@ -43,8 +43,8 @@ public ProxyInfo getRequestHttpProxy() {
   }
 
   @Override
-  public HttpType getRequestType() {
-    return HttpType.JODD_HTTP;
+  public HttpClientType getRequestType() {
+    return HttpClientType.JODD_HTTP;
   }
 
   @Override
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaServiceOkHttpImpl.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaServiceOkHttpImpl.java
index ff78a6984a..1053b809e9 100644
--- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaServiceOkHttpImpl.java
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaServiceOkHttpImpl.java
@@ -3,7 +3,7 @@
 import cn.binarywang.wx.miniapp.api.WxMaService;
 import cn.binarywang.wx.miniapp.bean.WxMaStableAccessTokenRequest;
 import cn.binarywang.wx.miniapp.config.WxMaConfig;
-import me.chanjar.weixin.common.util.http.HttpType;
+import me.chanjar.weixin.common.util.http.HttpClientType;
 import me.chanjar.weixin.common.util.http.okhttp.DefaultOkHttpClientBuilder;
 import me.chanjar.weixin.common.util.http.okhttp.OkHttpProxyInfo;
 import okhttp3.*;
@@ -58,8 +58,8 @@ public OkHttpProxyInfo getRequestHttpProxy() {
   }
 
   @Override
-  public HttpType getRequestType() {
-    return HttpType.OK_HTTP;
+  public HttpClientType getRequestType() {
+    return HttpClientType.OK_HTTP;
   }
 
   @Override
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaSubscribeServiceImpl.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaSubscribeServiceImpl.java
index 2167ba062b..a7db154a68 100644
--- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaSubscribeServiceImpl.java
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaSubscribeServiceImpl.java
@@ -8,7 +8,6 @@
 import me.chanjar.weixin.common.bean.subscribemsg.PubTemplateKeyword;
 import me.chanjar.weixin.common.bean.subscribemsg.TemplateInfo;
 import me.chanjar.weixin.common.bean.subscribemsg.PubTemplateTitleListResult;
-import cn.binarywang.wx.miniapp.constant.WxMaConstants;
 import cn.binarywang.wx.miniapp.json.WxMaGsonBuilder;
 import com.google.common.base.Joiner;
 import com.google.common.collect.ImmutableMap;
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaXPayServiceImpl.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaXPayServiceImpl.java
index 5e33d1059f..29a7c51a2c 100644
--- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaXPayServiceImpl.java
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaXPayServiceImpl.java
@@ -1,10 +1,10 @@
 package cn.binarywang.wx.miniapp.api.impl;
 
 import cn.binarywang.wx.miniapp.api.WxMaService;
-import cn.binarywang.wx.miniapp.bean.WxMaBaseResponse;
-import cn.binarywang.wx.miniapp.json.WxMaGsonBuilder;
 import cn.binarywang.wx.miniapp.api.WxMaXPayService;
+import cn.binarywang.wx.miniapp.bean.WxMaBaseResponse;
 import cn.binarywang.wx.miniapp.bean.xpay.*;
+import cn.binarywang.wx.miniapp.json.WxMaGsonBuilder;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import me.chanjar.weixin.common.enums.WxType;
@@ -235,4 +235,217 @@ public WxMaXPayQueryPublishGoodsResponse queryPublishGoods(WxMaXPayQueryPublishG
 
     return getDetailResponse;
   }
+
+  @Override
+  public WxMaXPayQueryBizBalanceResponse queryBizBalance(WxMaXPayQueryBizBalanceRequest request, WxMaXPaySigParams sigParams) throws WxErrorException {
+    final String postBody = request.toJson();
+    final String uri = sigParams.signUriWithPay(QUERY_BIZ_BALANCE_URL, postBody);
+    String responseContent = this.service.post(uri, postBody);
+    WxMaXPayQueryBizBalanceResponse getDetailResponse = WxMaGsonBuilder.create()
+      .fromJson(responseContent, WxMaXPayQueryBizBalanceResponse.class);
+
+    if (getDetailResponse.getErrcode() != 0) {
+      throw new WxErrorException(
+        new WxError(getDetailResponse.getErrcode(), getDetailResponse.getErrmsg()));
+    }
+
+    return getDetailResponse;
+  }
+
+  @Override
+  public WxMaXPayQueryTransferAccountResponse queryTransferAccount(WxMaXPayQueryTransferAccountRequest request, WxMaXPaySigParams sigParams) throws WxErrorException {
+    final String postBody = request.toJson();
+    final String uri = sigParams.signUriWithPay(QUERY_TRANSFER_ACCOUNT_URL, postBody);
+    String responseContent = this.service.post(uri, postBody);
+    WxMaXPayQueryTransferAccountResponse getDetailResponse = WxMaGsonBuilder.create()
+      .fromJson(responseContent, WxMaXPayQueryTransferAccountResponse.class);
+    if (getDetailResponse.getErrcode() != 0) {
+      throw new WxErrorException(
+        new WxError(getDetailResponse.getErrcode(), getDetailResponse.getErrmsg()));
+    }
+    return getDetailResponse;
+  }
+
+  @Override
+  public WxMaXPayQueryAdverFundsResponse queryAdverFunds(WxMaXPayQueryAdverFundsRequest request, WxMaXPaySigParams sigParams) throws WxErrorException {
+    final String postBody = request.toJson();
+    final String uri = sigParams.signUriWithPay(QUERY_ADVER_FUNDS_URL, postBody);
+    String responseContent = this.service.post(uri, postBody);
+    WxMaXPayQueryAdverFundsResponse getDetailResponse = WxMaGsonBuilder.create()
+      .fromJson(responseContent, WxMaXPayQueryAdverFundsResponse.class);
+    if (getDetailResponse.getErrcode() != 0) {
+      throw new WxErrorException(
+        new WxError(getDetailResponse.getErrcode(), getDetailResponse.getErrmsg()));
+    }
+    return getDetailResponse;
+  }
+
+  @Override
+  public WxMaXPayCreateFundsBillResponse createFundsBill(WxMaXPayCreateFundsBillRequest request, WxMaXPaySigParams sigParams) throws WxErrorException {
+    final String postBody = request.toJson();
+    final String uri = sigParams.signUriWithPay(CREATE_FUNDS_BILL_URL, postBody);
+    String responseContent = this.service.post(uri, postBody);
+    WxMaXPayCreateFundsBillResponse getDetailResponse = WxMaGsonBuilder.create()
+      .fromJson(responseContent, WxMaXPayCreateFundsBillResponse.class);
+    if (getDetailResponse.getErrcode() != 0) {
+      throw new WxErrorException(
+        new WxError(getDetailResponse.getErrcode(), getDetailResponse.getErrmsg()));
+    }
+    return getDetailResponse;
+  }
+
+  @Override
+  public WxMaBaseResponse bindTransferAccount(WxMaXPayBindTransferAccountRequest request, WxMaXPaySigParams sigParams) throws WxErrorException {
+    final String postBody = request.toJson();
+    final String uri = sigParams.signUriWithPay(BIND_TRANSFER_ACCOUNT_URL, postBody);
+    String responseContent = this.service.post(uri, postBody);
+    WxMaBaseResponse getDetailResponse = WxMaGsonBuilder.create()
+      .fromJson(responseContent, WxMaBaseResponse.class);
+    if (getDetailResponse.getErrcode() != 0) {
+      throw new WxErrorException(
+        new WxError(getDetailResponse.getErrcode(), getDetailResponse.getErrmsg()));
+    }
+    return getDetailResponse;
+  }
+
+  @Override
+  public WxMaXPayQueryFundsBillResponse queryFundsBill(WxMaXPayQueryFundsBillRequest request, WxMaXPaySigParams sigParams) throws WxErrorException {
+    final String postBody = request.toJson();
+    final String uri = sigParams.signUriWithPay(QUERY_FUNDS_BILL_URL, postBody);
+    String responseContent = this.service.post(uri, postBody);
+    WxMaXPayQueryFundsBillResponse getDetailResponse = WxMaGsonBuilder.create()
+      .fromJson(responseContent, WxMaXPayQueryFundsBillResponse.class);
+    if (getDetailResponse.getErrcode() != 0) {
+      throw new WxErrorException(
+        new WxError(getDetailResponse.getErrcode(), getDetailResponse.getErrmsg()));
+    }
+    return getDetailResponse;
+  }
+
+  @Override
+  public WxMaXPayQueryRecoverBillResponse queryRecoverBill(WxMaXPayQueryRecoverBillRequest request, WxMaXPaySigParams sigParams) throws WxErrorException {
+    final String postBody = request.toJson();
+    final String uri = sigParams.signUriWithPay(QUERY_RECOVER_BILL_URL, postBody);
+    String responseContent = this.service.post(uri, postBody);
+    WxMaXPayQueryRecoverBillResponse getDetailResponse = WxMaGsonBuilder.create()
+      .fromJson(responseContent, WxMaXPayQueryRecoverBillResponse.class);
+    if (getDetailResponse.getErrcode() != 0) {
+      throw new WxErrorException(
+        new WxError(getDetailResponse.getErrcode(), getDetailResponse.getErrmsg()));
+    }
+    return getDetailResponse;
+  }
+
+
+  @Override
+  public WxMaXPayGetComplaintListResponse getComplaintList(WxMaXPayGetComplaintListRequest request, WxMaXPaySigParams sigParams) throws WxErrorException {
+    final String postBody = request.toJson();
+    final String uri = sigParams.signUriWithPay(GET_COMPLAINT_LIST_URL, postBody);
+    String responseContent = this.service.post(uri, postBody);
+    WxMaXPayGetComplaintListResponse getDetailResponse = WxMaGsonBuilder.create()
+      .fromJson(responseContent, WxMaXPayGetComplaintListResponse.class);
+    if (getDetailResponse.getErrcode() != 0) {
+      throw new WxErrorException(
+        new WxError(getDetailResponse.getErrcode(), getDetailResponse.getErrmsg()));
+    }
+    return getDetailResponse;
+  }
+
+  @Override
+  public WxMaXPayGetComplaintDetailResponse getComplaintDetail(WxMaXPayGetComplaintDetailRequest request, WxMaXPaySigParams sigParams) throws WxErrorException {
+    final String postBody = request.toJson();
+    final String uri = sigParams.signUriWithPay(GET_COMPLAINT_DETAIL_URL, postBody);
+    String responseContent = this.service.post(uri, postBody);
+    WxMaXPayGetComplaintDetailResponse getDetailResponse = WxMaGsonBuilder.create()
+      .fromJson(responseContent, WxMaXPayGetComplaintDetailResponse.class);
+    if (getDetailResponse.getErrcode() != 0) {
+      throw new WxErrorException(
+        new WxError(getDetailResponse.getErrcode(), getDetailResponse.getErrmsg()));
+    }
+    return getDetailResponse;
+  }
+
+  @Override
+  public WxMaXPayGetNegotiationHistoryResponse getNegotiationHistory(WxMaXPayGetNegotiationHistoryRequest request, WxMaXPaySigParams sigParams) throws WxErrorException {
+    final String postBody = request.toJson();
+    final String uri = sigParams.signUriWithPay(GET_NEGOTIATION_HISTORY_URL, postBody);
+    String responseContent = this.service.post(uri, postBody);
+    WxMaXPayGetNegotiationHistoryResponse getDetailResponse = WxMaGsonBuilder.create()
+      .fromJson(responseContent, WxMaXPayGetNegotiationHistoryResponse.class);
+    if (getDetailResponse.getErrcode() != 0) {
+      throw new WxErrorException(
+        new WxError(getDetailResponse.getErrcode(), getDetailResponse.getErrmsg()));
+    }
+    return getDetailResponse;
+  }
+
+  @Override
+  public WxMaBaseResponse responseComplaint(WxMaXPayResponseComplaintRequest request, WxMaXPaySigParams sigParams) throws WxErrorException {
+    final String postBody = request.toJson();
+    final String uri = sigParams.signUriWithPay(RESPONSE_COMPLAINT_URL, postBody);
+    String responseContent = this.service.post(uri, postBody);
+    WxMaBaseResponse getDetailResponse = WxMaGsonBuilder.create()
+      .fromJson(responseContent, WxMaBaseResponse.class);
+    if (getDetailResponse.getErrcode() != 0) {
+      throw new WxErrorException(
+        new WxError(getDetailResponse.getErrcode(), getDetailResponse.getErrmsg()));
+    }
+    return getDetailResponse;
+  }
+
+  @Override
+  public WxMaBaseResponse completeComplaint(WxMaXPayCompleteComplaintRequest request, WxMaXPaySigParams sigParams) throws WxErrorException {
+    final String postBody = request.toJson();
+    final String uri = sigParams.signUriWithPay(COMPLETE_COMPLAINT_URL, postBody);
+    String responseContent = this.service.post(uri, postBody);
+    WxMaBaseResponse getDetailResponse = WxMaGsonBuilder.create()
+      .fromJson(responseContent, WxMaBaseResponse.class);
+    if (getDetailResponse.getErrcode() != 0) {
+      throw new WxErrorException(
+        new WxError(getDetailResponse.getErrcode(), getDetailResponse.getErrmsg()));
+    }
+    return getDetailResponse;
+  }
+
+  @Override
+  public WxMaXPayUploadVpFileResponse uploadVpFile(WxMaXPayUploadVpFileRequest request, WxMaXPaySigParams sigParams) throws WxErrorException {
+    final String postBody = request.toJson();
+    final String uri = sigParams.signUriWithPay(UPLOAD_VP_FILE_URL, postBody);
+    String responseContent = this.service.post(uri, postBody);
+    WxMaXPayUploadVpFileResponse getDetailResponse = WxMaGsonBuilder.create()
+      .fromJson(responseContent, WxMaXPayUploadVpFileResponse.class);
+    if (getDetailResponse.getErrcode() != 0) {
+      throw new WxErrorException(
+        new WxError(getDetailResponse.getErrcode(), getDetailResponse.getErrmsg()));
+    }
+    return getDetailResponse;
+  }
+
+  @Override
+  public WxMaXPayGetUploadFileSignResponse getUploadFileSign(WxMaXPayGetUploadFileSignRequest request, WxMaXPaySigParams sigParams) throws WxErrorException {
+    final String postBody = request.toJson();
+    final String uri = sigParams.signUriWithPay(GET_UPLOAD_FILE_SIGN_URL, postBody);
+    String responseContent = this.service.post(uri, postBody);
+    WxMaXPayGetUploadFileSignResponse getDetailResponse = WxMaGsonBuilder.create()
+      .fromJson(responseContent, WxMaXPayGetUploadFileSignResponse.class);
+    if (getDetailResponse.getErrcode() != 0) {
+      throw new WxErrorException(
+        new WxError(getDetailResponse.getErrcode(), getDetailResponse.getErrmsg()));
+    }
+    return getDetailResponse;
+  }
+
+  @Override
+  public WxMaXPayDownloadAdverfundsOrderResponse downloadAdverfundsOrder(WxMaXPayDownloadAdverfundsOrderRequest request, WxMaXPaySigParams sigParams) throws WxErrorException {
+    final String postBody = request.toJson();
+    final String uri = sigParams.signUriWithPay(DOWNLOAD_ADVERFUNDS_ORDER_URL, postBody);
+    String responseContent = this.service.post(uri, postBody);
+    WxMaXPayDownloadAdverfundsOrderResponse getDetailResponse = WxMaGsonBuilder.create()
+      .fromJson(responseContent, WxMaXPayDownloadAdverfundsOrderResponse.class);
+    if (getDetailResponse.getErrcode() != 0) {
+      throw new WxErrorException(
+        new WxError(getDetailResponse.getErrcode(), getDetailResponse.getErrmsg()));
+    }
+    return getDetailResponse;
+  }
 }
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/WxMaMessage.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/WxMaMessage.java
index 75d8174caf..d95882a240 100644
--- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/WxMaMessage.java
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/WxMaMessage.java
@@ -212,6 +212,51 @@ public class WxMaMessage implements Serializable {
   @XStreamAlias("SubscribeMsgSentEvent")
   private WxMaSubscribeMsgEvent.SubscribeMsgSentEvent subscribeMsgSentEvent;
 
+  // 小程序基本信息
+
+  //region 小程序基本信息 infoType=notify_3rd_wxa_auth_and_icp
+
+  /**
+   * 返回值
+   */
+  @XStreamAlias("ret")
+  private String ret;
+
+  /**
+   * 一级类目id
+   */
+  @XStreamAlias("first")
+  private String first;
+
+  /**
+   * 二级类目id
+   */
+  @XStreamAlias("second")
+  private String second;
+
+  /**
+   * 驳回原因
+   */
+  @XStreamAlias("reason")
+  private String reason;
+
+  /**
+   * 小程序代码审核驳回原因
+   */
+  @XStreamAlias("Reason")
+  private String weAppReason;
+
+  /**
+   * 昵称
+   */
+  @XStreamAlias("nickname")
+  private String nickname;
+
+  /**
+   * 原始通知内容
+   */
+  private String context;
+
   /**
    * 不要直接使用这个字段,
    * 这个字段只是为了适配 SubscribeMsgPopupEvent SubscribeMsgChangeEvent SubscribeMsgSentEvent
@@ -261,7 +306,9 @@ public static WxMaMessage fromEncryptedXml(String encryptedXml,
                                              WxMaConfig wxMaConfig, String timestamp, String nonce,
                                              String msgSignature) {
     String plainText = new WxMaCryptUtils(wxMaConfig).decryptXml(msgSignature, timestamp, nonce, encryptedXml);
-    return fromXml(plainText);
+    WxMaMessage wxMaMessage = fromXml(plainText);
+    wxMaMessage.setContext(plainText);
+    return wxMaMessage;
   }
 
   public static WxMaMessage fromEncryptedXml(InputStream is, WxMaConfig wxMaConfig, String timestamp,
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/customservice/WxMaCustomserviceResult.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/customservice/WxMaCustomserviceResult.java
new file mode 100644
index 0000000000..e7a9a46de3
--- /dev/null
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/customservice/WxMaCustomserviceResult.java
@@ -0,0 +1,56 @@
+package cn.binarywang.wx.miniapp.bean.customservice;
+
+import cn.binarywang.wx.miniapp.json.WxMaGsonBuilder;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.google.gson.annotations.SerializedName;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * 客服绑定结果信息,包括错误码、主体名称、企业ID和绑定时间戳。
+ * 

+ * 字段说明: + *

    + *
  • errCode: 错误码
  • + *
  • entityName: 小程序主体名称,未绑定时不返回
  • + *
  • corpid: 企业ID,未绑定时不返回
  • + *
  • bindTime: 接受绑定时间戳(毫秒)
  • + *
+ * @author tryking123 + * @since 2025/8/18 17:40 + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class WxMaCustomserviceResult implements Serializable { + private static final long serialVersionUID = 8854979405505241314L; + + @SerializedName("errcode") + private Integer errCode; + + /** + * 该小程序的主体名称,未绑定时不返回 + */ + @SerializedName("entityName") + private String entityName; + + /** + * 企业ID,未绑定时不返回 + */ + @SerializedName("corpid") + private String corpid; + + /** 接受绑定时间戳,ms */ + @JsonProperty("bindTime") + private Long bindTime; + + public static WxMaCustomserviceResult fromJson(String json) { + return WxMaGsonBuilder.create().fromJson(json, WxMaCustomserviceResult.class); + } +} diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/order/WxMaOrderManagementGetOrderDetailPath.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/order/WxMaOrderManagementGetOrderDetailPath.java index 02c53a53f8..b301e356e8 100644 --- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/order/WxMaOrderManagementGetOrderDetailPath.java +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/order/WxMaOrderManagementGetOrderDetailPath.java @@ -4,8 +4,6 @@ import lombok.Data; import lombok.EqualsAndHashCode; -import java.io.Serializable; - /** * @author xzh * @Description diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/shop/request/WxMaOrderShippingIsTradeManagedRequest.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/shop/request/WxMaOrderShippingIsTradeManagedRequest.java index d70e04327a..72d1381cf2 100644 --- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/shop/request/WxMaOrderShippingIsTradeManagedRequest.java +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/shop/request/WxMaOrderShippingIsTradeManagedRequest.java @@ -7,7 +7,6 @@ import lombok.NoArgsConstructor; import java.io.Serializable; -import java.util.List; /** * @author xzh diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/shop/request/WxMaShopAfterSaleAddRequest.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/shop/request/WxMaShopAfterSaleAddRequest.java index ca3c451601..a8bd30e19a 100644 --- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/shop/request/WxMaShopAfterSaleAddRequest.java +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/shop/request/WxMaShopAfterSaleAddRequest.java @@ -7,7 +7,6 @@ import lombok.NoArgsConstructor; import java.io.Serializable; -import java.util.List; /** * @author liming1019 diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/shop/request/WxMaShopAfterSaleListRequest.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/shop/request/WxMaShopAfterSaleListRequest.java index 19db2d2a1b..59aa5c3369 100644 --- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/shop/request/WxMaShopAfterSaleListRequest.java +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/shop/request/WxMaShopAfterSaleListRequest.java @@ -2,7 +2,7 @@ import com.google.gson.annotations.SerializedName; import java.io.Serializable; -import java.util.List; + import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/shop/request/WxMaShopAfterSaleUpdateRequest.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/shop/request/WxMaShopAfterSaleUpdateRequest.java index ac586fa7b7..a86804bb56 100644 --- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/shop/request/WxMaShopAfterSaleUpdateRequest.java +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/shop/request/WxMaShopAfterSaleUpdateRequest.java @@ -1,6 +1,5 @@ package cn.binarywang.wx.miniapp.bean.shop.request; -import cn.binarywang.wx.miniapp.bean.shop.request.WxMaShopAfterSaleAddRequest.UploadMediaList; import com.google.gson.annotations.SerializedName; import lombok.AllArgsConstructor; import lombok.Builder; diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/shop/request/shipping/WxMaOrderCombinedShippingInfoUploadRequest.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/shop/request/shipping/WxMaOrderCombinedShippingInfoUploadRequest.java index 74c4a76780..4d8caf010c 100644 --- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/shop/request/shipping/WxMaOrderCombinedShippingInfoUploadRequest.java +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/shop/request/shipping/WxMaOrderCombinedShippingInfoUploadRequest.java @@ -1,6 +1,5 @@ package cn.binarywang.wx.miniapp.bean.shop.request.shipping; -import cn.binarywang.wx.miniapp.bean.shop.request.shipping.ContactBean; import cn.binarywang.wx.miniapp.bean.shop.request.shipping.OrderKeyBean; import cn.binarywang.wx.miniapp.bean.shop.request.shipping.PayerBean; import com.google.gson.annotations.SerializedName; diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/shop/response/WxMaShopBaseResponse.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/shop/response/WxMaShopBaseResponse.java index e4a015e9ab..d83c657732 100644 --- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/shop/response/WxMaShopBaseResponse.java +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/shop/response/WxMaShopBaseResponse.java @@ -2,7 +2,6 @@ import com.google.gson.annotations.SerializedName; import lombok.Data; -import lombok.EqualsAndHashCode; import java.io.Serializable; diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/vod/WxMaVodListMediaRequest.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/vod/WxMaVodListMediaRequest.java index ace2c3b749..bb498d4add 100644 --- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/vod/WxMaVodListMediaRequest.java +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/vod/WxMaVodListMediaRequest.java @@ -16,18 +16,66 @@ public class WxMaVodListMediaRequest implements Serializable { private static final long serialVersionUID = 7495157056049312108L; + /** + *
+   *   必填:否
+   *   说明:根据剧目id获取剧集信息
+   * 
+ */ @SerializedName("drama_id") private Integer dramaId; + + /** + *
+   *   必填:否
+   *   说明:媒资文件名,支持精确匹配、模糊匹配。文件太多时使用该参数进行模糊匹配可能无法得到结果,推荐使用 media_name_fuzzy 参数。
+   * 
+ */ @SerializedName("media_name") private String mediaName; + /** + *
+   *   必填:否
+   *   说明:媒资文件名,模糊匹配。
+   * 
+ */ + @SerializedName("media_name_fuzzy") + private String mediaNameFuzzy; + + /** + *
+   *   必填:否
+   *   说明:媒资上传时间 >= start_time。
+   * 
+ */ @SerializedName("start_time") private Long startTime; + + /** + *
+   *   必填:否
+   *   说明:媒资上传时间 < end_time。
+   * 
+ */ @SerializedName("end_time") private Long endTime; + /** + *
+   * 必填:否
+   * 说明:分页拉取的起始偏移量。默认值:0。
+   * 
+ */ @SerializedName("offset") private Integer offset; + + /** + *
+   *   必填:否
+   *   说明:分页拉取的最大返回结果数。默认值:100;最大值:100。
+   * 
+ */ @SerializedName("limit") private Integer limit; diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/xpay/WxMaXPayBindTransferAccountRequest.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/xpay/WxMaXPayBindTransferAccountRequest.java new file mode 100644 index 0000000000..dcabbe8e94 --- /dev/null +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/xpay/WxMaXPayBindTransferAccountRequest.java @@ -0,0 +1,32 @@ +package cn.binarywang.wx.miniapp.bean.xpay; + +import cn.binarywang.wx.miniapp.json.WxMaGsonBuilder; +import com.google.gson.annotations.SerializedName; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * @date 2025-07-23 + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class WxMaXPayBindTransferAccountRequest implements Serializable { + private static final long serialVersionUID = 7495157056049312108L; + @SerializedName("transfer_account_uid") + private Long transferAccountUid; + @SerializedName("transfer_account_org_name") + private String transferAccountOrgName; + @SerializedName("env") + private Integer env; + + + public String toJson() { + return WxMaGsonBuilder.create().toJson(this); + } +} diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/xpay/WxMaXPayCompleteComplaintRequest.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/xpay/WxMaXPayCompleteComplaintRequest.java new file mode 100644 index 0000000000..2753a9eea9 --- /dev/null +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/xpay/WxMaXPayCompleteComplaintRequest.java @@ -0,0 +1,30 @@ +package cn.binarywang.wx.miniapp.bean.xpay; + +import cn.binarywang.wx.miniapp.json.WxMaGsonBuilder; +import com.google.gson.annotations.SerializedName; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * @date 2025-07-23 + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class WxMaXPayCompleteComplaintRequest implements Serializable { + private static final long serialVersionUID = 7495157056049312108L; + @SerializedName("env") + private Integer env; + @SerializedName("complaint_id") + private String complaintId; + + + public String toJson() { + return WxMaGsonBuilder.create().toJson(this); + } +} diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/xpay/WxMaXPayCreateFundsBillRequest.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/xpay/WxMaXPayCreateFundsBillRequest.java new file mode 100644 index 0000000000..1e6fb4fc71 --- /dev/null +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/xpay/WxMaXPayCreateFundsBillRequest.java @@ -0,0 +1,48 @@ +package cn.binarywang.wx.miniapp.bean.xpay; + +import cn.binarywang.wx.miniapp.json.WxMaGsonBuilder; +import com.google.gson.annotations.SerializedName; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * @date 2025-07-23 + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class WxMaXPayCreateFundsBillRequest implements Serializable { + private static final long serialVersionUID = 7495157056049312108L; + @SerializedName("transfer_amount") + private Integer transferAmount; + @SerializedName("transfer_account_uid") + private Long transferAccountUid; + @SerializedName("transfer_account_name") + private String transferAccountName; + @SerializedName("transfer_account_agency_id") + private Integer transferAccountAgencyId; + @SerializedName("request_id") + private String requestId; + @SerializedName("settle_begin") + private Long settleBegin; + @SerializedName("settle_end") + private Long settleEnd; + @SerializedName("env") + private Integer env; + @SerializedName("authorize_advertise") + private Integer authorizeAdvertise; + @SerializedName("fund_type") + private Integer fundType; + + + public String toJson() { + return WxMaGsonBuilder.create().toJson(this); + } +} + + diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/xpay/WxMaXPayCreateFundsBillResponse.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/xpay/WxMaXPayCreateFundsBillResponse.java new file mode 100644 index 0000000000..745976d017 --- /dev/null +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/xpay/WxMaXPayCreateFundsBillResponse.java @@ -0,0 +1,30 @@ +package cn.binarywang.wx.miniapp.bean.xpay; + +import cn.binarywang.wx.miniapp.bean.WxMaBaseResponse; +import cn.binarywang.wx.miniapp.json.WxMaGsonBuilder; +import com.google.gson.annotations.SerializedName; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class WxMaXPayCreateFundsBillResponse extends WxMaBaseResponse implements Serializable { + private static final long serialVersionUID = 7495157056049312109L; + + + @SerializedName("bill_id") + private String billId; + + public String toJson() { + return WxMaGsonBuilder.create().toJson(this); + } +} + + diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/xpay/WxMaXPayDownloadAdverfundsOrderRequest.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/xpay/WxMaXPayDownloadAdverfundsOrderRequest.java new file mode 100644 index 0000000000..8ae6135860 --- /dev/null +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/xpay/WxMaXPayDownloadAdverfundsOrderRequest.java @@ -0,0 +1,31 @@ +package cn.binarywang.wx.miniapp.bean.xpay; + +import cn.binarywang.wx.miniapp.json.WxMaGsonBuilder; +import com.google.gson.annotations.SerializedName; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * @auther fancg + * @date 2025-07-23 + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class WxMaXPayDownloadAdverfundsOrderRequest implements Serializable { + private static final long serialVersionUID = 7495157056049312108L; + @SerializedName("fund_id") + private String fundId; + @SerializedName("env") + private Integer env; + + + public String toJson() { + return WxMaGsonBuilder.create().toJson(this); + } +} diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/xpay/WxMaXPayDownloadAdverfundsOrderResponse.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/xpay/WxMaXPayDownloadAdverfundsOrderResponse.java new file mode 100644 index 0000000000..0a64784564 --- /dev/null +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/xpay/WxMaXPayDownloadAdverfundsOrderResponse.java @@ -0,0 +1,27 @@ +package cn.binarywang.wx.miniapp.bean.xpay; + +import cn.binarywang.wx.miniapp.bean.WxMaBaseResponse; +import cn.binarywang.wx.miniapp.json.WxMaGsonBuilder; +import com.google.gson.annotations.SerializedName; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class WxMaXPayDownloadAdverfundsOrderResponse extends WxMaBaseResponse implements Serializable { + private static final long serialVersionUID = 7495157056049312109L; + + @SerializedName("url") + private String url; + + public String toJson() { + return WxMaGsonBuilder.create().toJson(this); + } +} diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/xpay/WxMaXPayGetComplaintDetailRequest.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/xpay/WxMaXPayGetComplaintDetailRequest.java new file mode 100644 index 0000000000..24a2517435 --- /dev/null +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/xpay/WxMaXPayGetComplaintDetailRequest.java @@ -0,0 +1,30 @@ +package cn.binarywang.wx.miniapp.bean.xpay; + +import cn.binarywang.wx.miniapp.json.WxMaGsonBuilder; +import com.google.gson.annotations.SerializedName; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * @date 2025-07-23 + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class WxMaXPayGetComplaintDetailRequest implements Serializable { + private static final long serialVersionUID = 7495157056049312108L; + @SerializedName("env") + private Integer env; + @SerializedName("complaint_id") + private String complaintId; + + + public String toJson() { + return WxMaGsonBuilder.create().toJson(this); + } +} diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/xpay/WxMaXPayGetComplaintDetailResponse.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/xpay/WxMaXPayGetComplaintDetailResponse.java new file mode 100644 index 0000000000..497dcee0e0 --- /dev/null +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/xpay/WxMaXPayGetComplaintDetailResponse.java @@ -0,0 +1,101 @@ +package cn.binarywang.wx.miniapp.bean.xpay; + +import cn.binarywang.wx.miniapp.bean.WxMaBaseResponse; +import cn.binarywang.wx.miniapp.json.WxMaGsonBuilder; +import com.google.gson.annotations.SerializedName; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; +import java.util.List; + + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class WxMaXPayGetComplaintDetailResponse extends WxMaBaseResponse implements Serializable { + private static final long serialVersionUID = 7495157056049312109L; + + + @SerializedName("complaint") + private Complaint complaint; + + @Data + public static class Complaint { + @SerializedName("complaint_id") + private String complaintId; + @SerializedName("complaint_time") + private String complaintTime; + @SerializedName("complaint_detail") + private String complaintDetail; + @SerializedName("complaint_state") + private String complaintState; + @SerializedName("payer_phone") + private String payerPhone; + @SerializedName("payer_openid") + private String payerOpenid; + @SerializedName("complaint_order_info") + private List complaintOrderInfo; + @SerializedName("complaint_full_refunded") + private Boolean complaintFullRefunded; + @SerializedName("incoming_user_response") + private Boolean incomingUserResponse; + @SerializedName("user_complaint_times") + private Integer userComplaintTimes; + @SerializedName("complaint_media_list") + private List complaintMediaList; + @SerializedName("problem_description") + private String problemDescription; + @SerializedName("problem_type") + private String problemType; + @SerializedName("apply_refund_amount") + private Integer applyRefundAmount; + @SerializedName("user_tag_list") + private List userTagList; + @SerializedName("service_order_info") + private List serviceOrderInfo; + + + } + + @Data + public static class ComplaintOrderInfo { + @SerializedName("transaction_id") + private String transactionId; + @SerializedName("out_trade_no") + private String outTradeNo; + @SerializedName("amount") + private Integer amount; + @SerializedName("wxa_out_trade_no") + private String wxaOutTradeNo; + @SerializedName("wx_order_id") + private String wxOrderId; + } + + @Data + public static class ComplaintMedia { + + @SerializedName("media_type") + private String mediaType; + @SerializedName("media_url") + private List mediaUrl; + } + + @Data + public static class ServiceOrderInfo { + @SerializedName("order_id") + private String orderId; + @SerializedName("out_order_no") + private String outOrderNo; + @SerializedName("state") + private String state; + } + + + public String toJson() { + return WxMaGsonBuilder.create().toJson(this); + } +} diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/xpay/WxMaXPayGetComplaintListRequest.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/xpay/WxMaXPayGetComplaintListRequest.java new file mode 100644 index 0000000000..69c9d0aaf5 --- /dev/null +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/xpay/WxMaXPayGetComplaintListRequest.java @@ -0,0 +1,36 @@ +package cn.binarywang.wx.miniapp.bean.xpay; + +import cn.binarywang.wx.miniapp.json.WxMaGsonBuilder; +import com.google.gson.annotations.SerializedName; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * @date 2025-07-23 + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class WxMaXPayGetComplaintListRequest implements Serializable { + private static final long serialVersionUID = 7495157056049312108L; + @SerializedName("env") + private Integer env; + @SerializedName("begin_date") + private String beginDate; + @SerializedName("end_date") + private String endDate; + @SerializedName("offset") + private Integer offset; + @SerializedName("limit") + private Integer limit; + + + public String toJson() { + return WxMaGsonBuilder.create().toJson(this); + } +} diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/xpay/WxMaXPayGetComplaintListResponse.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/xpay/WxMaXPayGetComplaintListResponse.java new file mode 100644 index 0000000000..a1d926cf8f --- /dev/null +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/xpay/WxMaXPayGetComplaintListResponse.java @@ -0,0 +1,102 @@ +package cn.binarywang.wx.miniapp.bean.xpay; + +import cn.binarywang.wx.miniapp.bean.WxMaBaseResponse; +import cn.binarywang.wx.miniapp.json.WxMaGsonBuilder; +import com.google.gson.annotations.SerializedName; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; +import java.util.List; + + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class WxMaXPayGetComplaintListResponse extends WxMaBaseResponse implements Serializable { + private static final long serialVersionUID = 7495157056049312109L; + + + @SerializedName("total") + private Integer total; + @SerializedName("complaints") + private List complaints; + + @Data + public static class Complaint { + @SerializedName("complaint_id") + private String complaintId; + @SerializedName("complaint_time") + private String complaintTime; + @SerializedName("complaint_detail") + private String complaintDetail; + @SerializedName("complaint_state") + private String complaintState; + @SerializedName("payer_phone") + private String payerPhone; + @SerializedName("payer_openid") + private String payerOpenid; + @SerializedName("complaint_order_info") + private List complaintOrderInfo; + @SerializedName("complaint_full_refunded") + private Boolean complaintFullRefunded; + @SerializedName("incoming_user_response") + private Boolean incomingUserResponse; + @SerializedName("user_complaint_times") + private Integer userComplaintTimes; + @SerializedName("complaint_media_list") + private List complaintMediaList; + @SerializedName("problem_description") + private String problemDescription; + @SerializedName("problem_type") + private String problemType; + @SerializedName("apply_refund_amount") + private Integer applyRefundAmount; + @SerializedName("user_tag_list") + private List userTagList; + @SerializedName("service_order_info") + private List serviceOrderInfo; + + + } + + @Data + public static class ComplaintOrderInfo { + @SerializedName("transaction_id") + private String transactionId; + @SerializedName("out_trade_no") + private String outTradeNo; + @SerializedName("amount") + private Integer amount; + @SerializedName("wxa_out_trade_no") + private String wxaOutTradeNo; + @SerializedName("wx_order_id") + private String wxOrderId; + } + + @Data + public static class ComplaintMedia { + + @SerializedName("media_type") + private String mediaType; + @SerializedName("media_url") + private List mediaUrl; + } + + @Data + public static class ServiceOrderInfo { + @SerializedName("order_id") + private String orderId; + @SerializedName("out_order_no") + private String outOrderNo; + @SerializedName("state") + private String state; + } + + public String toJson() { + return WxMaGsonBuilder.create().toJson(this); + } +} diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/xpay/WxMaXPayGetNegotiationHistoryRequest.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/xpay/WxMaXPayGetNegotiationHistoryRequest.java new file mode 100644 index 0000000000..26dee2c9b9 --- /dev/null +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/xpay/WxMaXPayGetNegotiationHistoryRequest.java @@ -0,0 +1,34 @@ +package cn.binarywang.wx.miniapp.bean.xpay; + +import cn.binarywang.wx.miniapp.json.WxMaGsonBuilder; +import com.google.gson.annotations.SerializedName; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * @date 2025-07-23 + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class WxMaXPayGetNegotiationHistoryRequest implements Serializable { + private static final long serialVersionUID = 7495157056049312108L; + @SerializedName("env") + private Integer env; + @SerializedName("complaint_id") + private String complaintId; + @SerializedName("offset") + private Integer offset; + @SerializedName("limit") + private Integer limit; + + + public String toJson() { + return WxMaGsonBuilder.create().toJson(this); + } +} diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/xpay/WxMaXPayGetNegotiationHistoryResponse.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/xpay/WxMaXPayGetNegotiationHistoryResponse.java new file mode 100644 index 0000000000..73d8220b7a --- /dev/null +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/xpay/WxMaXPayGetNegotiationHistoryResponse.java @@ -0,0 +1,56 @@ +package cn.binarywang.wx.miniapp.bean.xpay; + +import cn.binarywang.wx.miniapp.bean.WxMaBaseResponse; +import cn.binarywang.wx.miniapp.json.WxMaGsonBuilder; +import com.google.gson.annotations.SerializedName; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; +import java.util.List; + + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class WxMaXPayGetNegotiationHistoryResponse extends WxMaBaseResponse implements Serializable { + private static final long serialVersionUID = 7495157056049312109L; + + + @SerializedName("total") + private Integer total; + @SerializedName("history") + private List history; + + @Data + public static class History { + @SerializedName("log_id") + private String logId; + @SerializedName("operator") + private String operator; + @SerializedName("operate_time") + private String operateTime; + @SerializedName("operate_type") + private String operateType; + @SerializedName("operate_details") + private String operateDetails; + @SerializedName("complaint_media_list") + private List complaintMediaList; + + @Data + public static class ComplaintMedia { + + @SerializedName("media_type") + private String mediaType; + @SerializedName("media_url") + private List mediaUrl; + } + } + + public String toJson() { + return WxMaGsonBuilder.create().toJson(this); + } +} diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/xpay/WxMaXPayGetUploadFileSignRequest.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/xpay/WxMaXPayGetUploadFileSignRequest.java new file mode 100644 index 0000000000..2613988842 --- /dev/null +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/xpay/WxMaXPayGetUploadFileSignRequest.java @@ -0,0 +1,34 @@ +package cn.binarywang.wx.miniapp.bean.xpay; + +import cn.binarywang.wx.miniapp.json.WxMaGsonBuilder; +import com.google.gson.annotations.SerializedName; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * @date 2025-07-23 + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class WxMaXPayGetUploadFileSignRequest implements Serializable { + private static final long serialVersionUID = 7495157056049312108L; + @SerializedName("env") + private Integer env; + @SerializedName("wxpay_url") + private String wxpayUrl; + @SerializedName("convert_cos") + private Boolean convertCos; + @SerializedName("complaint_id") + private String complaintId; + + + public String toJson() { + return WxMaGsonBuilder.create().toJson(this); + } +} diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/xpay/WxMaXPayGetUploadFileSignResponse.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/xpay/WxMaXPayGetUploadFileSignResponse.java new file mode 100644 index 0000000000..ec0cd7e3a2 --- /dev/null +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/xpay/WxMaXPayGetUploadFileSignResponse.java @@ -0,0 +1,30 @@ +package cn.binarywang.wx.miniapp.bean.xpay; + +import cn.binarywang.wx.miniapp.bean.WxMaBaseResponse; +import cn.binarywang.wx.miniapp.json.WxMaGsonBuilder; +import com.google.gson.annotations.SerializedName; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class WxMaXPayGetUploadFileSignResponse extends WxMaBaseResponse implements Serializable { + private static final long serialVersionUID = 7495157056049312109L; + + + @SerializedName("sign") + private String sign; + @SerializedName("cos_url") + private String cosUrl; + + public String toJson() { + return WxMaGsonBuilder.create().toJson(this); + } +} diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/xpay/WxMaXPayQueryAdverFundsRequest.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/xpay/WxMaXPayQueryAdverFundsRequest.java new file mode 100644 index 0000000000..d22d0cf40c --- /dev/null +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/xpay/WxMaXPayQueryAdverFundsRequest.java @@ -0,0 +1,44 @@ +package cn.binarywang.wx.miniapp.bean.xpay; + +import cn.binarywang.wx.miniapp.json.WxMaGsonBuilder; +import com.google.gson.annotations.SerializedName; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * @date 2025-07-23 + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class WxMaXPayQueryAdverFundsRequest implements Serializable { + private static final long serialVersionUID = 7495157056049312108L; + @SerializedName("page") + private Integer page; + @SerializedName("page_size") + private Integer pageSize; + @SerializedName("filter") + private Filter filter; + @SerializedName("env") + private Integer env; + + @Data + public static class Filter { + @SerializedName("settle_begin") + private Long settleBegin; + @SerializedName("settle_end") + private Long settleEnd; + @SerializedName("fund_type") + private Integer fundType; + + } + + public String toJson() { + return WxMaGsonBuilder.create().toJson(this); + } +} diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/xpay/WxMaXPayQueryAdverFundsResponse.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/xpay/WxMaXPayQueryAdverFundsResponse.java new file mode 100644 index 0000000000..2095fc3a97 --- /dev/null +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/xpay/WxMaXPayQueryAdverFundsResponse.java @@ -0,0 +1,49 @@ +package cn.binarywang.wx.miniapp.bean.xpay; + +import cn.binarywang.wx.miniapp.bean.WxMaBaseResponse; +import cn.binarywang.wx.miniapp.json.WxMaGsonBuilder; +import com.google.gson.annotations.SerializedName; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; +import java.util.List; + + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class WxMaXPayQueryAdverFundsResponse extends WxMaBaseResponse implements Serializable { + private static final long serialVersionUID = 7495157056049312109L; + + @SerializedName("adver_funds_list") + private List adverFundsList; + @SerializedName("total_page") + private Integer totalPage; + + + @Data + public static class AdverFunds { + @SerializedName("settle_begin") + private Long settleBegin; + @SerializedName("settle_end") + private Long settleEnd; + @SerializedName("total_amount") + private Integer totalAmount; + @SerializedName("remain_amount") + private Integer remainAmount; + @SerializedName("expire_time") + private Long expireTime; + @SerializedName("fund_type") + private Integer fundType; + @SerializedName("fund_id") + private String fundId; + } + + public String toJson() { + return WxMaGsonBuilder.create().toJson(this); + } +} diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/xpay/WxMaXPayQueryBizBalanceRequest.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/xpay/WxMaXPayQueryBizBalanceRequest.java new file mode 100644 index 0000000000..767390915d --- /dev/null +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/xpay/WxMaXPayQueryBizBalanceRequest.java @@ -0,0 +1,24 @@ +package cn.binarywang.wx.miniapp.bean.xpay; + +import cn.binarywang.wx.miniapp.json.WxMaGsonBuilder; +import com.google.gson.annotations.SerializedName; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class WxMaXPayQueryBizBalanceRequest implements Serializable { + private static final long serialVersionUID = 7495157056049312108L; + @SerializedName("env") + private Integer env; + + public String toJson() { + return WxMaGsonBuilder.create().toJson(this); + } +} diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/xpay/WxMaXPayQueryBizBalanceResponse.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/xpay/WxMaXPayQueryBizBalanceResponse.java new file mode 100644 index 0000000000..c2f9a59db7 --- /dev/null +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/xpay/WxMaXPayQueryBizBalanceResponse.java @@ -0,0 +1,36 @@ +package cn.binarywang.wx.miniapp.bean.xpay; + +import cn.binarywang.wx.miniapp.bean.WxMaBaseResponse; +import cn.binarywang.wx.miniapp.json.WxMaGsonBuilder; +import com.google.gson.annotations.SerializedName; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class WxMaXPayQueryBizBalanceResponse extends WxMaBaseResponse implements Serializable { + private static final long serialVersionUID = 7495157056049312108L; + + @SerializedName("balance_available") + private BalanceAvailable balanceAvailable; + + @Data + public static class BalanceAvailable { + @SerializedName("amount") + private String amount; + @SerializedName("currency_code") + private String currencyCode; + + } + + public String toJson() { + return WxMaGsonBuilder.create().toJson(this); + } + +} diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/xpay/WxMaXPayQueryFundsBillRequest.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/xpay/WxMaXPayQueryFundsBillRequest.java new file mode 100644 index 0000000000..23a4f408b6 --- /dev/null +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/xpay/WxMaXPayQueryFundsBillRequest.java @@ -0,0 +1,45 @@ +package cn.binarywang.wx.miniapp.bean.xpay; + +import cn.binarywang.wx.miniapp.json.WxMaGsonBuilder; +import com.google.gson.annotations.SerializedName; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * @date 2025-07-23 + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class WxMaXPayQueryFundsBillRequest implements Serializable { + private static final long serialVersionUID = 7495157056049312108L; + @SerializedName("page") + private Integer page; + @SerializedName("page_size") + private Integer pageSize; + @SerializedName("filter") + private Filter filter; + @SerializedName("env") + private Integer env; + + @Data + public static class Filter { + @SerializedName("oper_time_begin") + private Long operTimeBegin; + @SerializedName("oper_time_end") + private Long operTimeEnd; + @SerializedName("bill_id") + private String billId; + @SerializedName("request_id") + private String requestId; + } + + public String toJson() { + return WxMaGsonBuilder.create().toJson(this); + } +} diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/xpay/WxMaXPayQueryFundsBillResponse.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/xpay/WxMaXPayQueryFundsBillResponse.java new file mode 100644 index 0000000000..e99c47171f --- /dev/null +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/xpay/WxMaXPayQueryFundsBillResponse.java @@ -0,0 +1,55 @@ +package cn.binarywang.wx.miniapp.bean.xpay; + +import cn.binarywang.wx.miniapp.bean.WxMaBaseResponse; +import cn.binarywang.wx.miniapp.json.WxMaGsonBuilder; +import com.google.gson.annotations.SerializedName; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; +import java.util.List; + + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class WxMaXPayQueryFundsBillResponse extends WxMaBaseResponse implements Serializable { + private static final long serialVersionUID = 7495157056049312109L; + + @SerializedName("bill_list") + private List billList; + @SerializedName("total_page") + private Integer totalPage; + + + @Data + public static class Bill { + @SerializedName("bill_id") + private String billId; + @SerializedName("oper_time") + private Long operTime; + @SerializedName("settle_begin") + private Long settleBegin; + @SerializedName("settle_end") + private Long settleEnd; + @SerializedName("fund_id") + private String fundId; + @SerializedName("transfer_account_name") + private String transferAccountName; + @SerializedName("transfer_account_uid") + private Integer transferAccountUid; + @SerializedName("transfer_amount") + private Integer transferAmount; + @SerializedName("status") + private Integer status; + @SerializedName("request_id") + private String requestId; + } + + public String toJson() { + return WxMaGsonBuilder.create().toJson(this); + } +} diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/xpay/WxMaXPayQueryRecoverBillRequest.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/xpay/WxMaXPayQueryRecoverBillRequest.java new file mode 100644 index 0000000000..cf76960844 --- /dev/null +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/xpay/WxMaXPayQueryRecoverBillRequest.java @@ -0,0 +1,43 @@ +package cn.binarywang.wx.miniapp.bean.xpay; + +import cn.binarywang.wx.miniapp.json.WxMaGsonBuilder; +import com.google.gson.annotations.SerializedName; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * @date 2025-07-23 + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class WxMaXPayQueryRecoverBillRequest implements Serializable { + private static final long serialVersionUID = 7495157056049312108L; + @SerializedName("page") + private Integer page; + @SerializedName("page_size") + private Integer pageSize; + @SerializedName("filter") + private Filter filter; + @SerializedName("env") + private Integer env; + + @Data + public static class Filter { + @SerializedName("recover_time_begin") + private Long recoverTimeBegin; + @SerializedName("recover_time_end") + private Long recoverTimeEnd; + @SerializedName("bill_id") + private String billId; + } + + public String toJson() { + return WxMaGsonBuilder.create().toJson(this); + } +} diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/xpay/WxMaXPayQueryRecoverBillResponse.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/xpay/WxMaXPayQueryRecoverBillResponse.java new file mode 100644 index 0000000000..26c04129a3 --- /dev/null +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/xpay/WxMaXPayQueryRecoverBillResponse.java @@ -0,0 +1,50 @@ +package cn.binarywang.wx.miniapp.bean.xpay; + +import cn.binarywang.wx.miniapp.bean.WxMaBaseResponse; +import cn.binarywang.wx.miniapp.json.WxMaGsonBuilder; +import com.google.gson.annotations.SerializedName; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; +import java.util.List; + + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class WxMaXPayQueryRecoverBillResponse extends WxMaBaseResponse implements Serializable { + private static final long serialVersionUID = 7495157056049312109L; + + @SerializedName("bill_list") + private List billList; + @SerializedName("total_page") + private Integer totalPage; + + @Data + public static class Bill { + @SerializedName("bill_id") + private String billId; + @SerializedName("recover_time") + private Long recoverTime; + @SerializedName("settle_begin") + private Long settleBegin; + @SerializedName("settle_end") + private Long settleEnd; + @SerializedName("fund_id") + private String fundId; + @SerializedName("recover_account_name") + private String recoverAccountName; + @SerializedName("recover_amount") + private Integer recoverAmount; + @SerializedName("refund_order_list") + private List refundOrderList; + } + + public String toJson() { + return WxMaGsonBuilder.create().toJson(this); + } +} diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/xpay/WxMaXPayQueryTransferAccountRequest.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/xpay/WxMaXPayQueryTransferAccountRequest.java new file mode 100644 index 0000000000..9b90dd250c --- /dev/null +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/xpay/WxMaXPayQueryTransferAccountRequest.java @@ -0,0 +1,28 @@ +package cn.binarywang.wx.miniapp.bean.xpay; + +import cn.binarywang.wx.miniapp.json.WxMaGsonBuilder; +import com.google.gson.annotations.SerializedName; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * @author 秋日 + * @date 2025-07-23 + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class WxMaXPayQueryTransferAccountRequest implements Serializable { + private static final long serialVersionUID = 7495157056049312108L; + @SerializedName("env") + private Integer env; + + public String toJson() { + return WxMaGsonBuilder.create().toJson(this); + } +} diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/xpay/WxMaXPayQueryTransferAccountResponse.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/xpay/WxMaXPayQueryTransferAccountResponse.java new file mode 100644 index 0000000000..079f088b2e --- /dev/null +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/xpay/WxMaXPayQueryTransferAccountResponse.java @@ -0,0 +1,46 @@ +package cn.binarywang.wx.miniapp.bean.xpay; + +import cn.binarywang.wx.miniapp.bean.WxMaBaseResponse; +import cn.binarywang.wx.miniapp.json.WxMaGsonBuilder; +import com.google.gson.annotations.SerializedName; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; +import java.util.List; + + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class WxMaXPayQueryTransferAccountResponse extends WxMaBaseResponse implements Serializable { + private static final long serialVersionUID = 7495157056049312109L; + + @SerializedName("acct_list") + private List acctList; + + public String toJson() { + return WxMaGsonBuilder.create().toJson(this); + } + + @Data + public static class AcctList { + @SerializedName("transfer_account_name") + private String transferAccountName; + @SerializedName("transfer_account_uid") + private Long transferAccountUid; + @SerializedName("transfer_account_agency_id") + private Long transferAccountAgencyId; + @SerializedName("transfer_account_agency_name") + private String transferAccountAgencyName; + @SerializedName("state") + private Integer state; + @SerializedName("bind_result") + private Integer bindResult; + @SerializedName("error_msg") + private String errorMsg; + } +} diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/xpay/WxMaXPayResponseComplaintRequest.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/xpay/WxMaXPayResponseComplaintRequest.java new file mode 100644 index 0000000000..1fac9fc9b2 --- /dev/null +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/xpay/WxMaXPayResponseComplaintRequest.java @@ -0,0 +1,35 @@ +package cn.binarywang.wx.miniapp.bean.xpay; + +import cn.binarywang.wx.miniapp.json.WxMaGsonBuilder; +import com.google.gson.annotations.SerializedName; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; +import java.util.List; + +/** + * @date 2025-07-23 + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class WxMaXPayResponseComplaintRequest implements Serializable { + private static final long serialVersionUID = 7495157056049312108L; + @SerializedName("env") + private Integer env; + @SerializedName("complaint_id") + private String complaintId; + @SerializedName("response_content") + private String responseContent; + @SerializedName("response_images") + private List responseImages; + + + public String toJson() { + return WxMaGsonBuilder.create().toJson(this); + } +} diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/xpay/WxMaXPayUploadVpFileRequest.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/xpay/WxMaXPayUploadVpFileRequest.java new file mode 100644 index 0000000000..2a8ac44b5b --- /dev/null +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/xpay/WxMaXPayUploadVpFileRequest.java @@ -0,0 +1,34 @@ +package cn.binarywang.wx.miniapp.bean.xpay; + +import cn.binarywang.wx.miniapp.json.WxMaGsonBuilder; +import com.google.gson.annotations.SerializedName; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * @date 2025-07-23 + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class WxMaXPayUploadVpFileRequest implements Serializable { + private static final long serialVersionUID = 7495157056049312108L; + @SerializedName("env") + private Integer env; + @SerializedName("base64_img") + private String base64Img; + @SerializedName("img_url") + private String imgUrl; + @SerializedName("file_name") + private String fileName; + + + public String toJson() { + return WxMaGsonBuilder.create().toJson(this); + } +} diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/xpay/WxMaXPayUploadVpFileResponse.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/xpay/WxMaXPayUploadVpFileResponse.java new file mode 100644 index 0000000000..68ca557dca --- /dev/null +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/xpay/WxMaXPayUploadVpFileResponse.java @@ -0,0 +1,28 @@ +package cn.binarywang.wx.miniapp.bean.xpay; + +import cn.binarywang.wx.miniapp.bean.WxMaBaseResponse; +import cn.binarywang.wx.miniapp.json.WxMaGsonBuilder; +import com.google.gson.annotations.SerializedName; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class WxMaXPayUploadVpFileResponse extends WxMaBaseResponse implements Serializable { + private static final long serialVersionUID = 7495157056049312109L; + + + @SerializedName("file_id") + private String fileId; + + public String toJson() { + return WxMaGsonBuilder.create().toJson(this); + } +} diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/constant/WxMaApiUrlConstants.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/constant/WxMaApiUrlConstants.java index d61ade73c3..79806dbd84 100644 --- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/constant/WxMaApiUrlConstants.java +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/constant/WxMaApiUrlConstants.java @@ -626,7 +626,7 @@ public interface InstantDelivery { String GET_DELIVERY_LIST_URL = "https://api.weixin.qq.com/cgi-bin/express/delivery/open_msg/get_delivery_list"; - /** 获取运力id列表get_delivery_list 商户使用此接口获取所有运力id的列表 */ + /** 物流服务-查询组件-更新物品信息接口 update_waybill_goods 更新物品信息 */ String UPDATE_WAYBILL_GOODS_URL = "https://api.weixin.qq.com/cgi-bin/express/delivery/open_msg/update_waybill_goods"; @@ -885,6 +885,22 @@ public interface XPay { "https://api.weixin.qq.com/xpay/start_publish_goods?pay_sig=%s"; String QUERY_PUBLISH_GOODS_URL = "https://api.weixin.qq.com/xpay/query_publish_goods?pay_sig=%s"; + String QUERY_BIZ_BALANCE_URL = + "https://api.weixin.qq.com/xpay/query_biz_balance?pay_sig=%s"; + String QUERY_TRANSFER_ACCOUNT_URL = "https://api.weixin.qq.com/xpay/query_transfer_account?pay_sig=%s"; + String QUERY_ADVER_FUNDS_URL = "https://api.weixin.qq.com/xpay/query_adver_funds?pay_sig=%s"; + String CREATE_FUNDS_BILL_URL = "https://api.weixin.qq.com/xpay/create_funds_bill?pay_sig=%s"; + String BIND_TRANSFER_ACCOUNT_URL = "https://api.weixin.qq.com/xpay/bind_transfer_accout?pay_sig=%s"; + String QUERY_FUNDS_BILL_URL = "https://api.weixin.qq.com/xpay/query_funds_bill?pay_sig=%s"; + String QUERY_RECOVER_BILL_URL = "https://api.weixin.qq.com/xpay/query_recover_bill?pay_sig=%s"; + String GET_COMPLAINT_LIST_URL = "https://api.weixin.qq.com/xpay/get_complaint_list?pay_sig=%s"; + String GET_COMPLAINT_DETAIL_URL = "https://api.weixin.qq.com/xpay/get_complaint_detail?pay_sig=%s"; + String GET_NEGOTIATION_HISTORY_URL = "https://api.weixin.qq.com/xpay/get_negotiation_history?pay_sig=%s"; + String RESPONSE_COMPLAINT_URL = "https://api.weixin.qq.com/xpay/response_complaint?pay_sig=%s"; + String COMPLETE_COMPLAINT_URL = "https://api.weixin.qq.com/xpay/complete_complaint?pay_sig=%s"; + String UPLOAD_VP_FILE_URL = "https://api.weixin.qq.com/xpay/upload_vp_file?pay_sig=%s"; + String GET_UPLOAD_FILE_SIGN_URL = "https://api.weixin.qq.com/xpay/get_upload_file_sign?pay_sig=%s"; + String DOWNLOAD_ADVERFUNDS_ORDER_URL = "https://api.weixin.qq.com/xpay/download_adverfunds_order?pay_sig=%s"; } /** diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/executor/ApacheApiSignaturePostRequestExecutor.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/executor/ApacheApiSignaturePostRequestExecutor.java index 3dcf22b10f..0a858256a8 100644 --- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/executor/ApacheApiSignaturePostRequestExecutor.java +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/executor/ApacheApiSignaturePostRequestExecutor.java @@ -1,30 +1,27 @@ package cn.binarywang.wx.miniapp.executor; import cn.binarywang.wx.miniapp.bean.WxMaApiResponse; -import java.io.IOException; -import java.util.HashMap; -import java.util.Map; import me.chanjar.weixin.common.enums.WxType; import me.chanjar.weixin.common.error.WxErrorException; import me.chanjar.weixin.common.util.http.RequestHttp; import me.chanjar.weixin.common.util.http.apache.Utf8ResponseHandler; -import org.apache.http.Consts; import org.apache.http.Header; import org.apache.http.HttpHost; import org.apache.http.client.config.RequestConfig; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpPost; +import org.apache.http.entity.ContentType; import org.apache.http.entity.StringEntity; import org.apache.http.impl.client.CloseableHttpClient; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -public class ApacheApiSignaturePostRequestExecutor - extends ApiSignaturePostRequestExecutor { - private static final Logger logger = - LoggerFactory.getLogger(ApacheApiSignaturePostRequestExecutor.class); +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.Map; + +public class ApacheApiSignaturePostRequestExecutor extends ApiSignaturePostRequestExecutor { - public ApacheApiSignaturePostRequestExecutor(RequestHttp requestHttp) { + public ApacheApiSignaturePostRequestExecutor(RequestHttp requestHttp) { super(requestHttp); } @@ -49,8 +46,7 @@ public WxMaApiResponse execute( } if (postEntity != null) { - StringEntity entity = new StringEntity(postEntity, Consts.UTF_8); - entity.setContentType("application/json; charset=utf-8"); + StringEntity entity = new StringEntity(postEntity, ContentType.APPLICATION_JSON.withCharset(StandardCharsets.UTF_8)); httpPost.setEntity(entity); } @@ -64,8 +60,6 @@ public WxMaApiResponse execute( } } return this.handleResponse(wxType, responseContent, respHeaders); - } finally { - httpPost.releaseConnection(); } } } diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/executor/ApacheQrcodeBytesRequestExecutor.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/executor/ApacheQrcodeBytesRequestExecutor.java index 58c7beabee..fff1be7fc4 100644 --- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/executor/ApacheQrcodeBytesRequestExecutor.java +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/executor/ApacheQrcodeBytesRequestExecutor.java @@ -63,8 +63,6 @@ public byte[] execute(String uri, AbstractWxMaQrcodeWrapper qrcodeWrapper, WxTyp } return IOUtils.toByteArray(inputStream); - } finally { - httpPost.releaseConnection(); } } } diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/executor/ApacheQrcodeFileRequestExecutor.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/executor/ApacheQrcodeFileRequestExecutor.java index c7f57b2f68..fbc0edcfbe 100644 --- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/executor/ApacheQrcodeFileRequestExecutor.java +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/executor/ApacheQrcodeFileRequestExecutor.java @@ -71,8 +71,6 @@ public File execute(String uri, AbstractWxMaQrcodeWrapper qrcodeWrapper, WxType return FileUtils.createTmpFile(inputStream, UUID.randomUUID().toString(), "jpg"); } return FileUtils.createTmpFile(inputStream, UUID.randomUUID().toString(), "jpg", Paths.get(filePath).toFile()); - } finally { - httpPost.releaseConnection(); } } } diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/executor/ApacheUploadAuthMaterialRequestExecutor.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/executor/ApacheUploadAuthMaterialRequestExecutor.java index ac3ffd7c71..59b35567bb 100644 --- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/executor/ApacheUploadAuthMaterialRequestExecutor.java +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/executor/ApacheUploadAuthMaterialRequestExecutor.java @@ -9,7 +9,6 @@ import org.apache.http.HttpEntity; import org.apache.http.HttpHost; import org.apache.http.client.config.RequestConfig; -import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpPost; import org.apache.http.entity.mime.HttpMultipartMode; import org.apache.http.entity.mime.MultipartEntityBuilder; @@ -24,34 +23,30 @@ */ public class ApacheUploadAuthMaterialRequestExecutor extends UploadAuthMaterialRequestExecutor { - public ApacheUploadAuthMaterialRequestExecutor(RequestHttp requestHttp) { - super(requestHttp); - } + public ApacheUploadAuthMaterialRequestExecutor(RequestHttp requestHttp) { + super(requestHttp); + } - @Override - public WxMaUploadAuthMaterialResult execute(String uri, File file, WxType wxType) throws WxErrorException, IOException { - HttpPost httpPost = new HttpPost(uri); - if (requestHttp.getRequestHttpProxy() != null) { - RequestConfig config = RequestConfig.custom().setProxy(requestHttp.getRequestHttpProxy()).build(); - httpPost.setConfig(config); - } - if (file != null) { - HttpEntity entity = MultipartEntityBuilder - .create() - .addBinaryBody("media", file) - .setMode(HttpMultipartMode.RFC6532) - .build(); - httpPost.setEntity(entity); - } - try (CloseableHttpResponse response = requestHttp.getRequestHttpClient().execute(httpPost)) { - String responseContent = Utf8ResponseHandler.INSTANCE.handleResponse(response); - WxError error = WxError.fromJson(responseContent, wxType); - if (error.getErrorCode() != 0) { - throw new WxErrorException(error); - } - return WxMaUploadAuthMaterialResult.fromJson(responseContent); - } finally { - httpPost.releaseConnection(); - } + @Override + public WxMaUploadAuthMaterialResult execute(String uri, File file, WxType wxType) throws WxErrorException, IOException { + HttpPost httpPost = new HttpPost(uri); + if (requestHttp.getRequestHttpProxy() != null) { + RequestConfig config = RequestConfig.custom().setProxy(requestHttp.getRequestHttpProxy()).build(); + httpPost.setConfig(config); + } + if (file != null) { + HttpEntity entity = MultipartEntityBuilder + .create() + .addBinaryBody("media", file) + .setMode(HttpMultipartMode.RFC6532) + .build(); + httpPost.setEntity(entity); + } + String responseContent = requestHttp.getRequestHttpClient().execute(httpPost, Utf8ResponseHandler.INSTANCE); + WxError error = WxError.fromJson(responseContent, wxType); + if (error.getErrorCode() != 0) { + throw new WxErrorException(error); } + return WxMaUploadAuthMaterialResult.fromJson(responseContent); + } } diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/executor/ApacheVodSingleUploadRequestExecutor.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/executor/ApacheVodSingleUploadRequestExecutor.java index a9ffd1af39..2ca23ae325 100644 --- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/executor/ApacheVodSingleUploadRequestExecutor.java +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/executor/ApacheVodSingleUploadRequestExecutor.java @@ -8,7 +8,6 @@ import me.chanjar.weixin.common.util.http.apache.Utf8ResponseHandler; import org.apache.http.HttpHost; import org.apache.http.client.config.RequestConfig; -import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpPost; import org.apache.http.entity.mime.HttpMultipartMode; import org.apache.http.entity.mime.MultipartEntityBuilder; @@ -22,9 +21,8 @@ */ public class ApacheVodSingleUploadRequestExecutor extends VodSingleUploadRequestExecutor { - public ApacheVodSingleUploadRequestExecutor(RequestHttp requestHttp, String mediaName, String mediaType, String coverType, File coverData, String sourceContext) { + public ApacheVodSingleUploadRequestExecutor(RequestHttp requestHttp, String mediaName, String mediaType, String coverType, File coverData, String sourceContext) { super(requestHttp, mediaName, mediaType, coverType, coverData, sourceContext); - } @Override @@ -54,15 +52,11 @@ public WxMaVodSingleFileUploadResult execute(String uri, File file, WxType wxTyp httpPost.setEntity(entityBuilder.build()); } - try (CloseableHttpResponse response = requestHttp.getRequestHttpClient().execute(httpPost)) { - String responseContent = Utf8ResponseHandler.INSTANCE.handleResponse(response); - WxError error = WxError.fromJson(responseContent, wxType); - if (error.getErrorCode() != 0) { - throw new WxErrorException(error); - } - return WxMaVodSingleFileUploadResult.fromJson(responseContent); - } finally { - httpPost.releaseConnection(); + String responseContent = requestHttp.getRequestHttpClient().execute(httpPost, Utf8ResponseHandler.INSTANCE); + WxError error = WxError.fromJson(responseContent, wxType); + if (error.getErrorCode() != 0) { + throw new WxErrorException(error); } + return WxMaVodSingleFileUploadResult.fromJson(responseContent); } } diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/executor/ApacheVodUploadPartRequestExecutor.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/executor/ApacheVodUploadPartRequestExecutor.java index e27840cd59..f6c1ec36b6 100644 --- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/executor/ApacheVodUploadPartRequestExecutor.java +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/executor/ApacheVodUploadPartRequestExecutor.java @@ -8,7 +8,6 @@ import me.chanjar.weixin.common.util.http.apache.Utf8ResponseHandler; import org.apache.http.HttpHost; import org.apache.http.client.config.RequestConfig; -import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpPost; import org.apache.http.entity.mime.HttpMultipartMode; import org.apache.http.entity.mime.MultipartEntityBuilder; @@ -22,7 +21,7 @@ */ public class ApacheVodUploadPartRequestExecutor extends VodUploadPartRequestExecutor { - public ApacheVodUploadPartRequestExecutor(RequestHttp requestHttp, String uploadId, Integer partNumber, Integer resourceType) { + public ApacheVodUploadPartRequestExecutor(RequestHttp requestHttp, String uploadId, Integer partNumber, Integer resourceType) { super(requestHttp, uploadId, partNumber, resourceType); } @@ -45,15 +44,12 @@ public WxMaVodUploadPartResult execute(String uri, File file, WxType wxType) thr httpPost.setEntity(entityBuilder.build()); } - try (CloseableHttpResponse response = requestHttp.getRequestHttpClient().execute(httpPost)) { - String responseContent = Utf8ResponseHandler.INSTANCE.handleResponse(response); - WxError error = WxError.fromJson(responseContent, wxType); - if (error.getErrorCode() != 0) { - throw new WxErrorException(error); - } - return WxMaVodUploadPartResult.fromJson(responseContent); - } finally { - httpPost.releaseConnection(); + + String responseContent = requestHttp.getRequestHttpClient().execute(httpPost, Utf8ResponseHandler.INSTANCE); + WxError error = WxError.fromJson(responseContent, wxType); + if (error.getErrorCode() != 0) { + throw new WxErrorException(error); } + return WxMaVodUploadPartResult.fromJson(responseContent); } } diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/executor/ApiSignaturePostRequestExecutor.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/executor/ApiSignaturePostRequestExecutor.java index 8e3ade961e..c01a7ab5de 100644 --- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/executor/ApiSignaturePostRequestExecutor.java +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/executor/ApiSignaturePostRequestExecutor.java @@ -1,23 +1,28 @@ package cn.binarywang.wx.miniapp.executor; import cn.binarywang.wx.miniapp.bean.WxMaApiResponse; -import java.io.IOException; -import java.rmi.RemoteException; -import java.util.Map; +import jodd.http.HttpConnectionProvider; +import jodd.http.ProxyInfo; import me.chanjar.weixin.common.enums.WxType; import me.chanjar.weixin.common.error.WxError; import me.chanjar.weixin.common.error.WxErrorException; import me.chanjar.weixin.common.util.http.RequestExecutor; import me.chanjar.weixin.common.util.http.RequestHttp; import me.chanjar.weixin.common.util.http.ResponseHandler; +import me.chanjar.weixin.common.util.http.okhttp.OkHttpProxyInfo; +import okhttp3.OkHttpClient; import org.jetbrains.annotations.NotNull; +import java.io.IOException; +import java.rmi.RemoteException; +import java.util.Map; + public abstract class ApiSignaturePostRequestExecutor implements RequestExecutor { protected RequestHttp requestHttp; - public ApiSignaturePostRequestExecutor(RequestHttp requestHttp) { + public ApiSignaturePostRequestExecutor(RequestHttp requestHttp) { this.requestHttp = requestHttp; } @@ -54,16 +59,21 @@ public WxMaApiResponse handleResponse( return response; } - public static ApiSignaturePostRequestExecutor create(RequestHttp requestHttp) { + @SuppressWarnings("unchecked") + public static ApiSignaturePostRequestExecutor create(RequestHttp requestHttp) { switch (requestHttp.getRequestType()) { case APACHE_HTTP: - return new ApacheApiSignaturePostRequestExecutor(requestHttp); + return new ApacheApiSignaturePostRequestExecutor( + (RequestHttp) requestHttp); case JODD_HTTP: - return new JoddApiSignaturePostRequestExecutor(requestHttp); + return new JoddApiSignaturePostRequestExecutor((RequestHttp) requestHttp); case OK_HTTP: - return new OkHttpApiSignaturePostRequestExecutor(requestHttp); + return new OkHttpApiSignaturePostRequestExecutor((RequestHttp) requestHttp); + case HTTP_COMPONENTS: + return new HttpComponentsApiSignaturePostRequestExecutor( + (RequestHttp) requestHttp); default: - throw new IllegalArgumentException("非法请求参数"); + throw new IllegalArgumentException("不支持的http执行器类型:" + requestHttp.getRequestType()); } } } diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/executor/HttpComponentsApiSignaturePostRequestExecutor.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/executor/HttpComponentsApiSignaturePostRequestExecutor.java new file mode 100644 index 0000000000..23d2231855 --- /dev/null +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/executor/HttpComponentsApiSignaturePostRequestExecutor.java @@ -0,0 +1,63 @@ +package cn.binarywang.wx.miniapp.executor; + +import cn.binarywang.wx.miniapp.bean.WxMaApiResponse; +import me.chanjar.weixin.common.enums.WxType; +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.common.util.http.RequestHttp; +import me.chanjar.weixin.common.util.http.hc.Utf8ResponseHandler; +import org.apache.hc.client5.http.ClientProtocolException; +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.client5.http.impl.classic.CloseableHttpResponse; +import org.apache.hc.core5.http.ContentType; +import org.apache.hc.core5.http.Header; +import org.apache.hc.core5.http.HttpException; +import org.apache.hc.core5.http.HttpHost; +import org.apache.hc.core5.http.io.entity.StringEntity; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.Map; + +public class HttpComponentsApiSignaturePostRequestExecutor extends ApiSignaturePostRequestExecutor { + + public HttpComponentsApiSignaturePostRequestExecutor(RequestHttp requestHttp) { + super(requestHttp); + } + + @Override + public WxMaApiResponse execute( + String uri, Map headers, String postEntity, WxType wxType) + throws WxErrorException, IOException { + HttpPost httpPost = new HttpPost(uri); + if (requestHttp.getRequestHttpProxy() != null) { + RequestConfig config = RequestConfig.custom().setProxy(requestHttp.getRequestHttpProxy()).build(); + httpPost.setConfig(config); + } + + if (headers != null) { + headers.forEach(httpPost::addHeader); + } + + if (postEntity != null) { + StringEntity entity = new StringEntity(postEntity, ContentType.APPLICATION_JSON.withCharset(StandardCharsets.UTF_8)); + httpPost.setEntity(entity); + } + + try (CloseableHttpResponse response = requestHttp.getRequestHttpClient().execute(httpPost)) { + String responseContent = Utf8ResponseHandler.INSTANCE.handleResponse(response); + Map respHeaders = new HashMap<>(); + Header[] rHeaders = response.getHeaders(); + if (rHeaders != null) { + for (Header h : rHeaders) { + respHeaders.putIfAbsent(h.getName(), h.getValue()); + } + } + return this.handleResponse(wxType, responseContent, respHeaders); + } catch (HttpException httpException) { + throw new ClientProtocolException(httpException.getMessage(), httpException); + } + } +} diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/executor/HttpComponentsQrcodeBytesRequestExecutor.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/executor/HttpComponentsQrcodeBytesRequestExecutor.java new file mode 100644 index 0000000000..655296fdaf --- /dev/null +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/executor/HttpComponentsQrcodeBytesRequestExecutor.java @@ -0,0 +1,70 @@ +package cn.binarywang.wx.miniapp.executor; + +import cn.binarywang.wx.miniapp.bean.AbstractWxMaQrcodeWrapper; +import me.chanjar.weixin.common.enums.WxType; +import me.chanjar.weixin.common.error.WxError; +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.common.util.http.RequestHttp; +import me.chanjar.weixin.common.util.http.hc.InputStreamResponseHandler; +import me.chanjar.weixin.common.util.http.hc.Utf8ResponseHandler; +import org.apache.commons.io.IOUtils; +import org.apache.hc.client5.http.ClientProtocolException; +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.client5.http.impl.classic.CloseableHttpResponse; +import org.apache.hc.core5.http.ContentType; +import org.apache.hc.core5.http.Header; +import org.apache.hc.core5.http.HttpException; +import org.apache.hc.core5.http.HttpHost; +import org.apache.hc.core5.http.io.entity.StringEntity; + +import java.io.IOException; +import java.io.InputStream; + +/** + * @author altusea + */ +public class HttpComponentsQrcodeBytesRequestExecutor extends QrcodeBytesRequestExecutor { + + public HttpComponentsQrcodeBytesRequestExecutor(RequestHttp requestHttp) { + super(requestHttp); + } + + /** + * 执行http请求. + * + * @param uri uri + * @param qrcodeWrapper 数据 + * @param wxType 微信模块类型 + * @return 响应结果 + * @throws WxErrorException 自定义异常 + * @throws IOException io异常 + */ + @Override + public byte[] execute(String uri, AbstractWxMaQrcodeWrapper qrcodeWrapper, WxType wxType) throws WxErrorException, IOException { + HttpPost httpPost = new HttpPost(uri); + if (requestHttp.getRequestHttpProxy() != null) { + httpPost.setConfig( + RequestConfig.custom().setProxy(requestHttp.getRequestHttpProxy()).build() + ); + } + + httpPost.setEntity(new StringEntity(qrcodeWrapper.toJson())); + + try (final CloseableHttpResponse response = requestHttp.getRequestHttpClient().execute(httpPost); + final InputStream inputStream = InputStreamResponseHandler.INSTANCE.handleResponse(response)) { + Header[] contentTypeHeader = response.getHeaders("Content-Type"); + if (contentTypeHeader != null && contentTypeHeader.length > 0 + && ContentType.APPLICATION_JSON.getMimeType() + .equals(ContentType.parse(contentTypeHeader[0].getValue()).getMimeType())) { + String responseContent = Utf8ResponseHandler.INSTANCE.handleResponse(response); + throw new WxErrorException(WxError.fromJson(responseContent, wxType)); + } + + return IOUtils.toByteArray(inputStream); + } catch (HttpException httpException) { + throw new ClientProtocolException(httpException.getMessage(), httpException); + } + } +} diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/executor/HttpComponentsQrcodeFileRequestExecutor.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/executor/HttpComponentsQrcodeFileRequestExecutor.java new file mode 100644 index 0000000000..10d01b1cfd --- /dev/null +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/executor/HttpComponentsQrcodeFileRequestExecutor.java @@ -0,0 +1,79 @@ +package cn.binarywang.wx.miniapp.executor; + +import cn.binarywang.wx.miniapp.bean.AbstractWxMaQrcodeWrapper; +import me.chanjar.weixin.common.enums.WxType; +import me.chanjar.weixin.common.error.WxError; +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.common.util.fs.FileUtils; +import me.chanjar.weixin.common.util.http.RequestHttp; +import me.chanjar.weixin.common.util.http.hc.InputStreamResponseHandler; +import me.chanjar.weixin.common.util.http.hc.Utf8ResponseHandler; +import org.apache.commons.lang3.StringUtils; +import org.apache.hc.client5.http.ClientProtocolException; +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.client5.http.impl.classic.CloseableHttpResponse; +import org.apache.hc.core5.http.ContentType; +import org.apache.hc.core5.http.Header; +import org.apache.hc.core5.http.HttpException; +import org.apache.hc.core5.http.HttpHost; +import org.apache.hc.core5.http.io.entity.StringEntity; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Paths; +import java.util.UUID; + +/** + * @author altusea + */ +public class HttpComponentsQrcodeFileRequestExecutor extends QrcodeRequestExecutor { + + private final String filePath; + + public HttpComponentsQrcodeFileRequestExecutor(RequestHttp requestHttp, String filePath) { + super(requestHttp); + this.filePath = filePath; + } + + /** + * 执行http请求. + * + * @param uri uri + * @param qrcodeWrapper 数据 + * @param wxType 微信模块类型 + * @return 响应结果 + * @throws WxErrorException 自定义异常 + * @throws IOException io异常 + */ + @Override + public File execute(String uri, AbstractWxMaQrcodeWrapper qrcodeWrapper, WxType wxType) throws WxErrorException, IOException { + HttpPost httpPost = new HttpPost(uri); + if (requestHttp.getRequestHttpProxy() != null) { + httpPost.setConfig( + RequestConfig.custom().setProxy(requestHttp.getRequestHttpProxy()).build() + ); + } + + httpPost.setEntity(new StringEntity(qrcodeWrapper.toJson(), ContentType.APPLICATION_JSON)); + + try (final CloseableHttpResponse response = requestHttp.getRequestHttpClient().execute(httpPost); + final InputStream inputStream = InputStreamResponseHandler.INSTANCE.handleResponse(response)) { + Header[] contentTypeHeader = response.getHeaders("Content-Type"); + if (contentTypeHeader != null && contentTypeHeader.length > 0 + && ContentType.APPLICATION_JSON.getMimeType() + .equals(ContentType.parse(contentTypeHeader[0].getValue()).getMimeType())) { + String responseContent = Utf8ResponseHandler.INSTANCE.handleResponse(response); + throw new WxErrorException(WxError.fromJson(responseContent, wxType)); + } + if (StringUtils.isBlank(filePath)) { + return FileUtils.createTmpFile(inputStream, UUID.randomUUID().toString(), "jpg"); + } + return FileUtils.createTmpFile(inputStream, UUID.randomUUID().toString(), "jpg", Paths.get(filePath).toFile()); + } catch (HttpException httpException) { + throw new ClientProtocolException(httpException.getMessage(), httpException); + } + } +} diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/executor/HttpComponentsUploadAuthMaterialRequestExecutor.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/executor/HttpComponentsUploadAuthMaterialRequestExecutor.java new file mode 100644 index 0000000000..8bfed3b5fa --- /dev/null +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/executor/HttpComponentsUploadAuthMaterialRequestExecutor.java @@ -0,0 +1,51 @@ +package cn.binarywang.wx.miniapp.executor; + +import cn.binarywang.wx.miniapp.bean.WxMaUploadAuthMaterialResult; +import me.chanjar.weixin.common.enums.WxType; +import me.chanjar.weixin.common.error.WxError; +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.common.util.http.RequestHttp; +import me.chanjar.weixin.common.util.http.hc.Utf8ResponseHandler; +import org.apache.hc.client5.http.classic.methods.HttpPost; +import org.apache.hc.client5.http.config.RequestConfig; +import org.apache.hc.client5.http.entity.mime.HttpMultipartMode; +import org.apache.hc.client5.http.entity.mime.MultipartEntityBuilder; +import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; +import org.apache.hc.core5.http.HttpEntity; +import org.apache.hc.core5.http.HttpHost; + +import java.io.File; +import java.io.IOException; + +/** + * @author altusea + */ +public class HttpComponentsUploadAuthMaterialRequestExecutor extends UploadAuthMaterialRequestExecutor { + + public HttpComponentsUploadAuthMaterialRequestExecutor(RequestHttp requestHttp) { + super(requestHttp); + } + + @Override + public WxMaUploadAuthMaterialResult execute(String uri, File file, WxType wxType) throws WxErrorException, IOException { + HttpPost httpPost = new HttpPost(uri); + if (requestHttp.getRequestHttpProxy() != null) { + RequestConfig config = RequestConfig.custom().setProxy(requestHttp.getRequestHttpProxy()).build(); + httpPost.setConfig(config); + } + if (file != null) { + HttpEntity entity = MultipartEntityBuilder + .create() + .addBinaryBody("media", file) + .setMode(HttpMultipartMode.EXTENDED) + .build(); + httpPost.setEntity(entity); + } + String responseContent = requestHttp.getRequestHttpClient().execute(httpPost, Utf8ResponseHandler.INSTANCE); + WxError error = WxError.fromJson(responseContent, wxType); + if (error.getErrorCode() != 0) { + throw new WxErrorException(error); + } + return WxMaUploadAuthMaterialResult.fromJson(responseContent); + } +} diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/executor/HttpComponentsVodSingleUploadRequestExecutor.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/executor/HttpComponentsVodSingleUploadRequestExecutor.java new file mode 100644 index 0000000000..3f9139d459 --- /dev/null +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/executor/HttpComponentsVodSingleUploadRequestExecutor.java @@ -0,0 +1,59 @@ +package cn.binarywang.wx.miniapp.executor; + +import cn.binarywang.wx.miniapp.bean.vod.WxMaVodSingleFileUploadResult; +import me.chanjar.weixin.common.enums.WxType; +import me.chanjar.weixin.common.error.WxError; +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.common.util.http.RequestHttp; +import me.chanjar.weixin.common.util.http.hc.Utf8ResponseHandler; +import org.apache.hc.client5.http.classic.methods.HttpPost; +import org.apache.hc.client5.http.config.RequestConfig; +import org.apache.hc.client5.http.entity.mime.HttpMultipartMode; +import org.apache.hc.client5.http.entity.mime.MultipartEntityBuilder; +import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; +import org.apache.hc.core5.http.HttpHost; + +import java.io.File; +import java.io.IOException; + +public class HttpComponentsVodSingleUploadRequestExecutor extends VodSingleUploadRequestExecutor { + + public HttpComponentsVodSingleUploadRequestExecutor(RequestHttp requestHttp, String mediaName, String mediaType, String coverType, File coverData, String sourceContext) { + super(requestHttp, mediaName, mediaType, coverType, coverData, sourceContext); + } + + @Override + public WxMaVodSingleFileUploadResult execute(String uri, File file, WxType wxType) throws WxErrorException, IOException { + HttpPost httpPost = new HttpPost(uri); + if (requestHttp.getRequestHttpProxy() != null) { + RequestConfig config = RequestConfig.custom().setProxy(requestHttp.getRequestHttpProxy()).build(); + httpPost.setConfig(config); + } + if (file != null) { + MultipartEntityBuilder entityBuilder = MultipartEntityBuilder + .create() + .setMode(HttpMultipartMode.EXTENDED) + .addTextBody("media_name", mediaName) + .addTextBody("media_type", mediaType) + .addBinaryBody("media_data", file); + + if (coverType != null) { + entityBuilder.addTextBody("cover_type", coverType); + } + if (coverData != null) { + entityBuilder.addBinaryBody("cover_data", coverData); + } + if (sourceContext != null) { + entityBuilder.addTextBody("source_context", sourceContext); + } + + httpPost.setEntity(entityBuilder.build()); + } + String responseContent = requestHttp.getRequestHttpClient().execute(httpPost, Utf8ResponseHandler.INSTANCE); + WxError error = WxError.fromJson(responseContent, wxType); + if (error.getErrorCode() != 0) { + throw new WxErrorException(error); + } + return WxMaVodSingleFileUploadResult.fromJson(responseContent); + } +} diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/executor/HttpComponentsVodUploadPartRequestExecutor.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/executor/HttpComponentsVodUploadPartRequestExecutor.java new file mode 100644 index 0000000000..eb2cf8e9db --- /dev/null +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/executor/HttpComponentsVodUploadPartRequestExecutor.java @@ -0,0 +1,52 @@ +package cn.binarywang.wx.miniapp.executor; + +import cn.binarywang.wx.miniapp.bean.vod.WxMaVodUploadPartResult; +import me.chanjar.weixin.common.enums.WxType; +import me.chanjar.weixin.common.error.WxError; +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.common.util.http.RequestHttp; +import me.chanjar.weixin.common.util.http.hc.Utf8ResponseHandler; +import org.apache.hc.client5.http.classic.methods.HttpPost; +import org.apache.hc.client5.http.config.RequestConfig; +import org.apache.hc.client5.http.entity.mime.HttpMultipartMode; +import org.apache.hc.client5.http.entity.mime.MultipartEntityBuilder; +import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; +import org.apache.hc.core5.http.HttpHost; + +import java.io.File; +import java.io.IOException; + +public class HttpComponentsVodUploadPartRequestExecutor extends VodUploadPartRequestExecutor { + + public HttpComponentsVodUploadPartRequestExecutor(RequestHttp requestHttp, String uploadId, Integer partNumber, Integer resourceType) { + super(requestHttp, uploadId, partNumber, resourceType); + + } + + @Override + public WxMaVodUploadPartResult execute(String uri, File file, WxType wxType) throws WxErrorException, IOException { + HttpPost httpPost = new HttpPost(uri); + if (requestHttp.getRequestHttpProxy() != null) { + RequestConfig config = RequestConfig.custom().setProxy(requestHttp.getRequestHttpProxy()).build(); + httpPost.setConfig(config); + } + if (file != null) { + MultipartEntityBuilder entityBuilder = MultipartEntityBuilder + .create() + .setMode(HttpMultipartMode.EXTENDED) + .addTextBody("upload_id", uploadId) + .addTextBody("part_number", String.valueOf(partNumber)) + .addTextBody("resource_type", String.valueOf(resourceType)) + .addBinaryBody("data", file); + + httpPost.setEntity(entityBuilder.build()); + } + + String responseContent = requestHttp.getRequestHttpClient().execute(httpPost, Utf8ResponseHandler.INSTANCE); + WxError error = WxError.fromJson(responseContent, wxType); + if (error.getErrorCode() != 0) { + throw new WxErrorException(error); + } + return WxMaVodUploadPartResult.fromJson(responseContent); + } +} diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/executor/JoddApiSignaturePostRequestExecutor.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/executor/JoddApiSignaturePostRequestExecutor.java index b7568bc21d..d8724a6ac8 100644 --- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/executor/JoddApiSignaturePostRequestExecutor.java +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/executor/JoddApiSignaturePostRequestExecutor.java @@ -20,7 +20,7 @@ public class JoddApiSignaturePostRequestExecutor private static final Logger logger = LoggerFactory.getLogger(JoddApiSignaturePostRequestExecutor.class); - public JoddApiSignaturePostRequestExecutor(RequestHttp requestHttp) { + public JoddApiSignaturePostRequestExecutor(RequestHttp requestHttp) { super(requestHttp); } diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/executor/JoddHttpQrcodeFileRequestExecutor.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/executor/JoddHttpQrcodeFileRequestExecutor.java index d63e29c5d4..b121932d74 100644 --- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/executor/JoddHttpQrcodeFileRequestExecutor.java +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/executor/JoddHttpQrcodeFileRequestExecutor.java @@ -11,8 +11,6 @@ import me.chanjar.weixin.common.error.WxErrorException; import me.chanjar.weixin.common.util.fs.FileUtils; import me.chanjar.weixin.common.util.http.RequestHttp; -import me.chanjar.weixin.common.util.http.okhttp.OkHttpProxyInfo; -import okhttp3.*; import org.apache.commons.lang3.StringUtils; import java.io.ByteArrayInputStream; diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/executor/JoddHttpUploadAuthMaterialRequestExecutor.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/executor/JoddHttpUploadAuthMaterialRequestExecutor.java index cff63972e3..874a96f2c4 100644 --- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/executor/JoddHttpUploadAuthMaterialRequestExecutor.java +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/executor/JoddHttpUploadAuthMaterialRequestExecutor.java @@ -20,7 +20,7 @@ */ public class JoddHttpUploadAuthMaterialRequestExecutor extends UploadAuthMaterialRequestExecutor { - public JoddHttpUploadAuthMaterialRequestExecutor(RequestHttp requestHttp) { + public JoddHttpUploadAuthMaterialRequestExecutor(RequestHttp requestHttp) { super(requestHttp); } diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/executor/JoddHttpVodSingleUploadRequestExecutor.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/executor/JoddHttpVodSingleUploadRequestExecutor.java index ed47a9fc67..cb71076c60 100644 --- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/executor/JoddHttpVodSingleUploadRequestExecutor.java +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/executor/JoddHttpVodSingleUploadRequestExecutor.java @@ -19,7 +19,7 @@ */ public class JoddHttpVodSingleUploadRequestExecutor extends VodSingleUploadRequestExecutor { - public JoddHttpVodSingleUploadRequestExecutor(RequestHttp requestHttp, String mediaName, String mediaType, String coverType, File coverData, String sourceContext) { + public JoddHttpVodSingleUploadRequestExecutor(RequestHttp requestHttp, String mediaName, String mediaType, String coverType, File coverData, String sourceContext) { super(requestHttp, mediaName, mediaType, coverType, coverData, sourceContext); } diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/executor/JoddHttpVodUploadPartRequestExecutor.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/executor/JoddHttpVodUploadPartRequestExecutor.java index 36e53b66bf..e86a1d5b98 100644 --- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/executor/JoddHttpVodUploadPartRequestExecutor.java +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/executor/JoddHttpVodUploadPartRequestExecutor.java @@ -19,7 +19,7 @@ */ public class JoddHttpVodUploadPartRequestExecutor extends VodUploadPartRequestExecutor { - public JoddHttpVodUploadPartRequestExecutor(RequestHttp requestHttp, String uploadId, Integer partNumber, Integer resourceType) { + public JoddHttpVodUploadPartRequestExecutor(RequestHttp requestHttp, String uploadId, Integer partNumber, Integer resourceType) { super(requestHttp, uploadId, partNumber, resourceType); } diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/executor/OkHttpApiSignaturePostRequestExecutor.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/executor/OkHttpApiSignaturePostRequestExecutor.java index 10c75a26bd..f9d1262821 100644 --- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/executor/OkHttpApiSignaturePostRequestExecutor.java +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/executor/OkHttpApiSignaturePostRequestExecutor.java @@ -18,7 +18,7 @@ public class OkHttpApiSignaturePostRequestExecutor private static final Logger logger = LoggerFactory.getLogger(OkHttpApiSignaturePostRequestExecutor.class); - public OkHttpApiSignaturePostRequestExecutor(RequestHttp requestHttp) { + public OkHttpApiSignaturePostRequestExecutor(RequestHttp requestHttp) { super(requestHttp); } diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/executor/OkHttpUploadAuthMaterialRequestExecutor.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/executor/OkHttpUploadAuthMaterialRequestExecutor.java index 698fb78894..67d0d99b3f 100644 --- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/executor/OkHttpUploadAuthMaterialRequestExecutor.java +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/executor/OkHttpUploadAuthMaterialRequestExecutor.java @@ -17,7 +17,7 @@ */ public class OkHttpUploadAuthMaterialRequestExecutor extends UploadAuthMaterialRequestExecutor { - public OkHttpUploadAuthMaterialRequestExecutor(RequestHttp requestHttp) { + public OkHttpUploadAuthMaterialRequestExecutor(RequestHttp requestHttp) { super(requestHttp); } diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/executor/OkHttpVodSingleUploadRequestExecutor.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/executor/OkHttpVodSingleUploadRequestExecutor.java index 78fbdd3d25..d6e8a6880f 100644 --- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/executor/OkHttpVodSingleUploadRequestExecutor.java +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/executor/OkHttpVodSingleUploadRequestExecutor.java @@ -16,7 +16,7 @@ */ public class OkHttpVodSingleUploadRequestExecutor extends VodSingleUploadRequestExecutor { - public OkHttpVodSingleUploadRequestExecutor(RequestHttp requestHttp, String mediaName, String mediaType, String coverType, File coverData, String sourceContext) { + public OkHttpVodSingleUploadRequestExecutor(RequestHttp requestHttp, String mediaName, String mediaType, String coverType, File coverData, String sourceContext) { super(requestHttp, mediaName, mediaType, coverType, coverData, sourceContext); } diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/executor/OkHttpVodUploadPartRequestExecutor.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/executor/OkHttpVodUploadPartRequestExecutor.java index c9e991d082..59d4aa932d 100644 --- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/executor/OkHttpVodUploadPartRequestExecutor.java +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/executor/OkHttpVodUploadPartRequestExecutor.java @@ -16,7 +16,7 @@ */ public class OkHttpVodUploadPartRequestExecutor extends VodUploadPartRequestExecutor { - public OkHttpVodUploadPartRequestExecutor(RequestHttp requestHttp, String uploadId, Integer partNumber, Integer resourceType) { + public OkHttpVodUploadPartRequestExecutor(RequestHttp requestHttp, String uploadId, Integer partNumber, Integer resourceType) { super(requestHttp, uploadId, partNumber, resourceType); } diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/executor/QrcodeBytesRequestExecutor.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/executor/QrcodeBytesRequestExecutor.java index a4a5112565..4d95a6daae 100644 --- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/executor/QrcodeBytesRequestExecutor.java +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/executor/QrcodeBytesRequestExecutor.java @@ -6,6 +6,8 @@ import me.chanjar.weixin.common.util.http.RequestExecutor; import me.chanjar.weixin.common.util.http.RequestHttp; import me.chanjar.weixin.common.util.http.ResponseHandler; +import me.chanjar.weixin.common.util.http.okhttp.OkHttpProxyInfo; +import okhttp3.OkHttpClient; import java.io.IOException; @@ -16,7 +18,7 @@ public abstract class QrcodeBytesRequestExecutor implements RequestExecuto protected RequestHttp requestHttp; - public QrcodeBytesRequestExecutor(RequestHttp requestHttp) { + public QrcodeBytesRequestExecutor(RequestHttp requestHttp) { this.requestHttp = requestHttp; } @@ -25,16 +27,19 @@ public void execute(String uri, AbstractWxMaQrcodeWrapper data, ResponseHandler< handler.handle(this.execute(uri, data, wxType)); } - public static RequestExecutor create(RequestHttp requestHttp) { + @SuppressWarnings("unchecked") + public static RequestExecutor create(RequestHttp requestHttp) { switch (requestHttp.getRequestType()) { case APACHE_HTTP: - return new ApacheQrcodeBytesRequestExecutor(requestHttp); - case JODD_HTTP: - return null; + return new ApacheQrcodeBytesRequestExecutor( + (RequestHttp) requestHttp); case OK_HTTP: - return new OkHttpQrcodeBytesRequestExecutor(requestHttp); + return new OkHttpQrcodeBytesRequestExecutor((RequestHttp) requestHttp); + case HTTP_COMPONENTS: + return new HttpComponentsQrcodeBytesRequestExecutor( + (RequestHttp) requestHttp); default: - return null; + throw new IllegalArgumentException("不支持的http执行器类型:" + requestHttp.getRequestType()); } } } diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/executor/QrcodeRequestExecutor.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/executor/QrcodeRequestExecutor.java index bf85004ac5..ec1d0fd158 100644 --- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/executor/QrcodeRequestExecutor.java +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/executor/QrcodeRequestExecutor.java @@ -6,6 +6,10 @@ import me.chanjar.weixin.common.util.http.RequestExecutor; import me.chanjar.weixin.common.util.http.RequestHttp; import me.chanjar.weixin.common.util.http.ResponseHandler; +import me.chanjar.weixin.common.util.http.okhttp.OkHttpProxyInfo; +import okhttp3.OkHttpClient; +import org.apache.http.HttpHost; +import org.apache.http.impl.client.CloseableHttpClient; import java.io.File; import java.io.IOException; @@ -25,29 +29,34 @@ public void execute(String uri, AbstractWxMaQrcodeWrapper data, ResponseHandler< handler.handle(this.execute(uri, data, wxType)); } - - public static RequestExecutor create(RequestHttp requestHttp, String path) { + @SuppressWarnings("unchecked") + public static RequestExecutor create(RequestHttp requestHttp, String path) { switch (requestHttp.getRequestType()) { case APACHE_HTTP: - return new ApacheQrcodeFileRequestExecutor(requestHttp, path); + return new ApacheQrcodeFileRequestExecutor( + (RequestHttp) requestHttp, path); case OK_HTTP: - return new OkHttpQrcodeFileRequestExecutor(requestHttp, path); - case JODD_HTTP: + return new OkHttpQrcodeFileRequestExecutor((RequestHttp) requestHttp, path); + case HTTP_COMPONENTS: + return new HttpComponentsQrcodeFileRequestExecutor( + (RequestHttp) requestHttp, path); default: - return null; + throw new IllegalArgumentException("不支持的http执行器类型:" + requestHttp.getRequestType()); } } - public static RequestExecutor create(RequestHttp requestHttp) { + @SuppressWarnings("unchecked") + public static RequestExecutor create(RequestHttp requestHttp) { switch (requestHttp.getRequestType()) { case APACHE_HTTP: - return new ApacheQrcodeFileRequestExecutor(requestHttp, null); - case JODD_HTTP: - return null; + return new ApacheQrcodeFileRequestExecutor((RequestHttp) requestHttp, null); case OK_HTTP: - return new OkHttpQrcodeFileRequestExecutor(requestHttp, null); + return new OkHttpQrcodeFileRequestExecutor((RequestHttp) requestHttp, null); + case HTTP_COMPONENTS: + return new HttpComponentsQrcodeFileRequestExecutor( + (RequestHttp) requestHttp, null); default: - return null; + throw new IllegalArgumentException("不支持的http执行器类型:" + requestHttp.getRequestType()); } } } diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/executor/UploadAuthMaterialRequestExecutor.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/executor/UploadAuthMaterialRequestExecutor.java index 35bdcd9ed1..4d232ced21 100644 --- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/executor/UploadAuthMaterialRequestExecutor.java +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/executor/UploadAuthMaterialRequestExecutor.java @@ -1,11 +1,15 @@ package cn.binarywang.wx.miniapp.executor; import cn.binarywang.wx.miniapp.bean.WxMaUploadAuthMaterialResult; +import jodd.http.HttpConnectionProvider; +import jodd.http.ProxyInfo; import me.chanjar.weixin.common.enums.WxType; import me.chanjar.weixin.common.error.WxErrorException; import me.chanjar.weixin.common.util.http.RequestExecutor; import me.chanjar.weixin.common.util.http.RequestHttp; import me.chanjar.weixin.common.util.http.ResponseHandler; +import me.chanjar.weixin.common.util.http.okhttp.OkHttpProxyInfo; +import okhttp3.OkHttpClient; import java.io.File; import java.io.IOException; @@ -19,27 +23,32 @@ * @since 2024/01/07 */ public abstract class UploadAuthMaterialRequestExecutor implements RequestExecutor { - protected RequestHttp requestHttp; + protected RequestHttp requestHttp; - public UploadAuthMaterialRequestExecutor(RequestHttp requestHttp) { - this.requestHttp = requestHttp; - } + public UploadAuthMaterialRequestExecutor(RequestHttp requestHttp) { + this.requestHttp = requestHttp; + } - @Override - public void execute(String uri, File data, ResponseHandler handler, WxType wxType) throws WxErrorException, IOException { - handler.handle(this.execute(uri, data, wxType)); - } + @Override + public void execute(String uri, File data, ResponseHandler handler, WxType wxType) throws WxErrorException, IOException { + handler.handle(this.execute(uri, data, wxType)); + } - public static RequestExecutor create(RequestHttp requestHttp) { - switch (requestHttp.getRequestType()) { - case APACHE_HTTP: - return new ApacheUploadAuthMaterialRequestExecutor(requestHttp); - case JODD_HTTP: - return new JoddHttpUploadAuthMaterialRequestExecutor(requestHttp); - case OK_HTTP: - return new OkHttpUploadAuthMaterialRequestExecutor(requestHttp); - default: - return null; - } + @SuppressWarnings("unchecked") + public static RequestExecutor create(RequestHttp requestHttp) { + switch (requestHttp.getRequestType()) { + case APACHE_HTTP: + return new ApacheUploadAuthMaterialRequestExecutor( + (RequestHttp) requestHttp); + case JODD_HTTP: + return new JoddHttpUploadAuthMaterialRequestExecutor((RequestHttp) requestHttp); + case OK_HTTP: + return new OkHttpUploadAuthMaterialRequestExecutor((RequestHttp) requestHttp); + case HTTP_COMPONENTS: + return new HttpComponentsUploadAuthMaterialRequestExecutor( + (RequestHttp) requestHttp); + default: + throw new IllegalArgumentException("不支持的http执行器类型:" + requestHttp.getRequestType()); } + } } diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/executor/VodSingleUploadRequestExecutor.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/executor/VodSingleUploadRequestExecutor.java index 40c73b0064..578fc8949c 100644 --- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/executor/VodSingleUploadRequestExecutor.java +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/executor/VodSingleUploadRequestExecutor.java @@ -1,11 +1,15 @@ package cn.binarywang.wx.miniapp.executor; import cn.binarywang.wx.miniapp.bean.vod.WxMaVodSingleFileUploadResult; +import jodd.http.HttpConnectionProvider; +import jodd.http.ProxyInfo; import me.chanjar.weixin.common.enums.WxType; import me.chanjar.weixin.common.error.WxErrorException; import me.chanjar.weixin.common.util.http.RequestExecutor; import me.chanjar.weixin.common.util.http.RequestHttp; import me.chanjar.weixin.common.util.http.ResponseHandler; +import me.chanjar.weixin.common.util.http.okhttp.OkHttpProxyInfo; +import okhttp3.OkHttpClient; import java.io.File; import java.io.IOException; @@ -27,7 +31,7 @@ public abstract class VodSingleUploadRequestExecutor implements RequestExe protected String sourceContext; protected File coverData; - public VodSingleUploadRequestExecutor(RequestHttp requestHttp, String mediaName, String mediaType, String coverType, File coverData, String sourceContext) { + public VodSingleUploadRequestExecutor(RequestHttp requestHttp, String mediaName, String mediaType, String coverType, File coverData, String sourceContext) { this.requestHttp = requestHttp; this.mediaName = mediaName; this.mediaType = mediaType; @@ -37,16 +41,24 @@ public VodSingleUploadRequestExecutor(RequestHttp requestHttp, String mediaName, } - public static RequestExecutor create(RequestHttp requestHttp, String mediaName, String mediaType, String coverType, File coverData, String sourceContext) { + @SuppressWarnings("unchecked") + public static RequestExecutor create(RequestHttp requestHttp, String mediaName, String mediaType, String coverType, File coverData, String sourceContext) { switch (requestHttp.getRequestType()) { case APACHE_HTTP: - return new ApacheVodSingleUploadRequestExecutor(requestHttp, mediaName, mediaType, coverType, coverData, sourceContext); + return new ApacheVodSingleUploadRequestExecutor( + (RequestHttp) requestHttp, + mediaName, mediaType, coverType, coverData, sourceContext); case JODD_HTTP: - return new JoddHttpVodSingleUploadRequestExecutor(requestHttp, mediaName, mediaType, coverType, coverData, sourceContext); + return new JoddHttpVodSingleUploadRequestExecutor((RequestHttp) requestHttp, mediaName, mediaType, coverType, coverData, sourceContext); case OK_HTTP: - return new OkHttpVodSingleUploadRequestExecutor(requestHttp, mediaName, mediaType, coverType, coverData, sourceContext); + return new OkHttpVodSingleUploadRequestExecutor( + (RequestHttp) requestHttp, mediaName, mediaType, coverType, coverData, sourceContext); + case HTTP_COMPONENTS: + return new HttpComponentsVodSingleUploadRequestExecutor( + (RequestHttp) requestHttp, + mediaName, mediaType, coverType, coverData, sourceContext); default: - return null; + throw new IllegalArgumentException("不支持的http执行器类型:" + requestHttp.getRequestType()); } } @@ -55,5 +67,4 @@ public void execute(String uri, File data, ResponseHandler implements RequestExecu protected Integer partNumber; protected Integer resourceType; - public VodUploadPartRequestExecutor(RequestHttp requestHttp, String uploadId, Integer partNumber, Integer resourceType) { + public VodUploadPartRequestExecutor(RequestHttp requestHttp, String uploadId, Integer partNumber, Integer resourceType) { this.requestHttp = requestHttp; this.uploadId = uploadId; this.partNumber = partNumber; @@ -27,16 +31,23 @@ public VodUploadPartRequestExecutor(RequestHttp requestHttp, String uploadId, In } - public static RequestExecutor create(RequestHttp requestHttp, String uploadId, Integer partNumber, Integer resourceType) { + @SuppressWarnings("unchecked") + public static RequestExecutor create(RequestHttp requestHttp, String uploadId, Integer partNumber, Integer resourceType) { switch (requestHttp.getRequestType()) { case APACHE_HTTP: - return new ApacheVodUploadPartRequestExecutor(requestHttp, uploadId, partNumber, resourceType); + return new ApacheVodUploadPartRequestExecutor( + (RequestHttp) requestHttp, + uploadId, partNumber, resourceType); case JODD_HTTP: - return new JoddHttpVodUploadPartRequestExecutor(requestHttp, uploadId, partNumber, resourceType); + return new JoddHttpVodUploadPartRequestExecutor((RequestHttp) requestHttp, uploadId, partNumber, resourceType); case OK_HTTP: - return new OkHttpVodUploadPartRequestExecutor(requestHttp, uploadId, partNumber, resourceType); + return new OkHttpVodUploadPartRequestExecutor((RequestHttp) requestHttp, uploadId, partNumber, resourceType); + case HTTP_COMPONENTS: + return new HttpComponentsVodUploadPartRequestExecutor( + (RequestHttp) requestHttp, + uploadId, partNumber, resourceType); default: - return null; + throw new IllegalArgumentException("不支持的http执行器类型:" + requestHttp.getRequestType()); } } @@ -45,5 +56,4 @@ public void execute(String uri, File data, ResponseHandler getAsMap(JsonObject object, String memberName) { JsonArray array = object.getAsJsonArray(memberName); - if (array != null && array.size() > 0) { + if (array != null && !array.isEmpty()) { Map map = new LinkedHashMap<>(array.size()); for (JsonElement element : array) { JsonObject elementObject = element.getAsJsonObject(); diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/json/adaptor/WxMaRetainInfoGsonAdapter.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/json/adaptor/WxMaRetainInfoGsonAdapter.java index 2e71f9eb4e..d316bbfeb1 100644 --- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/json/adaptor/WxMaRetainInfoGsonAdapter.java +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/json/adaptor/WxMaRetainInfoGsonAdapter.java @@ -35,7 +35,7 @@ public WxMaRetainInfo deserialize(JsonElement json, Type type, JsonDeserializati private Map getAsMap(JsonObject object, String memberName) { JsonArray array = object.getAsJsonArray(memberName); - if (array != null && array.size() > 0) { + if (array != null && !array.isEmpty()) { Map map = new LinkedHashMap<>(array.size()); for (JsonElement element : array) { JsonObject elementObject = element.getAsJsonObject(); diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/json/adaptor/WxMaSubscribeMsgEventJsonAdapter.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/json/adaptor/WxMaSubscribeMsgEventJsonAdapter.java index 377f8e35ef..f875be5a9e 100644 --- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/json/adaptor/WxMaSubscribeMsgEventJsonAdapter.java +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/json/adaptor/WxMaSubscribeMsgEventJsonAdapter.java @@ -24,7 +24,7 @@ public WxMaSubscribeMsgEvent.WxMaSubscribeMsgEventJson deserialize(JsonElement j WxMaSubscribeMsgEvent.WxMaSubscribeMsgEventJson result = new WxMaSubscribeMsgEvent.WxMaSubscribeMsgEventJson(); if (json.isJsonArray()) { JsonArray array = json.getAsJsonArray(); - if (array.size() > 0) { + if (!array.isEmpty()) { JsonObject obj = array.get(0).getAsJsonObject(); MsgEventTypeEnum eventType = detectMsgEventType(obj); for (int i = 0; i < array.size(); ++i) { diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/json/adaptor/WxMaUserPortraitGsonAdapter.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/json/adaptor/WxMaUserPortraitGsonAdapter.java index c99fd67ba3..edcc272ae1 100644 --- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/json/adaptor/WxMaUserPortraitGsonAdapter.java +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/json/adaptor/WxMaUserPortraitGsonAdapter.java @@ -50,7 +50,7 @@ private WxMaUserPortrait.Item getPortraitItem(JsonObject object) { private Map getAsMap(JsonObject object, String memberName) { JsonArray array = object.getAsJsonArray(memberName); - if (array != null && array.size() > 0) { + if (array != null && !array.isEmpty()) { Map map = new LinkedHashMap<>(array.size()); for (JsonElement element : array) { JsonObject elementObject = element.getAsJsonObject(); diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/message/WxMaMessageRouter.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/message/WxMaMessageRouter.java index 3d81b6d66a..fd369f517a 100644 --- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/message/WxMaMessageRouter.java +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/message/WxMaMessageRouter.java @@ -7,7 +7,6 @@ import lombok.Data; import me.chanjar.weixin.common.api.WxErrorExceptionHandler; import me.chanjar.weixin.common.api.WxMessageDuplicateChecker; -import me.chanjar.weixin.common.api.WxMessageInMemoryDuplicateChecker; import me.chanjar.weixin.common.api.WxMessageInMemoryDuplicateCheckerSingleton; import me.chanjar.weixin.common.session.InternalSession; import me.chanjar.weixin.common.session.InternalSessionManager; @@ -125,7 +124,7 @@ public WxMaXmlOutMessage route(final WxMaMessage wxMessage, final Map 0) { + if (!futures.isEmpty()) { this.executorService.submit(() -> { for (Future future : futures) { try { diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/util/WxMaConfigHolder.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/util/WxMaConfigHolder.java index 15dd8654c0..68bd6286d6 100644 --- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/util/WxMaConfigHolder.java +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/util/WxMaConfigHolder.java @@ -7,12 +7,7 @@ * created on 2020-08-16 */ public class WxMaConfigHolder { - private static final ThreadLocal THREAD_LOCAL = new ThreadLocal() { - @Override - protected String initialValue() { - return "default"; - } - }; + private static final ThreadLocal THREAD_LOCAL = ThreadLocal.withInitial(() -> "default"); public static String get() { return THREAD_LOCAL.get(); 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 0222265e44..08346dbbb8 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 @@ -10,8 +10,6 @@ import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; -import com.google.common.base.CharMatcher; -import com.google.common.io.BaseEncoding; import me.chanjar.weixin.common.error.WxRuntimeException; import org.apache.commons.codec.binary.Base64; import org.apache.commons.lang3.StringUtils; diff --git a/weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/api/impl/WxMaXPayServiceImplTest.java b/weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/api/impl/WxMaXPayServiceImplTest.java index 0c980fda55..1533f2a0b6 100644 --- a/weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/api/impl/WxMaXPayServiceImplTest.java +++ b/weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/api/impl/WxMaXPayServiceImplTest.java @@ -1,6 +1,7 @@ package cn.binarywang.wx.miniapp.api.impl; import cn.binarywang.wx.miniapp.api.WxMaService; +import cn.binarywang.wx.miniapp.bean.WxMaBaseResponse; import cn.binarywang.wx.miniapp.bean.xpay.*; import cn.binarywang.wx.miniapp.constant.WxMaConstants; import cn.binarywang.wx.miniapp.test.ApiTestModule; @@ -119,6 +120,7 @@ public void testDownloadBill() throws Exception { WxMaXPayDownloadBillResponse response = this.wxService.getWxMaXPayService().downloadBill(request, sigParams); assertNotNull(response); } + @Test public void testRefundOrder() throws Exception { WxMaXPayRefundOrderRequest request = WxMaXPayRefundOrderRequest.builder() @@ -217,4 +219,209 @@ public void testQueryPublishGoods() throws Exception { assertNotNull(response); } + @Test + public void testQueryBizBalance() throws Exception { + WxMaXPayQueryBizBalanceRequest request = WxMaXPayQueryBizBalanceRequest.builder() + .env(WxMaConstants.XPayEnv.PRODUCT) + .build(); + WxMaXPaySigParams sigParams = new WxMaXPaySigParams(); + sigParams.setAppKey(""); + WxMaXPayQueryBizBalanceResponse response = this.wxService.getWxMaXPayService().queryBizBalance(request, sigParams); + assertNotNull(response); + } + + @Test + public void testQueryTransferAccount() throws Exception { + WxMaXPayQueryTransferAccountRequest request = WxMaXPayQueryTransferAccountRequest.builder() + .env(WxMaConstants.XPayEnv.PRODUCT) + .build(); + WxMaXPaySigParams sigParams = new WxMaXPaySigParams(); + sigParams.setAppKey(""); + WxMaXPayQueryTransferAccountResponse response = this.wxService.getWxMaXPayService().queryTransferAccount(request, sigParams); + assertNotNull(response); + } + + @Test + public void testQueryAdverFunds() throws Exception { + WxMaXPayQueryAdverFundsRequest request = WxMaXPayQueryAdverFundsRequest.builder() + .env(WxMaConstants.XPayEnv.PRODUCT) + .page(0) + .pageSize(10) + .filter(new WxMaXPayQueryAdverFundsRequest.Filter()) + .build(); + WxMaXPaySigParams sigParams = new WxMaXPaySigParams(); + sigParams.setAppKey(""); + WxMaXPayQueryAdverFundsResponse response = this.wxService.getWxMaXPayService().queryAdverFunds(request, sigParams); + assertNotNull(response); + } + + @Test + public void testCreateFundsBill() throws Exception { + WxMaXPayCreateFundsBillRequest request = WxMaXPayCreateFundsBillRequest.builder() + .env(WxMaConstants.XPayEnv.PRODUCT) + .transferAmount(0) + .transferAccountUid(0L) + .transferAccountName("") + .transferAccountAgencyId(0) + .requestId("") + .settleBegin(0L) + .settleEnd(0L) + .authorizeAdvertise(0) + .fundType(0) + .build(); + WxMaXPaySigParams sigParams = new WxMaXPaySigParams(); + sigParams.setAppKey(""); + WxMaXPayCreateFundsBillResponse response = this.wxService.getWxMaXPayService().createFundsBill(request, sigParams); + assertNotNull(response); + } + + @Test + public void testBindTransferAccount() throws Exception { + WxMaXPayBindTransferAccountRequest request = WxMaXPayBindTransferAccountRequest.builder() + .env(WxMaConstants.XPayEnv.PRODUCT) + .transferAccountOrgName("") + .transferAccountUid(0L) + .build(); + WxMaXPaySigParams sigParams = new WxMaXPaySigParams(); + sigParams.setAppKey(""); + WxMaBaseResponse response = this.wxService.getWxMaXPayService().bindTransferAccount(request, sigParams); + assertNotNull(response); + } + + @Test + public void testQueryFundsBill() throws Exception { + WxMaXPayQueryFundsBillRequest request = WxMaXPayQueryFundsBillRequest.builder() + .env(WxMaConstants.XPayEnv.PRODUCT) + .page(0) + .pageSize(10) + .filter(new WxMaXPayQueryFundsBillRequest.Filter()) + .build(); + WxMaXPaySigParams sigParams = new WxMaXPaySigParams(); + sigParams.setAppKey(""); + WxMaXPayQueryFundsBillResponse response = this.wxService.getWxMaXPayService().queryFundsBill(request, sigParams); + assertNotNull(response); + } + + + @Test + public void testQueryRecoverBill() throws Exception { + WxMaXPayQueryRecoverBillRequest request = WxMaXPayQueryRecoverBillRequest.builder() + .env(WxMaConstants.XPayEnv.PRODUCT) + .page(0) + .pageSize(10) + .filter(new WxMaXPayQueryRecoverBillRequest.Filter()) + .build(); + WxMaXPaySigParams sigParams = new WxMaXPaySigParams(); + sigParams.setAppKey(""); + WxMaXPayQueryRecoverBillResponse response = this.wxService.getWxMaXPayService().queryRecoverBill(request, sigParams); + assertNotNull(response); + } + + @Test + public void testGetComplaintList() throws Exception { + WxMaXPayGetComplaintListRequest request = WxMaXPayGetComplaintListRequest.builder() + .env(WxMaConstants.XPayEnv.PRODUCT) + .beginDate("") + .endDate("") + .offset(0) + .limit(10) + .build(); + WxMaXPaySigParams sigParams = new WxMaXPaySigParams(); + sigParams.setAppKey(""); + WxMaXPayGetComplaintListResponse response = this.wxService.getWxMaXPayService().getComplaintList(request, sigParams); + assertNotNull(response); + } + + @Test + public void testGetComplaintDetail() throws Exception { + WxMaXPayGetComplaintDetailRequest request = WxMaXPayGetComplaintDetailRequest.builder() + .env(WxMaConstants.XPayEnv.PRODUCT) + .complaintId("") + .build(); + WxMaXPaySigParams sigParams = new WxMaXPaySigParams(); + sigParams.setAppKey(""); + WxMaXPayGetComplaintDetailResponse response = this.wxService.getWxMaXPayService().getComplaintDetail(request, sigParams); + assertNotNull(response); + } + + @Test + public void testGetNegotiationHistory() throws Exception { + WxMaXPayGetNegotiationHistoryRequest request = WxMaXPayGetNegotiationHistoryRequest.builder() + .env(WxMaConstants.XPayEnv.PRODUCT) + .limit(10) + .offset(0) + .complaintId("") + .build(); + WxMaXPaySigParams sigParams = new WxMaXPaySigParams(); + sigParams.setAppKey(""); + WxMaXPayGetNegotiationHistoryResponse response = this.wxService.getWxMaXPayService().getNegotiationHistory(request, sigParams); + assertNotNull(response); + } + + @Test + public void testResponseComplaint() throws Exception { + WxMaXPayResponseComplaintRequest request = WxMaXPayResponseComplaintRequest.builder() + .env(WxMaConstants.XPayEnv.PRODUCT) + .complaintId("") + .responseContent("") + .responseImages(new ArrayList<>()) + .build(); + WxMaXPaySigParams sigParams = new WxMaXPaySigParams(); + sigParams.setAppKey(""); + WxMaBaseResponse response = this.wxService.getWxMaXPayService().responseComplaint(request, sigParams); + assertNotNull(response); + } + + @Test + public void testCompleteComplaint() throws Exception { + WxMaXPayCompleteComplaintRequest request = WxMaXPayCompleteComplaintRequest.builder() + .env(WxMaConstants.XPayEnv.PRODUCT) + .complaintId("") + .build(); + WxMaXPaySigParams sigParams = new WxMaXPaySigParams(); + sigParams.setAppKey(""); + WxMaBaseResponse response = this.wxService.getWxMaXPayService().completeComplaint(request, sigParams); + assertNotNull(response); + } + + @Test + public void testUploadVpFile() throws Exception { + WxMaXPayUploadVpFileRequest request = WxMaXPayUploadVpFileRequest.builder() + .env(WxMaConstants.XPayEnv.PRODUCT) + .base64Img("") + .fileName("") + .imgUrl("") + .build(); + WxMaXPaySigParams sigParams = new WxMaXPaySigParams(); + sigParams.setAppKey(""); + WxMaXPayUploadVpFileResponse response = this.wxService.getWxMaXPayService().uploadVpFile(request, sigParams); + assertNotNull(response); + } + + @Test + public void testGetUploadFileSign() throws Exception { + WxMaXPayGetUploadFileSignRequest request = WxMaXPayGetUploadFileSignRequest.builder() + .env(WxMaConstants.XPayEnv.PRODUCT) + .wxpayUrl("") + .complaintId("") + .convertCos(true) + .build(); + WxMaXPaySigParams sigParams = new WxMaXPaySigParams(); + sigParams.setAppKey(""); + WxMaXPayGetUploadFileSignResponse response = this.wxService.getWxMaXPayService().getUploadFileSign(request, sigParams); + assertNotNull(response); + } + + @Test + public void testDownloadAdverfundsOrder() throws Exception { + WxMaXPayDownloadAdverfundsOrderRequest request = WxMaXPayDownloadAdverfundsOrderRequest.builder() + .env(WxMaConstants.XPayEnv.PRODUCT) + .fundId("") + .build(); + WxMaXPaySigParams sigParams = new WxMaXPaySigParams(); + sigParams.setAppKey("123"); + WxMaXPayDownloadAdverfundsOrderResponse response = this.wxService.getWxMaXPayService().downloadAdverfundsOrder(request, sigParams); + assertNotNull(response); + } + } diff --git a/weixin-java-mp/pom.xml b/weixin-java-mp/pom.xml index 0aecd36da2..1fb7271d37 100644 --- a/weixin-java-mp/pom.xml +++ b/weixin-java-mp/pom.xml @@ -7,7 +7,7 @@ com.github.binarywang wx-java - 4.7.2.B + 4.7.8.B weixin-java-mp @@ -31,6 +31,11 @@ okhttp provided + + org.apache.httpcomponents.client5 + httpclient5 + provided + org.testng diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpMaterialService.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpMaterialService.java index b3a6fb6a6c..ef396b7036 100644 --- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpMaterialService.java +++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpMaterialService.java @@ -146,30 +146,6 @@ public interface WxMpMaterialService { */ WxMpMaterialUploadResult materialFileUpload(String mediaType, WxMpMaterial material) throws WxErrorException; - /** - *
-     * 新增永久图文素材
-     *
-     * 详情请见: 新增永久素材
-     * 接口url格式:https://api.weixin.qq.com/cgi-bin/material/add_news?access_token=ACCESS_TOKEN
-     *
-     * 除了3天就会失效的临时素材外,开发者有时需要永久保存一些素材,届时就可以通过本接口新增永久素材。
-     * 永久图片素材新增后,将带有URL返回给开发者,开发者可以在腾讯系域名内使用(腾讯系域名外使用,图片将被屏蔽)。
-     * 请注意:
-     * 1、新增的永久素材也可以在公众平台官网素材管理模块中看到
-     * 2、永久素材的数量是有上限的,请谨慎新增。图文消息素材和图片素材的上限为5000,其他类型为1000
-     * 3、素材的格式大小等要求与公众平台官网一致。具体是,图片大小不超过2M,支持bmp/png/jpeg/jpg/gif格式,语音大小不超过5M,长度不超过60秒,支持mp3/wma/wav/amr格式
-     * 4、调用该接口需https协议
-     * 
- * - * @param news 上传的图文消息, 请看{@link WxMpMaterialNews} - * @return the wx mp material upload result - * @throws WxErrorException the wx error exception - * @deprecated 关于永久图文素材相关接口下线的公告 : https://mp.weixin.qq.com/cgi-bin/announce?action=getannouncement&announce_id=11644831863qFQSh&version=&token=2085564289&lang=zh_CN - */ - @Deprecated - WxMpMaterialUploadResult materialNewsUpload(WxMpMaterialNews news) throws WxErrorException; - /** *
      * 获取声音或者图片永久素材
@@ -212,22 +188,6 @@ public interface WxMpMaterialService {
      */
     WxMpMaterialNews materialNewsInfo(String mediaId) throws WxErrorException;
 
-    /**
-     * 
-     * 修改永久图文素材
-     *
-     * 详情请见: 修改永久图文素材
-     * 接口url格式:https://api.weixin.qq.com/cgi-bin/material/update_news?access_token=ACCESS_TOKEN
-     * 
- * - * @param wxMpMaterialArticleUpdate 用来更新图文素材的bean, 请看{@link WxMpMaterialArticleUpdate} - * @return the boolean - * @throws WxErrorException the wx error exception - * @deprecated 关于永久图文素材相关接口下线的公告 : https://mp.weixin.qq.com/cgi-bin/announce?action=getannouncement&announce_id=11644831863qFQSh&version=&token=2085564289&lang=zh_CN - */ - @Deprecated - boolean materialNewsUpdate(WxMpMaterialArticleUpdate wxMpMaterialArticleUpdate) throws WxErrorException; - /** *
      * 删除永久素材
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpMemberCardService.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpMemberCardService.java
index ea8cab7e50..289cb6a067 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpMemberCardService.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpMemberCardService.java
@@ -2,9 +2,6 @@
 
 import me.chanjar.weixin.common.error.WxErrorException;
 import me.chanjar.weixin.mp.bean.card.CardUpdateResult;
-import me.chanjar.weixin.mp.bean.card.membercard.MemberCardActivateUserFormRequest;
-import me.chanjar.weixin.mp.bean.card.membercard.MemberCardActivateUserFormResult;
-import me.chanjar.weixin.mp.bean.card.membercard.MemberCardUpdateRequest;
 import me.chanjar.weixin.mp.bean.card.WxMpCardCreateResult;
 import me.chanjar.weixin.mp.bean.card.membercard.*;
 
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpQrcodeService.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpQrcodeService.java
index 847f6e7ecf..ed8c5e6a79 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpQrcodeService.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpQrcodeService.java
@@ -89,7 +89,7 @@ public interface WxMpQrcodeService {
      * @throws WxErrorException the wx error exception
      */
     @Deprecated
-  String qrCodePictureUrl(String ticket, boolean needShortUrl) throws WxErrorException;
+    String qrCodePictureUrl(String ticket, boolean needShortUrl) throws WxErrorException;
 
     /**
      * 
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 6df78c12d2..47a24b7931 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
@@ -528,7 +528,7 @@ public interface WxMpService extends WxService {
    *
    * @return RequestHttp对象 request http
    */
-  RequestHttp getRequestHttp();
+  RequestHttp getRequestHttp();
 
   /**
    * 返回群发消息相关接口方法的实现类对象,以方便调用其各个接口.
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpStoreService.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpStoreService.java
index 44ca69f3f3..0010932ca7 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpStoreService.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpStoreService.java
@@ -11,11 +11,9 @@
  * 门店管理的相关接口代码.
  * Created by Binary Wang on 2016-09-23.
  *
- * @param  the type parameter
- * @param 

the type parameter * @author Binary Wang */ -public interface WxMpStoreService { +public interface WxMpStoreService { /** *

      * 创建门店
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 b2719301ec..63ca608eba 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
@@ -188,7 +188,7 @@ public boolean checkSignature(String timestamp, String nonce, String signature)
       return SHA1.gen(this.getWxMpConfigStorage().getToken(), timestamp, nonce)
         .equals(signature);
     } catch (Exception e) {
-      log.error("Checking signature failed, and the reason is :" + e.getMessage());
+      log.error("Checking signature failed, and the reason is :{}", e.getMessage());
       return false;
     }
   }
@@ -649,7 +649,7 @@ public void setMaxRetryTimes(int maxRetryTimes) {
   }
 
   @Override
-  public RequestHttp getRequestHttp() {
+  public RequestHttp getRequestHttp() {
     return this;
   }
 
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpCardServiceImpl.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpCardServiceImpl.java
index 2ac835bbc4..8fce1d4736 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpCardServiceImpl.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpCardServiceImpl.java
@@ -114,7 +114,7 @@ public WxMpCardResult queryCardCode(String cardId, String code, boolean checkCon
     param.addProperty("code", code);
     param.addProperty("check_consume", checkConsume);
     String responseContent = this.wxMpService.post(WxMpApiUrl.Card.CARD_CODE_GET, param.toString());
-    JsonElement tmpJsonElement = new JsonParser().parse(responseContent);
+    JsonElement tmpJsonElement = JsonParser.parseString(responseContent);
     return WxMpGsonBuilder.create().fromJson(tmpJsonElement,
       new TypeToken() {
       }.getType());
@@ -145,7 +145,7 @@ public void markCardCode(String code, String cardId, String openId, boolean isMa
     param.addProperty("openid", openId);
     param.addProperty("is_mark", isMark);
     String responseContent = this.getWxMpService().post(WxMpApiUrl.Card.CARD_CODE_MARK, param.toString());
-    JsonElement tmpJsonElement = new JsonParser().parse(responseContent);
+    JsonElement tmpJsonElement = JsonParser.parseString(responseContent);
     WxMpCardResult cardResult = WxMpGsonBuilder.create().fromJson(tmpJsonElement,
       new TypeToken() {
       }.getType());
@@ -166,7 +166,7 @@ public String getCardDetail(String cardId) throws WxErrorException {
     if (!"0".equals(errcode)) {
       String errmsg = json.get("errmsg").getAsString();
       throw new WxErrorException(WxError.builder()
-        .errorCode(Integer.valueOf(errcode)).errorMsg(errmsg)
+        .errorCode(Integer.parseInt(errcode)).errorMsg(errmsg)
         .build());
     }
 
@@ -257,7 +257,7 @@ public WxMpCardDeleteResult deleteCard(String cardId) throws WxErrorException {
   @Override
   public WxMpCardCodeDepositResult cardCodeDeposit(String cardId, List codeList) throws WxErrorException {
     checkCardId(cardId);
-    if (codeList.size() == 0 || codeList.size() > 100) {
+    if (codeList.isEmpty() || codeList.size() > 100) {
       throw new WxErrorException(WxError.builder().errorCode(40109).errorMsg("code数量为0或者code数量超过100个").build());
     }
     JsonObject param = new JsonObject();
@@ -283,7 +283,7 @@ public WxMpCardCodeDepositCountResult cardCodeDepositCount(String cardId) throws
   @Override
   public WxMpCardCodeCheckcodeResult cardCodeCheckcode(String cardId, List codeList) throws WxErrorException {
     checkCardId(cardId);
-    if (codeList.size() == 0 || codeList.size() > 100) {
+    if (codeList.isEmpty() || codeList.size() > 100) {
       throw new WxErrorException(WxError.builder().errorCode(40109).errorMsg("code数量为0或者code数量超过100个").build());
     }
     JsonObject param = new JsonObject();
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpGuideServiceImpl.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpGuideServiceImpl.java
index dd6256c3e8..94491a72f8 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpGuideServiceImpl.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpGuideServiceImpl.java
@@ -121,7 +121,7 @@ public WxMpGuideConfig getGuideConfig(String account, String openid) throws WxEr
   @Override
   public void setGuideAcctConfig(boolean isDelete, List blackKeyWord, String guideAutoReply) throws WxErrorException {
     JsonObject jsonObject1 = null;
-    if (blackKeyWord != null && blackKeyWord.size() > 0) {
+    if (blackKeyWord != null && !blackKeyWord.isEmpty()) {
       jsonObject1 = new JsonObject();
       JsonArray jsonArray = new JsonArray();
       blackKeyWord.forEach(jsonArray::add);
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpGuideTagServiceImpl.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpGuideTagServiceImpl.java
index 11b0e4d2de..4680bee320 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpGuideTagServiceImpl.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpGuideTagServiceImpl.java
@@ -91,7 +91,7 @@ public List getGuideBuyerTag(String account, String openid, String userO
       new TypeToken>() {
       }.getType());
     if (isExclude) {
-      if (list.size() > 0) {
+      if (!list.isEmpty()) {
         if (list.get(list.size() - 1).contains("\n")) {
           list.remove(list.size() - 1);
         }
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpImgProcServiceImpl.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpImgProcServiceImpl.java
index ea1785f233..679146c4a9 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpImgProcServiceImpl.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpImgProcServiceImpl.java
@@ -86,8 +86,8 @@ public WxImgProcAiCropResult aiCrop(String imgUrl, String ratios) throws WxError
       ratios = "";
     }
 
-    final String result = this.wxMpService.get(String.format(AI_CROP.getUrl(this.wxMpService.getWxMpConfigStorage()),
-      imgUrl, ratios), null);
+    final String result = this.wxMpService.post(String.format(AI_CROP.getUrl(this.wxMpService.getWxMpConfigStorage()),
+      imgUrl, ratios), "");
     return WxImgProcAiCropResult.fromJson(result);
   }
 
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpKefuServiceImpl.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpKefuServiceImpl.java
index cde4df5b67..24a88e3bff 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpKefuServiceImpl.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpKefuServiceImpl.java
@@ -144,7 +144,7 @@ public WxMpKfMsgList kfMsgList(Date startTime, Date endTime) throws WxErrorExcep
     if (result != null && result.getNumber() == number) {
       Long msgId = result.getMsgId();
       WxMpKfMsgList followingResult = this.kfMsgList(startTime, endTime, msgId, number);
-      while (followingResult != null && followingResult.getRecords().size() > 0) {
+      while (followingResult != null && !followingResult.getRecords().isEmpty()) {
         result.getRecords().addAll(followingResult.getRecords());
         result.setNumber(result.getNumber() + followingResult.getNumber());
         result.setMsgId(followingResult.getMsgId());
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpMaterialServiceImpl.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpMaterialServiceImpl.java
index 45e1c5c4b1..dfaf7d8a98 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpMaterialServiceImpl.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpMaterialServiceImpl.java
@@ -83,15 +83,6 @@ public WxMpMaterialUploadResult materialFileUpload(String mediaType, WxMpMateria
     return this.wxMpService.execute(MaterialUploadRequestExecutor.create(this.wxMpService.getRequestHttp()), url, material);
   }
 
-  @Override
-  public WxMpMaterialUploadResult materialNewsUpload(WxMpMaterialNews news) throws WxErrorException {
-    if (news == null || news.isEmpty()) {
-      throw new IllegalArgumentException("news is empty!");
-    }
-    String responseContent = this.wxMpService.post(NEWS_ADD_URL, news.toJson());
-    return WxMpMaterialUploadResult.fromJson(responseContent);
-  }
-
   @Override
   public InputStream materialImageOrVoiceDownload(String mediaId) throws WxErrorException {
     return this.wxMpService.execute(MaterialVoiceAndImageDownloadRequestExecutor
@@ -111,17 +102,6 @@ public WxMpMaterialNews materialNewsInfo(String mediaId) throws WxErrorException
       MATERIAL_GET_URL, mediaId);
   }
 
-  @Override
-  public boolean materialNewsUpdate(WxMpMaterialArticleUpdate wxMpMaterialArticleUpdate) throws WxErrorException {
-    String responseText = this.wxMpService.post(NEWS_UPDATE_URL, wxMpMaterialArticleUpdate.toJson());
-    WxError wxError = WxError.fromJson(responseText, WxType.MP);
-    if (wxError.getErrorCode() == 0) {
-      return true;
-    } else {
-      throw new WxErrorException(wxError);
-    }
-  }
-
   @Override
   public boolean materialDelete(String mediaId) throws WxErrorException {
     return this.wxMpService.execute(MaterialDeleteRequestExecutor.create(this.wxMpService.getRequestHttp()),
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpMemberCardServiceImpl.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpMemberCardServiceImpl.java
index 4f4471b2bb..7a01c6a014 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpMemberCardServiceImpl.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpMemberCardServiceImpl.java
@@ -24,11 +24,6 @@
 import me.chanjar.weixin.mp.bean.card.BaseInfo;
 import me.chanjar.weixin.mp.bean.card.CardUpdateResult;
 import me.chanjar.weixin.mp.bean.card.DateInfo;
-import me.chanjar.weixin.mp.bean.card.membercard.MemberCard;
-import me.chanjar.weixin.mp.bean.card.membercard.MemberCardActivateUserFormRequest;
-import me.chanjar.weixin.mp.bean.card.membercard.MemberCardActivateUserFormResult;
-import me.chanjar.weixin.mp.bean.card.membercard.MemberCardCreateRequest;
-import me.chanjar.weixin.mp.bean.card.membercard.MemberCardUpdateRequest;
 import me.chanjar.weixin.mp.bean.card.WxMpCardCreateResult;
 import me.chanjar.weixin.mp.bean.card.enums.BusinessServiceType;
 import me.chanjar.weixin.mp.bean.card.enums.CardColor;
@@ -222,7 +217,7 @@ public WxMpMemberCardUserInfoResult getUserInfo(String cardId, String code) thro
 
     String responseContent = this.getWxMpService().post(WxMpApiUrl.MemberCard.MEMBER_CARD_USER_INFO_GET, jsonObject.toString());
     log.debug("{}", responseContent);
-    JsonElement tmpJsonElement = new JsonParser().parse(responseContent);
+    JsonElement tmpJsonElement = JsonParser.parseString(responseContent);
     return WxMpGsonBuilder.create().fromJson(tmpJsonElement,
       new TypeToken() {
       }.getType());
@@ -234,7 +229,7 @@ public WxMpMemberCardUpdateResult updateUserMemberCard(WxMpMemberCardUpdateMessa
 
     String responseContent = this.getWxMpService().post(WxMpApiUrl.MemberCard.MEMBER_CARD_UPDATE_USER, GSON.toJson(updateUserMessage));
 
-    JsonElement tmpJsonElement = new JsonParser().parse(responseContent);
+    JsonElement tmpJsonElement = JsonParser.parseString(responseContent);
     return WxMpGsonBuilder.create().fromJson(tmpJsonElement,
       new TypeToken() {
       }.getType());
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpMerchantInvoiceServiceImpl.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpMerchantInvoiceServiceImpl.java
index 4631a2e2cc..361c0f52d1 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpMerchantInvoiceServiceImpl.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpMerchantInvoiceServiceImpl.java
@@ -12,7 +12,6 @@
 import me.chanjar.weixin.mp.api.WxMpService;
 import me.chanjar.weixin.mp.bean.invoice.merchant.*;
 import me.chanjar.weixin.mp.enums.WxMpApiUrl;
-import me.chanjar.weixin.mp.util.json.WxMpGsonBuilder;
 
 import java.util.Map;
 
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpQrcodeServiceImpl.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpQrcodeServiceImpl.java
index 7bad648cb5..5719f4bb46 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpQrcodeServiceImpl.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpQrcodeServiceImpl.java
@@ -2,7 +2,6 @@
 
 import com.google.gson.JsonObject;
 import lombok.RequiredArgsConstructor;
-import me.chanjar.weixin.common.error.WxError;
 import me.chanjar.weixin.common.error.WxErrorException;
 import me.chanjar.weixin.mp.api.WxMpQrcodeService;
 import me.chanjar.weixin.mp.api.WxMpService;
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpServiceHttpClientImpl.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpServiceHttpClientImpl.java
index 750f787137..c61fd09b9f 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpServiceHttpClientImpl.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpServiceHttpClientImpl.java
@@ -1,18 +1,17 @@
 package me.chanjar.weixin.mp.api.impl;
 
-import me.chanjar.weixin.common.util.http.HttpType;
+import me.chanjar.weixin.common.util.http.HttpClientType;
+import me.chanjar.weixin.common.util.http.apache.ApacheBasicResponseHandler;
 import me.chanjar.weixin.common.util.http.apache.ApacheHttpClientBuilder;
 import me.chanjar.weixin.common.util.http.apache.DefaultApacheHttpClientBuilder;
 import me.chanjar.weixin.mp.bean.WxMpStableAccessTokenRequest;
 import me.chanjar.weixin.mp.config.WxMpConfigStorage;
 import org.apache.http.HttpHost;
 import org.apache.http.client.config.RequestConfig;
-import org.apache.http.client.methods.CloseableHttpResponse;
 import org.apache.http.client.methods.HttpGet;
 import org.apache.http.client.methods.HttpPost;
 import org.apache.http.entity.ContentType;
 import org.apache.http.entity.StringEntity;
-import org.apache.http.impl.client.BasicResponseHandler;
 import org.apache.http.impl.client.CloseableHttpClient;
 
 import java.io.IOException;
@@ -40,8 +39,8 @@ public HttpHost getRequestHttpProxy() {
   }
 
   @Override
-  public HttpType getRequestType() {
-    return HttpType.APACHE_HTTP;
+  public HttpClientType getRequestType() {
+    return HttpClientType.APACHE_HTTP;
   }
 
   @Override
@@ -68,61 +67,31 @@ public void initHttp() {
   protected String doGetAccessTokenRequest() throws IOException {
     String url = String.format(GET_ACCESS_TOKEN_URL.getUrl(getWxMpConfigStorage()), getWxMpConfigStorage().getAppId(), getWxMpConfigStorage().getSecret());
 
-    HttpGet httpGet = null;
-    CloseableHttpResponse response = null;
-    try {
-      httpGet = new HttpGet(url);
-      if (this.getRequestHttpProxy() != null) {
-        RequestConfig config = RequestConfig.custom().setProxy(this.getRequestHttpProxy()).build();
-        httpGet.setConfig(config);
-      }
-      response = getRequestHttpClient().execute(httpGet);
-      return new BasicResponseHandler().handleResponse(response);
-    } finally {
-      if (httpGet != null) {
-        httpGet.releaseConnection();
-      }
-      if (response != null) {
-        try {
-          response.close();
-        } catch (IOException e) {
-        }
-      }
+    HttpGet httpGet = new HttpGet(url);
+    if (this.getRequestHttpProxy() != null) {
+      RequestConfig config = RequestConfig.custom().setProxy(this.getRequestHttpProxy()).build();
+      httpGet.setConfig(config);
     }
+    return getRequestHttpClient().execute(httpGet, ApacheBasicResponseHandler.INSTANCE);
   }
 
   @Override
   protected String doGetStableAccessTokenRequest(boolean forceRefresh) throws IOException {
     String url = GET_STABLE_ACCESS_TOKEN_URL.getUrl(getWxMpConfigStorage());
 
-    HttpPost httpPost = null;
-    CloseableHttpResponse response = null;
-    try {
-      httpPost = new HttpPost(url);
-      if (this.getRequestHttpProxy() != null) {
-        RequestConfig config = RequestConfig.custom().setProxy(this.getRequestHttpProxy()).build();
-        httpPost.setConfig(config);
-      }
-      WxMpStableAccessTokenRequest wxMaAccessTokenRequest = new WxMpStableAccessTokenRequest();
-      wxMaAccessTokenRequest.setAppid(this.getWxMpConfigStorage().getAppId());
-      wxMaAccessTokenRequest.setSecret(this.getWxMpConfigStorage().getSecret());
-      wxMaAccessTokenRequest.setGrantType("client_credential");
-      wxMaAccessTokenRequest.setForceRefresh(forceRefresh);
-
-      httpPost.setEntity(new StringEntity(wxMaAccessTokenRequest.toJson(), ContentType.APPLICATION_JSON));
-      response = getRequestHttpClient().execute(httpPost);
-      return new BasicResponseHandler().handleResponse(response);
-    } finally {
-      if (httpPost != null) {
-        httpPost.releaseConnection();
-      }
-      if (response != null) {
-        try {
-          response.close();
-        } catch (IOException e) {
-        }
-      }
+    HttpPost httpPost = new HttpPost(url);
+    if (this.getRequestHttpProxy() != null) {
+      RequestConfig config = RequestConfig.custom().setProxy(this.getRequestHttpProxy()).build();
+      httpPost.setConfig(config);
     }
+    WxMpStableAccessTokenRequest wxMaAccessTokenRequest = new WxMpStableAccessTokenRequest();
+    wxMaAccessTokenRequest.setAppid(this.getWxMpConfigStorage().getAppId());
+    wxMaAccessTokenRequest.setSecret(this.getWxMpConfigStorage().getSecret());
+    wxMaAccessTokenRequest.setGrantType("client_credential");
+    wxMaAccessTokenRequest.setForceRefresh(forceRefresh);
+
+    httpPost.setEntity(new StringEntity(wxMaAccessTokenRequest.toJson(), ContentType.APPLICATION_JSON));
+    return getRequestHttpClient().execute(httpPost, ApacheBasicResponseHandler.INSTANCE);
   }
 
 }
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
new file mode 100644
index 0000000000..bbf065acfc
--- /dev/null
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpServiceHttpComponentsImpl.java
@@ -0,0 +1,94 @@
+package me.chanjar.weixin.mp.api.impl;
+
+import me.chanjar.weixin.common.util.http.HttpClientType;
+import me.chanjar.weixin.common.util.http.hc.BasicResponseHandler;
+import me.chanjar.weixin.common.util.http.hc.DefaultHttpComponentsClientBuilder;
+import me.chanjar.weixin.common.util.http.hc.HttpComponentsClientBuilder;
+import me.chanjar.weixin.mp.bean.WxMpStableAccessTokenRequest;
+import me.chanjar.weixin.mp.config.WxMpConfigStorage;
+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;
+
+import static me.chanjar.weixin.mp.enums.WxMpApiUrl.Other.GET_ACCESS_TOKEN_URL;
+import static me.chanjar.weixin.mp.enums.WxMpApiUrl.Other.GET_STABLE_ACCESS_TOKEN_URL;
+
+/**
+ * apache http client方式实现.
+ *
+ * @author altusea
+ */
+public class WxMpServiceHttpComponentsImpl extends BaseWxMpServiceImpl {
+  private CloseableHttpClient httpClient;
+  private HttpHost httpProxy;
+
+  @Override
+  public CloseableHttpClient getRequestHttpClient() {
+    return httpClient;
+  }
+
+  @Override
+  public HttpHost getRequestHttpProxy() {
+    return httpProxy;
+  }
+
+  @Override
+  public HttpClientType getRequestType() {
+    return HttpClientType.HTTP_COMPONENTS;
+  }
+
+  @Override
+  public void initHttp() {
+    WxMpConfigStorage configStorage = this.getWxMpConfigStorage();
+    HttpComponentsClientBuilder apacheHttpClientBuilder = DefaultHttpComponentsClientBuilder.get();
+
+    apacheHttpClientBuilder.httpProxyHost(configStorage.getHttpProxyHost())
+      .httpProxyPort(configStorage.getHttpProxyPort())
+      .httpProxyUsername(configStorage.getHttpProxyUsername())
+      .httpProxyPassword(configStorage.getHttpProxyPassword().toCharArray());
+
+    if (configStorage.getHttpProxyHost() != null && configStorage.getHttpProxyPort() > 0) {
+      this.httpProxy = new HttpHost(configStorage.getHttpProxyHost(), configStorage.getHttpProxyPort());
+    }
+
+    this.httpClient = apacheHttpClientBuilder.build();
+  }
+
+  @Override
+  protected String doGetAccessTokenRequest() throws IOException {
+    String url = String.format(GET_ACCESS_TOKEN_URL.getUrl(getWxMpConfigStorage()), getWxMpConfigStorage().getAppId(), getWxMpConfigStorage().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 = GET_STABLE_ACCESS_TOKEN_URL.getUrl(getWxMpConfigStorage());
+
+    HttpPost httpPost = new HttpPost(url);
+    if (this.getRequestHttpProxy() != null) {
+      RequestConfig config = RequestConfig.custom().setProxy(this.getRequestHttpProxy()).build();
+      httpPost.setConfig(config);
+    }
+    WxMpStableAccessTokenRequest wxMaAccessTokenRequest = new WxMpStableAccessTokenRequest();
+    wxMaAccessTokenRequest.setAppid(this.getWxMpConfigStorage().getAppId());
+    wxMaAccessTokenRequest.setSecret(this.getWxMpConfigStorage().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-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpServiceJoddHttpImpl.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpServiceJoddHttpImpl.java
index b174c4bdf9..7f67b3478b 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpServiceJoddHttpImpl.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpServiceJoddHttpImpl.java
@@ -5,7 +5,7 @@
 import jodd.http.ProxyInfo;
 import jodd.http.net.SocketHttpConnectionProvider;
 import jodd.net.MimeTypes;
-import me.chanjar.weixin.common.util.http.HttpType;
+import me.chanjar.weixin.common.util.http.HttpClientType;
 import me.chanjar.weixin.mp.bean.WxMpStableAccessTokenRequest;
 import me.chanjar.weixin.mp.config.WxMpConfigStorage;
 
@@ -35,8 +35,8 @@ public ProxyInfo getRequestHttpProxy() {
   }
 
   @Override
-  public HttpType getRequestType() {
-    return HttpType.JODD_HTTP;
+  public HttpClientType getRequestType() {
+    return HttpClientType.JODD_HTTP;
   }
 
   @Override
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpServiceOkHttpImpl.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpServiceOkHttpImpl.java
index 86555aa4a1..8bd4b2a227 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpServiceOkHttpImpl.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpServiceOkHttpImpl.java
@@ -1,6 +1,6 @@
 package me.chanjar.weixin.mp.api.impl;
 
-import me.chanjar.weixin.common.util.http.HttpType;
+import me.chanjar.weixin.common.util.http.HttpClientType;
 import me.chanjar.weixin.common.util.http.okhttp.DefaultOkHttpClientBuilder;
 import me.chanjar.weixin.common.util.http.okhttp.OkHttpProxyInfo;
 import me.chanjar.weixin.mp.bean.WxMpStableAccessTokenRequest;
@@ -33,8 +33,8 @@ public OkHttpProxyInfo getRequestHttpProxy() {
   }
 
   @Override
-  public HttpType getRequestType() {
-    return HttpType.OK_HTTP;
+  public HttpClientType getRequestType() {
+    return HttpClientType.OK_HTTP;
   }
 
   @Override
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpStoreServiceImpl.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpStoreServiceImpl.java
index e1378efc5c..a71a753ecd 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpStoreServiceImpl.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpStoreServiceImpl.java
@@ -86,7 +86,7 @@ public List listAll() throws WxErrorException {
     if (list.getTotalCount() > limit) {
       int begin = limit;
       WxMpStoreListResult followingList = this.list(begin, limit);
-      while (followingList.getBusinessList().size() > 0) {
+      while (!followingList.getBusinessList().isEmpty()) {
         stores.addAll(followingList.getBusinessList());
         begin += limit;
         if (begin >= list.getTotalCount()) {
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpWifiServiceImpl.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpWifiServiceImpl.java
index 0b75bb996b..2eca3fccea 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpWifiServiceImpl.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpWifiServiceImpl.java
@@ -46,11 +46,7 @@ public boolean updateShopWifiInfo(int shopId, String oldSsid, String ssid, Strin
     if (password != null) {
       json.addProperty("password", password);
     }
-    try {
-      this.wxMpService.post(BIZWIFI_SHOP_UPDATE, json.toString());
-      return true;
-    } catch (WxErrorException e) {
-      throw e;
-    }
+    this.wxMpService.post(BIZWIFI_SHOP_UPDATE, json.toString());
+    return true;
   }
 }
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/WxMpShakeInfoResult.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/WxMpShakeInfoResult.java
index 4cd6430000..3c053480dd 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/WxMpShakeInfoResult.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/WxMpShakeInfoResult.java
@@ -26,7 +26,7 @@ public static WxMpShakeInfoResult fromJson(String json) {
   }
 
   @Data
-  public class ShakeInfoData implements Serializable {
+  public static class ShakeInfoData implements Serializable {
     private static final long serialVersionUID = -4828142206067489488L;
 
     private String page_id;
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/card/enums/BusinessServiceType.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/card/enums/BusinessServiceType.java
index 3ae9cf8937..999cac43ce 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/card/enums/BusinessServiceType.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/card/enums/BusinessServiceType.java
@@ -9,7 +9,7 @@ public enum BusinessServiceType {
   BIZ_SERVICE_WITH_PET("可带宠物"),
   BIZ_SERVICE_FREE_WIFI("可带宠物");
 
-  private String description;
+  private final String description;
 
   BusinessServiceType(String description) {
     this.description = description;
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/card/enums/CardCodeType.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/card/enums/CardCodeType.java
index 35263188e0..61fb137701 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/card/enums/CardCodeType.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/card/enums/CardCodeType.java
@@ -9,7 +9,7 @@ public enum CardCodeType {
   CODE_TYPE_BARCODE("一维码"),
   CODE_TYPE_QRCODE("二维码");
 
-  private String description;
+  private final String description;
 
   CardCodeType(String description) {
     this.description = description;
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/card/enums/CardColor.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/card/enums/CardColor.java
index a694d4d372..594a78b58b 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/card/enums/CardColor.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/card/enums/CardColor.java
@@ -22,7 +22,7 @@ public enum CardColor {
   Color101("#cf3e36"),
   Color102("#5E6671");
 
-  private String type;
+  private final String type;
 
   CardColor(String type) {
     this.type = type;
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/card/enums/CardFieldType.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/card/enums/CardFieldType.java
index 42804b635b..e4f69d42a4 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/card/enums/CardFieldType.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/card/enums/CardFieldType.java
@@ -11,7 +11,7 @@ public enum CardFieldType {
   CUSTOM_FIELD("自定义选项"),
   RICH_FIELD("自定义富文本类型");
 
-  private String description;
+  private final String description;
 
   CardFieldType(String description) {
     this.description = description;
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/card/enums/CardRichFieldType.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/card/enums/CardRichFieldType.java
index 7659864939..eac1625d6c 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/card/enums/CardRichFieldType.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/card/enums/CardRichFieldType.java
@@ -11,7 +11,7 @@ public enum CardRichFieldType {
   FORM_FIELD_SELECT("自定义选择项"),
   FORM_FIELD_CHECK_BOX("自定义多选");
 
-  private String description;
+  private final String description;
 
   CardRichFieldType(String description) {
     this.description = description;
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/card/enums/CardSceneType.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/card/enums/CardSceneType.java
index ec5b9fcfbc..429dcacdea 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/card/enums/CardSceneType.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/card/enums/CardSceneType.java
@@ -9,7 +9,7 @@ public enum CardSceneType {
   SCENE_IVR("自动回复"),
   SCENE_CARD_CUSTOM_CELL("卡券自定义cell");
 
-  private String description;
+  private final String description;
 
   CardSceneType(String description) {
     this.description = description;
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/card/enums/CardStatusType.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/card/enums/CardStatusType.java
index 4108b7d4c2..bcb414aa6c 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/card/enums/CardStatusType.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/card/enums/CardStatusType.java
@@ -7,7 +7,7 @@ public enum CardStatusType {
   CARD_STATUS_DELETE("卡券被商户删除"),
   CARD_STATUS_DISPATCH("在公众平台投放过的卡券");
 
-  private String description;
+  private final String description;
 
   CardStatusType(String description) {
     this.description = description;
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/card/enums/CardWechatFieldType.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/card/enums/CardWechatFieldType.java
index c34bd58ace..9dc7f5d427 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/card/enums/CardWechatFieldType.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/card/enums/CardWechatFieldType.java
@@ -23,7 +23,7 @@ public enum CardWechatFieldType {
   USER_FORM_INFO_FLAG_INCOME("收入"),
   USER_FORM_INFO_FLAG_HABIT("兴趣爱好");
 
-  private String description;
+  private final String description;
 
   CardWechatFieldType(String description) {
     this.description = description;
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/card/enums/CustomFieldNameType.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/card/enums/CustomFieldNameType.java
index 53f3df8cf9..e6bea61685 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/card/enums/CustomFieldNameType.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/card/enums/CustomFieldNameType.java
@@ -14,7 +14,7 @@ public enum CustomFieldNameType {
   FIELD_NAME_TYPE_SET_POINTS("集点"),
   FIELD_NAME_TYPE_TIMS("次数");
 
-  private String description;
+  private final String description;
 
   CustomFieldNameType(String description) {
     this.description = description;
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/card/enums/DateInfoType.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/card/enums/DateInfoType.java
index bd8a23551c..93893dfa12 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/card/enums/DateInfoType.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/card/enums/DateInfoType.java
@@ -5,7 +5,7 @@ public enum DateInfoType {
   DATE_TYPE_FIX_TIME_RANGE("固定日期"),
   DATE_TYPE_FIX_TERM("固定时长");
 
-  private String description;
+  private final String description;
 
   DateInfoType(String description) {
     this.description = description;
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/device/BaseResp.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/device/BaseResp.java
index a0b65c8842..128e2d2528 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/device/BaseResp.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/device/BaseResp.java
@@ -21,7 +21,7 @@ public class BaseResp extends AbstractDeviceBean {
   private String errMsg;
 
   @Data
-  private class BaseInfo {
+  private static class BaseInfo {
     @SerializedName("device_type")
     private String deviceType;
 
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/draft/WxMpDraftArticles.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/draft/WxMpDraftArticles.java
index 80a7d37d4b..db37c66d10 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/draft/WxMpDraftArticles.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/draft/WxMpDraftArticles.java
@@ -23,6 +23,13 @@
 @NoArgsConstructor
 @AllArgsConstructor
 public class WxMpDraftArticles implements ToJson, Serializable {
+  /**
+   * 文章类型,分别有图文消息(news)、图片消息(newspic),不填默认为图文消息(news)
+   *
+   * @see me.chanjar.weixin.common.api.WxConsts.ArticleType
+   */
+  @SerializedName("article_type")
+  private String articleType;
   /**
    * 标题
    */
@@ -78,18 +85,31 @@ public class WxMpDraftArticles implements ToJson, Serializable {
    */
   @SerializedName("thumb_url")
   private String thumbUrl;
-
   /**
    * 封面裁剪为2.35:1规格的坐标字段。以原始图片(thumb_media_id)左上角(0,0),右下角(1,1)建立平面坐标系,经过裁剪后的图片,其左上角所在的坐标即为(X1,Y1),右下角所在的坐标则为(X2,Y2),用分隔符_拼接为X1_Y1_X2_Y2,每个坐标值的精度为不超过小数点后6位数字。示例见下图,图中(X1,Y1) 等于(0.1945,0),(X2,Y2)等于(1,0.5236),所以请求参数值为0.1945_0_1_0.5236。
    */
   @SerializedName("pic_crop_235_1")
   private String picCrop2351;
-
   /**
    * 封面裁剪为1:1规格的坐标字段,裁剪原理同pic_crop_235_1,裁剪后的图片必须符合规格要求。
    */
   @SerializedName("pic_crop_1_1")
   private String picCrop11;
+  /**
+   * 图片消息里的图片相关信息,图片数量最多为20张,首张图片即为封面图
+   */
+  @SerializedName("image_info")
+  private WxMpDraftImageInfo imageInfo;
+  /**
+   * 封面图裁剪信息
+   */
+  @SerializedName("cover_info")
+  private WxMpDraftCoverInfo coverInfo;
+  /**
+   * 商品相关信息
+   */
+  @SerializedName("product_info")
+  private WxMpDraftProductInfo productInfo;
 
   public static WxMpDraftArticles fromJson(String json) {
     return WxGsonBuilder.create().fromJson(json, WxMpDraftArticles.class);
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/draft/WxMpDraftCoverInfo.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/draft/WxMpDraftCoverInfo.java
new file mode 100644
index 0000000000..9b2ba09325
--- /dev/null
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/draft/WxMpDraftCoverInfo.java
@@ -0,0 +1,53 @@
+package me.chanjar.weixin.mp.bean.draft;
+
+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.common.util.json.WxGsonBuilder;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * 草稿箱能力-图片消息里的封面裁剪信息
+ *
+ * @author 阿杆
+ * created on 2025/5/23
+ */
+@Data
+@Builder
+@Accessors(chain = true)
+@NoArgsConstructor
+@AllArgsConstructor
+public class WxMpDraftCoverInfo implements Serializable {
+
+  private static final long serialVersionUID = -1676442833397632638L;
+
+  /**
+   * 封面裁剪信息,裁剪比例ratio支持:“1_1”,“16_9”,“2.35_1”。
+   * 以图片左上角(0,0),右下角(1,1)建立平面坐标系,经过裁剪后的图片,其左上角所在的坐标填入x1,y1参数,右下角所在的坐标填入x2,y2参数
+   */
+  @SerializedName("crop_percent_list")
+  private List cropPercentList;
+
+  public static WxMpDraftCoverInfo fromJson(String json) {
+    return WxGsonBuilder.create().fromJson(json, WxMpDraftCoverInfo.class);
+  }
+
+  @Data
+  @Builder
+  @NoArgsConstructor
+  @AllArgsConstructor
+  public static class CropPercent implements Serializable {
+    private static final long serialVersionUID = 8495528870408737871L;
+    private String ratio;
+    private String x1;
+    private String y1;
+    private String x2;
+    private String y2;
+  }
+
+}
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/draft/WxMpDraftImageInfo.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/draft/WxMpDraftImageInfo.java
new file mode 100644
index 0000000000..0f2af9f45b
--- /dev/null
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/draft/WxMpDraftImageInfo.java
@@ -0,0 +1,51 @@
+package me.chanjar.weixin.mp.bean.draft;
+
+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.common.util.json.WxGsonBuilder;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * 草稿箱能力-图片消息里的图片相关信息
+ *
+ * @author 阿杆
+ * created on 2025/5/23
+ */
+@Data
+@Builder
+@Accessors(chain = true)
+@NoArgsConstructor
+@AllArgsConstructor
+public class WxMpDraftImageInfo implements Serializable {
+
+  private static final long serialVersionUID = -1997245511033770476L;
+
+  /**
+   * 图片列表
+   */
+  @SerializedName("image_list")
+  private List imageList;
+
+  public static WxMpDraftImageInfo fromJson(String json) {
+    return WxGsonBuilder.create().fromJson(json, WxMpDraftImageInfo.class);
+  }
+
+  @Data
+  @NoArgsConstructor
+  @AllArgsConstructor
+  public static class ImageItem implements Serializable {
+    private static final long serialVersionUID = 4180558781166966752L;
+    /**
+     * 图片消息里的图片素材id(必须是永久MediaID)
+     */
+    @SerializedName("image_media_id")
+    private String imageMediaId;
+  }
+
+}
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/draft/WxMpDraftProductInfo.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/draft/WxMpDraftProductInfo.java
new file mode 100644
index 0000000000..1d6016d7a1
--- /dev/null
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/draft/WxMpDraftProductInfo.java
@@ -0,0 +1,48 @@
+package me.chanjar.weixin.mp.bean.draft;
+
+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.common.util.json.WxGsonBuilder;
+
+import java.io.Serializable;
+
+/**
+ * 草稿箱能力-商品相关信息
+ *
+ * @author 阿杆
+ * created on 2025/5/23
+ */
+@Data
+@Builder
+@Accessors(chain = true)
+@NoArgsConstructor
+@AllArgsConstructor
+public class WxMpDraftProductInfo implements Serializable {
+  private static final long serialVersionUID = 8637785998127610863L;
+
+  /**
+   * 文末插入商品相关信息
+   */
+  @SerializedName("footer_product_info")
+  private FooterProductInfo footerProductInfo;
+
+  public static WxMpDraftProductInfo fromJson(String json) {
+    return WxGsonBuilder.create().fromJson(json, WxMpDraftProductInfo.class);
+  }
+
+  @Data
+  @NoArgsConstructor
+  @AllArgsConstructor
+  public static class FooterProductInfo {
+    /**
+     * 商品key
+     */
+    @SerializedName("product_key")
+    private String productKey;
+  }
+
+}
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/config/impl/WxMpMapConfigImpl.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/config/impl/WxMpMapConfigImpl.java
index cd701d1efc..72e6e615f7 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/config/impl/WxMpMapConfigImpl.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/config/impl/WxMpMapConfigImpl.java
@@ -15,7 +15,7 @@ public class WxMpMapConfigImpl extends WxMpDefaultConfigImpl {
 
   private static final long serialVersionUID = 5311395137835650104L;
 
-  private static final ConcurrentHashMap CONCURRENT_HASH_MAP = new ConcurrentHashMap<>(1);
+  private final ConcurrentHashMap CONCURRENT_HASH_MAP = new ConcurrentHashMap<>(1);
 
   private static final String MAP_KEY = "access_token";
 
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/enums/AiLangType.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/enums/AiLangType.java
index b37772b01a..5049e88565 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/enums/AiLangType.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/enums/AiLangType.java
@@ -21,7 +21,7 @@ public enum AiLangType {
    */
   en_US("en_US");
 
-  private String code;
+  private final String code;
 
   AiLangType(String code) {
     this.code = code;
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/enums/WxCardType.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/enums/WxCardType.java
index 568f3cdedb..bb360eba3a 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/enums/WxCardType.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/enums/WxCardType.java
@@ -14,7 +14,7 @@ public enum WxCardType {
   GIFT("GIFT"),
   GENERAL_COUPON("GENERAL_COUPON");
 
-  private String code;
+  private final String code;
 
   WxCardType(String code) {
     this.code = code;
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/WxMpConfigStorageHolder.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/WxMpConfigStorageHolder.java
index b5e0dd8847..1a2f7a9d3c 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/WxMpConfigStorageHolder.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/WxMpConfigStorageHolder.java
@@ -5,12 +5,7 @@
  * created on  2019-03-20 22:06
  */
 public class WxMpConfigStorageHolder {
-  private static final ThreadLocal THREAD_LOCAL = new ThreadLocal() {
-    @Override
-    protected String initialValue() {
-      return "default";
-    }
-  };
+  private static final ThreadLocal THREAD_LOCAL = ThreadLocal.withInitial(() -> "default");
 
   public static String get() {
     return THREAD_LOCAL.get();
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 b14023ef91..99d759f32f 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
@@ -17,8 +17,6 @@
  */
 package me.chanjar.weixin.mp.util.crypto;
 
-import com.google.common.base.CharMatcher;
-import com.google.common.io.BaseEncoding;
 import me.chanjar.weixin.mp.config.WxMpConfigStorage;
 import org.apache.commons.lang3.StringUtils;
 
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/material/MaterialDeleteApacheHttpRequestExecutor.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/material/MaterialDeleteApacheHttpRequestExecutor.java
index 72fcaf1b3f..3d5cc58e7a 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/material/MaterialDeleteApacheHttpRequestExecutor.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/material/MaterialDeleteApacheHttpRequestExecutor.java
@@ -8,7 +8,6 @@
 import me.chanjar.weixin.common.util.json.WxGsonBuilder;
 import org.apache.http.HttpHost;
 import org.apache.http.client.config.RequestConfig;
-import org.apache.http.client.methods.CloseableHttpResponse;
 import org.apache.http.client.methods.HttpPost;
 import org.apache.http.entity.StringEntity;
 import org.apache.http.impl.client.CloseableHttpClient;
@@ -21,7 +20,7 @@
  * Created by ecoolper on 2017/5/5.
  */
 public class MaterialDeleteApacheHttpRequestExecutor extends MaterialDeleteRequestExecutor {
-  public MaterialDeleteApacheHttpRequestExecutor(RequestHttp requestHttp) {
+  public MaterialDeleteApacheHttpRequestExecutor(RequestHttp requestHttp) {
     super(requestHttp);
   }
 
@@ -36,16 +35,11 @@ public Boolean execute(String uri, String materialId, WxType wxType) throws WxEr
     Map params = new HashMap<>();
     params.put("media_id", materialId);
     httpPost.setEntity(new StringEntity(WxGsonBuilder.create().toJson(params)));
-    try (CloseableHttpResponse response = requestHttp.getRequestHttpClient().execute(httpPost)) {
-      String responseContent = Utf8ResponseHandler.INSTANCE.handleResponse(response);
-      WxError error = WxError.fromJson(responseContent, WxType.MP);
-      if (error.getErrorCode() != 0) {
-        throw new WxErrorException(error);
-      } else {
-        return true;
-      }
-    } finally {
-      httpPost.releaseConnection();
+    String responseContent = requestHttp.getRequestHttpClient().execute(httpPost, Utf8ResponseHandler.INSTANCE);
+    WxError error = WxError.fromJson(responseContent, WxType.MP);
+    if (error.getErrorCode() != 0) {
+      throw new WxErrorException(error);
     }
+    return true;
   }
 }
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/material/MaterialDeleteHttpComponentsRequestExecutor.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/material/MaterialDeleteHttpComponentsRequestExecutor.java
new file mode 100644
index 0000000000..46f8f16988
--- /dev/null
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/material/MaterialDeleteHttpComponentsRequestExecutor.java
@@ -0,0 +1,42 @@
+package me.chanjar.weixin.mp.util.requestexecuter.material;
+
+import me.chanjar.weixin.common.enums.WxType;
+import me.chanjar.weixin.common.error.WxError;
+import me.chanjar.weixin.common.error.WxErrorException;
+import me.chanjar.weixin.common.util.http.RequestHttp;
+import me.chanjar.weixin.common.util.http.hc.Utf8ResponseHandler;
+import me.chanjar.weixin.common.util.json.WxGsonBuilder;
+import org.apache.hc.client5.http.classic.methods.HttpPost;
+import org.apache.hc.client5.http.config.RequestConfig;
+import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
+import org.apache.hc.core5.http.HttpHost;
+import org.apache.hc.core5.http.io.entity.StringEntity;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+public class MaterialDeleteHttpComponentsRequestExecutor extends MaterialDeleteRequestExecutor {
+  public MaterialDeleteHttpComponentsRequestExecutor(RequestHttp requestHttp) {
+    super(requestHttp);
+  }
+
+  @Override
+  public Boolean execute(String uri, String materialId, WxType wxType) throws WxErrorException, IOException {
+    HttpPost httpPost = new HttpPost(uri);
+    if (requestHttp.getRequestHttpProxy() != null) {
+      RequestConfig config = RequestConfig.custom().setProxy(requestHttp.getRequestHttpProxy()).build();
+      httpPost.setConfig(config);
+    }
+
+    Map params = new HashMap<>();
+    params.put("media_id", materialId);
+    httpPost.setEntity(new StringEntity(WxGsonBuilder.create().toJson(params)));
+    String responseContent = requestHttp.getRequestHttpClient().execute(httpPost, Utf8ResponseHandler.INSTANCE);
+    WxError error = WxError.fromJson(responseContent, WxType.MP);
+    if (error.getErrorCode() != 0) {
+      throw new WxErrorException(error);
+    }
+    return true;
+  }
+}
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/material/MaterialDeleteJoddHttpRequestExecutor.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/material/MaterialDeleteJoddHttpRequestExecutor.java
index 318299bb34..5e8d5ee8f5 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/material/MaterialDeleteJoddHttpRequestExecutor.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/material/MaterialDeleteJoddHttpRequestExecutor.java
@@ -4,7 +4,6 @@
 import jodd.http.HttpRequest;
 import jodd.http.HttpResponse;
 import jodd.http.ProxyInfo;
-import jodd.util.StringPool;
 
 import me.chanjar.weixin.common.enums.WxType;
 import me.chanjar.weixin.common.error.WxError;
@@ -18,7 +17,7 @@
  * Created by ecoolper on 2017/5/5.
  */
 public class MaterialDeleteJoddHttpRequestExecutor extends MaterialDeleteRequestExecutor {
-  public MaterialDeleteJoddHttpRequestExecutor(RequestHttp requestHttp) {
+  public MaterialDeleteJoddHttpRequestExecutor(RequestHttp requestHttp) {
     super(requestHttp);
   }
 
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/material/MaterialDeleteOkhttpRequestExecutor.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/material/MaterialDeleteOkhttpRequestExecutor.java
index ed9aaa8a84..7ad3ebb6fd 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/material/MaterialDeleteOkhttpRequestExecutor.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/material/MaterialDeleteOkhttpRequestExecutor.java
@@ -23,7 +23,7 @@
 public class MaterialDeleteOkhttpRequestExecutor extends MaterialDeleteRequestExecutor {
   private final Logger logger = LoggerFactory.getLogger(this.getClass());
 
-  public MaterialDeleteOkhttpRequestExecutor(RequestHttp requestHttp) {
+  public MaterialDeleteOkhttpRequestExecutor(RequestHttp requestHttp) {
     super(requestHttp);
   }
 
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/material/MaterialDeleteRequestExecutor.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/material/MaterialDeleteRequestExecutor.java
index 18e696938b..a6dea564e2 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/material/MaterialDeleteRequestExecutor.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/material/MaterialDeleteRequestExecutor.java
@@ -1,17 +1,21 @@
 package me.chanjar.weixin.mp.util.requestexecuter.material;
 
-import java.io.IOException;
-
+import jodd.http.HttpConnectionProvider;
+import jodd.http.ProxyInfo;
 import me.chanjar.weixin.common.enums.WxType;
 import me.chanjar.weixin.common.error.WxErrorException;
 import me.chanjar.weixin.common.util.http.RequestExecutor;
 import me.chanjar.weixin.common.util.http.RequestHttp;
 import me.chanjar.weixin.common.util.http.ResponseHandler;
+import me.chanjar.weixin.common.util.http.okhttp.OkHttpProxyInfo;
+import okhttp3.OkHttpClient;
+
+import java.io.IOException;
 
 public abstract class MaterialDeleteRequestExecutor implements RequestExecutor {
   protected RequestHttp requestHttp;
 
-  public MaterialDeleteRequestExecutor(RequestHttp requestHttp) {
+  public MaterialDeleteRequestExecutor(RequestHttp requestHttp) {
     this.requestHttp = requestHttp;
   }
 
@@ -20,16 +24,21 @@ public void execute(String uri, String data, ResponseHandler handler, W
     handler.handle(this.execute(uri, data, wxType));
   }
 
-  public static RequestExecutor create(RequestHttp requestHttp) {
+  @SuppressWarnings("unchecked")
+  public static RequestExecutor create(RequestHttp requestHttp) {
     switch (requestHttp.getRequestType()) {
       case APACHE_HTTP:
-        return new MaterialDeleteApacheHttpRequestExecutor(requestHttp);
+        return new MaterialDeleteApacheHttpRequestExecutor(
+          (RequestHttp) requestHttp);
       case JODD_HTTP:
-        return new MaterialDeleteJoddHttpRequestExecutor(requestHttp);
+        return new MaterialDeleteJoddHttpRequestExecutor((RequestHttp) requestHttp);
       case OK_HTTP:
-        return new MaterialDeleteOkhttpRequestExecutor(requestHttp);
+        return new MaterialDeleteOkhttpRequestExecutor((RequestHttp) requestHttp);
+      case HTTP_COMPONENTS:
+        return new MaterialDeleteHttpComponentsRequestExecutor(
+          (RequestHttp) requestHttp);
       default:
-        return null;
+        throw new IllegalArgumentException("不支持的http执行器类型:" + requestHttp.getRequestType());
     }
   }
 
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/material/MaterialNewsInfoApacheHttpRequestExecutor.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/material/MaterialNewsInfoApacheHttpRequestExecutor.java
index a4c92cd55c..0059e17295 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/material/MaterialNewsInfoApacheHttpRequestExecutor.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/material/MaterialNewsInfoApacheHttpRequestExecutor.java
@@ -29,7 +29,7 @@
 public class MaterialNewsInfoApacheHttpRequestExecutor
   extends MaterialNewsInfoRequestExecutor {
 
-  public MaterialNewsInfoApacheHttpRequestExecutor(RequestHttp requestHttp) {
+  public MaterialNewsInfoApacheHttpRequestExecutor(RequestHttp requestHttp) {
     super(requestHttp);
   }
 
@@ -51,8 +51,6 @@ public WxMpMaterialNews execute(String uri, String materialId, WxType wxType) th
       } else {
         return WxMpGsonBuilder.create().fromJson(responseContent, WxMpMaterialNews.class);
       }
-    } finally {
-      httpPost.releaseConnection();
     }
   }
 }
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/material/MaterialNewsInfoHttpComponentsRequestExecutor.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/material/MaterialNewsInfoHttpComponentsRequestExecutor.java
new file mode 100644
index 0000000000..ddf3ad6762
--- /dev/null
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/material/MaterialNewsInfoHttpComponentsRequestExecutor.java
@@ -0,0 +1,46 @@
+package me.chanjar.weixin.mp.util.requestexecuter.material;
+
+import com.google.common.collect.ImmutableMap;
+import lombok.extern.slf4j.Slf4j;
+import me.chanjar.weixin.common.enums.WxType;
+import me.chanjar.weixin.common.error.WxError;
+import me.chanjar.weixin.common.error.WxErrorException;
+import me.chanjar.weixin.common.util.http.RequestHttp;
+import me.chanjar.weixin.common.util.http.hc.Utf8ResponseHandler;
+import me.chanjar.weixin.common.util.json.WxGsonBuilder;
+import me.chanjar.weixin.mp.bean.material.WxMpMaterialNews;
+import me.chanjar.weixin.mp.util.json.WxMpGsonBuilder;
+import org.apache.hc.client5.http.classic.methods.HttpPost;
+import org.apache.hc.client5.http.config.RequestConfig;
+import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
+import org.apache.hc.core5.http.HttpHost;
+import org.apache.hc.core5.http.io.entity.StringEntity;
+
+import java.io.IOException;
+
+@Slf4j
+public class MaterialNewsInfoHttpComponentsRequestExecutor extends MaterialNewsInfoRequestExecutor {
+
+  public MaterialNewsInfoHttpComponentsRequestExecutor(RequestHttp requestHttp) {
+    super(requestHttp);
+  }
+
+  @Override
+  public WxMpMaterialNews execute(String uri, String materialId, WxType wxType) throws WxErrorException, IOException {
+    HttpPost httpPost = new HttpPost(uri);
+    if (requestHttp.getRequestHttpProxy() != null) {
+      RequestConfig config = RequestConfig.custom().setProxy(requestHttp.getRequestHttpProxy()).build();
+      httpPost.setConfig(config);
+    }
+
+    httpPost.setEntity(new StringEntity(WxGsonBuilder.create().toJson(ImmutableMap.of("media_id", materialId))));
+    String responseContent = requestHttp.getRequestHttpClient().execute(httpPost, Utf8ResponseHandler.INSTANCE);
+    log.debug("响应原始数据:{}", responseContent);
+    WxError error = WxError.fromJson(responseContent, WxType.MP);
+    if (error.getErrorCode() != 0) {
+      throw new WxErrorException(error);
+    } else {
+      return WxMpGsonBuilder.create().fromJson(responseContent, WxMpMaterialNews.class);
+    }
+  }
+}
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/material/MaterialNewsInfoJoddHttpRequestExecutor.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/material/MaterialNewsInfoJoddHttpRequestExecutor.java
index 780c0734e1..d1be53b923 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/material/MaterialNewsInfoJoddHttpRequestExecutor.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/material/MaterialNewsInfoJoddHttpRequestExecutor.java
@@ -5,7 +5,6 @@
 import jodd.http.HttpRequest;
 import jodd.http.HttpResponse;
 import jodd.http.ProxyInfo;
-import jodd.util.StringPool;
 
 import lombok.extern.slf4j.Slf4j;
 import me.chanjar.weixin.common.enums.WxType;
@@ -15,8 +14,6 @@
 import me.chanjar.weixin.common.util.json.WxGsonBuilder;
 import me.chanjar.weixin.mp.bean.material.WxMpMaterialNews;
 import me.chanjar.weixin.mp.util.json.WxMpGsonBuilder;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 import java.io.IOException;
 import java.nio.charset.StandardCharsets;
@@ -26,7 +23,7 @@
  */
 @Slf4j
 public class MaterialNewsInfoJoddHttpRequestExecutor extends MaterialNewsInfoRequestExecutor {
-  public MaterialNewsInfoJoddHttpRequestExecutor(RequestHttp requestHttp) {
+  public MaterialNewsInfoJoddHttpRequestExecutor(RequestHttp requestHttp) {
     super(requestHttp);
   }
 
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/material/MaterialNewsInfoOkhttpRequestExecutor.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/material/MaterialNewsInfoOkhttpRequestExecutor.java
index 2e3f14dddd..25e0074e9e 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/material/MaterialNewsInfoOkhttpRequestExecutor.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/material/MaterialNewsInfoOkhttpRequestExecutor.java
@@ -22,7 +22,7 @@
  */
 @Slf4j
 public class MaterialNewsInfoOkhttpRequestExecutor extends MaterialNewsInfoRequestExecutor {
-  public MaterialNewsInfoOkhttpRequestExecutor(RequestHttp requestHttp) {
+  public MaterialNewsInfoOkhttpRequestExecutor(RequestHttp requestHttp) {
     super(requestHttp);
   }
 
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/material/MaterialNewsInfoRequestExecutor.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/material/MaterialNewsInfoRequestExecutor.java
index 41374809c6..ca06327abd 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/material/MaterialNewsInfoRequestExecutor.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/material/MaterialNewsInfoRequestExecutor.java
@@ -1,18 +1,22 @@
 package me.chanjar.weixin.mp.util.requestexecuter.material;
 
-import java.io.IOException;
-
+import jodd.http.HttpConnectionProvider;
+import jodd.http.ProxyInfo;
 import me.chanjar.weixin.common.enums.WxType;
 import me.chanjar.weixin.common.error.WxErrorException;
 import me.chanjar.weixin.common.util.http.RequestExecutor;
 import me.chanjar.weixin.common.util.http.RequestHttp;
 import me.chanjar.weixin.common.util.http.ResponseHandler;
+import me.chanjar.weixin.common.util.http.okhttp.OkHttpProxyInfo;
 import me.chanjar.weixin.mp.bean.material.WxMpMaterialNews;
+import okhttp3.OkHttpClient;
+
+import java.io.IOException;
 
 public abstract class MaterialNewsInfoRequestExecutor implements RequestExecutor {
   protected RequestHttp requestHttp;
 
-  public MaterialNewsInfoRequestExecutor(RequestHttp requestHttp) {
+  public MaterialNewsInfoRequestExecutor(RequestHttp requestHttp) {
     this.requestHttp = requestHttp;
   }
 
@@ -21,17 +25,21 @@ public void execute(String uri, String data, ResponseHandler h
     handler.handle(this.execute(uri, data, wxType));
   }
 
-  public static RequestExecutor create(RequestHttp requestHttp) {
+  @SuppressWarnings("unchecked")
+  public static RequestExecutor create(RequestHttp requestHttp) {
     switch (requestHttp.getRequestType()) {
       case APACHE_HTTP:
-        return new MaterialNewsInfoApacheHttpRequestExecutor(requestHttp);
+        return new MaterialNewsInfoApacheHttpRequestExecutor(
+          (RequestHttp) requestHttp);
       case JODD_HTTP:
-        return new MaterialNewsInfoJoddHttpRequestExecutor(requestHttp);
+        return new MaterialNewsInfoJoddHttpRequestExecutor((RequestHttp) requestHttp);
       case OK_HTTP:
-        return new MaterialNewsInfoOkhttpRequestExecutor(requestHttp);
+        return new MaterialNewsInfoOkhttpRequestExecutor((RequestHttp) requestHttp);
+      case HTTP_COMPONENTS:
+        return new MaterialNewsInfoHttpComponentsRequestExecutor(
+          (RequestHttp) requestHttp);
       default:
-        //TODO 需要优化抛出异常
-        return null;
+        throw new IllegalArgumentException("不支持的http执行器类型:" + requestHttp.getRequestType());
     }
   }
 
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/material/MaterialUploadApacheHttpRequestExecutor.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/material/MaterialUploadApacheHttpRequestExecutor.java
index 6a31484420..bf1b42fb9b 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/material/MaterialUploadApacheHttpRequestExecutor.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/material/MaterialUploadApacheHttpRequestExecutor.java
@@ -8,7 +8,6 @@
 import me.chanjar.weixin.common.util.json.WxGsonBuilder;
 import me.chanjar.weixin.mp.bean.material.WxMpMaterial;
 import me.chanjar.weixin.mp.bean.material.WxMpMaterialUploadResult;
-import org.apache.http.Consts;
 import org.apache.http.HttpHost;
 import org.apache.http.client.config.RequestConfig;
 import org.apache.http.client.methods.CloseableHttpResponse;
@@ -22,13 +21,14 @@
 import java.io.File;
 import java.io.FileNotFoundException;
 import java.io.IOException;
+import java.nio.charset.StandardCharsets;
 import java.util.Map;
 
 /**
  * Created by ecoolper on 2017/5/5.
  */
 public class MaterialUploadApacheHttpRequestExecutor extends MaterialUploadRequestExecutor {
-  public MaterialUploadApacheHttpRequestExecutor(RequestHttp requestHttp) {
+  public MaterialUploadApacheHttpRequestExecutor(RequestHttp requestHttp) {
     super(requestHttp);
   }
 
@@ -56,7 +56,7 @@ public WxMpMaterialUploadResult execute(String uri, WxMpMaterial material, WxTyp
     Map form = material.getForm();
     if (material.getForm() != null) {
       multipartEntityBuilder.addPart("description",
-        new StringBody(WxGsonBuilder.create().toJson(form), ContentType.create("text/plain", Consts.UTF_8)));
+        new StringBody(WxGsonBuilder.create().toJson(form), ContentType.TEXT_PLAIN.withCharset(StandardCharsets.UTF_8)));
     }
     httpPost.setEntity(multipartEntityBuilder.build());
 
@@ -68,8 +68,6 @@ public WxMpMaterialUploadResult execute(String uri, WxMpMaterial material, WxTyp
       } else {
         return WxMpMaterialUploadResult.fromJson(responseContent);
       }
-    } finally {
-      httpPost.releaseConnection();
     }
   }
 }
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/material/MaterialUploadHttpComponentsRequestExecutor.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/material/MaterialUploadHttpComponentsRequestExecutor.java
new file mode 100644
index 0000000000..05ae0fe506
--- /dev/null
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/material/MaterialUploadHttpComponentsRequestExecutor.java
@@ -0,0 +1,67 @@
+package me.chanjar.weixin.mp.util.requestexecuter.material;
+
+import me.chanjar.weixin.common.enums.WxType;
+import me.chanjar.weixin.common.error.WxError;
+import me.chanjar.weixin.common.error.WxErrorException;
+import me.chanjar.weixin.common.util.http.RequestHttp;
+import me.chanjar.weixin.common.util.http.hc.Utf8ResponseHandler;
+import me.chanjar.weixin.common.util.json.WxGsonBuilder;
+import me.chanjar.weixin.mp.bean.material.WxMpMaterial;
+import me.chanjar.weixin.mp.bean.material.WxMpMaterialUploadResult;
+import org.apache.hc.client5.http.classic.methods.HttpPost;
+import org.apache.hc.client5.http.config.RequestConfig;
+import org.apache.hc.client5.http.entity.mime.HttpMultipartMode;
+import org.apache.hc.client5.http.entity.mime.MultipartEntityBuilder;
+import org.apache.hc.client5.http.entity.mime.StringBody;
+import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
+import org.apache.hc.core5.http.ContentType;
+import org.apache.hc.core5.http.HttpHost;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.Map;
+
+public class MaterialUploadHttpComponentsRequestExecutor extends MaterialUploadRequestExecutor {
+  public MaterialUploadHttpComponentsRequestExecutor(RequestHttp requestHttp) {
+    super(requestHttp);
+  }
+
+  @Override
+  public WxMpMaterialUploadResult execute(String uri, WxMpMaterial material, WxType wxType) throws WxErrorException, IOException {
+    HttpPost httpPost = new HttpPost(uri);
+    if (requestHttp.getRequestHttpProxy() != null) {
+      RequestConfig response = RequestConfig.custom().setProxy(requestHttp.getRequestHttpProxy()).build();
+      httpPost.setConfig(response);
+    }
+
+    if (material == null) {
+      throw new WxErrorException("非法请求,material参数为空");
+    }
+
+    File file = material.getFile();
+    if (file == null || !file.exists()) {
+      throw new FileNotFoundException();
+    }
+
+    MultipartEntityBuilder multipartEntityBuilder = MultipartEntityBuilder.create();
+    multipartEntityBuilder
+      .addBinaryBody("media", file)
+      .setMode(HttpMultipartMode.EXTENDED);
+    Map form = material.getForm();
+    if (material.getForm() != null) {
+      multipartEntityBuilder.addPart("description",
+        new StringBody(WxGsonBuilder.create().toJson(form), ContentType.TEXT_PLAIN.withCharset(StandardCharsets.UTF_8)));
+    }
+    httpPost.setEntity(multipartEntityBuilder.build());
+
+    String responseContent = requestHttp.getRequestHttpClient().execute(httpPost, Utf8ResponseHandler.INSTANCE);
+    WxError error = WxError.fromJson(responseContent, WxType.MP);
+    if (error.getErrorCode() != 0) {
+      throw new WxErrorException(error);
+    } else {
+      return WxMpMaterialUploadResult.fromJson(responseContent);
+    }
+  }
+}
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/material/MaterialUploadJoddHttpRequestExecutor.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/material/MaterialUploadJoddHttpRequestExecutor.java
index d4c4dfbf89..23740328f2 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/material/MaterialUploadJoddHttpRequestExecutor.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/material/MaterialUploadJoddHttpRequestExecutor.java
@@ -4,7 +4,6 @@
 import jodd.http.HttpRequest;
 import jodd.http.HttpResponse;
 import jodd.http.ProxyInfo;
-import jodd.util.StringPool;
 
 import me.chanjar.weixin.common.enums.WxType;
 import me.chanjar.weixin.common.error.WxError;
@@ -24,7 +23,7 @@
  * Created by ecoolper on 2017/5/5.
  */
 public class MaterialUploadJoddHttpRequestExecutor extends MaterialUploadRequestExecutor {
-  public MaterialUploadJoddHttpRequestExecutor(RequestHttp requestHttp) {
+  public MaterialUploadJoddHttpRequestExecutor(RequestHttp requestHttp) {
     super(requestHttp);
   }
 
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/material/MaterialUploadOkhttpRequestExecutor.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/material/MaterialUploadOkhttpRequestExecutor.java
index 7416f94f0e..20e4622415 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/material/MaterialUploadOkhttpRequestExecutor.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/material/MaterialUploadOkhttpRequestExecutor.java
@@ -23,7 +23,7 @@
 public class MaterialUploadOkhttpRequestExecutor extends MaterialUploadRequestExecutor {
   private final Logger logger = LoggerFactory.getLogger(this.getClass());
 
-  public MaterialUploadOkhttpRequestExecutor(RequestHttp requestHttp) {
+  public MaterialUploadOkhttpRequestExecutor(RequestHttp requestHttp) {
     super(requestHttp);
   }
 
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/material/MaterialUploadRequestExecutor.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/material/MaterialUploadRequestExecutor.java
index 9044d052a8..76ad3f88fa 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/material/MaterialUploadRequestExecutor.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/material/MaterialUploadRequestExecutor.java
@@ -1,14 +1,18 @@
 package me.chanjar.weixin.mp.util.requestexecuter.material;
 
-import java.io.IOException;
-
+import jodd.http.HttpConnectionProvider;
+import jodd.http.ProxyInfo;
 import me.chanjar.weixin.common.enums.WxType;
 import me.chanjar.weixin.common.error.WxErrorException;
 import me.chanjar.weixin.common.util.http.RequestExecutor;
 import me.chanjar.weixin.common.util.http.RequestHttp;
 import me.chanjar.weixin.common.util.http.ResponseHandler;
+import me.chanjar.weixin.common.util.http.okhttp.OkHttpProxyInfo;
 import me.chanjar.weixin.mp.bean.material.WxMpMaterial;
 import me.chanjar.weixin.mp.bean.material.WxMpMaterialUploadResult;
+import okhttp3.OkHttpClient;
+
+import java.io.IOException;
 
 /**
  * @author codepiano
@@ -16,7 +20,7 @@
 public abstract class MaterialUploadRequestExecutor implements RequestExecutor {
   protected RequestHttp requestHttp;
 
-  public MaterialUploadRequestExecutor(RequestHttp requestHttp) {
+  public MaterialUploadRequestExecutor(RequestHttp requestHttp) {
     this.requestHttp = requestHttp;
   }
 
@@ -25,16 +29,21 @@ public void execute(String uri, WxMpMaterial data, ResponseHandler create(RequestHttp requestHttp) {
+  @SuppressWarnings("unchecked")
+  public static RequestExecutor create(RequestHttp requestHttp) {
     switch (requestHttp.getRequestType()) {
       case APACHE_HTTP:
-        return new MaterialUploadApacheHttpRequestExecutor(requestHttp);
+        return new MaterialUploadApacheHttpRequestExecutor(
+          (RequestHttp) requestHttp);
       case JODD_HTTP:
-        return new MaterialUploadJoddHttpRequestExecutor(requestHttp);
+        return new MaterialUploadJoddHttpRequestExecutor((RequestHttp) requestHttp);
       case OK_HTTP:
-        return new MaterialUploadOkhttpRequestExecutor(requestHttp);
+        return new MaterialUploadOkhttpRequestExecutor((RequestHttp) requestHttp);
+      case HTTP_COMPONENTS:
+        return new MaterialUploadHttpComponentsRequestExecutor(
+          (RequestHttp) requestHttp);
       default:
-        return null;
+        throw new IllegalArgumentException("不支持的http执行器类型:" + requestHttp.getRequestType());
     }
   }
 
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/material/MaterialVideoInfoApacheHttpRequestExecutor.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/material/MaterialVideoInfoApacheHttpRequestExecutor.java
index 7034379fbe..387ed50c9e 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/material/MaterialVideoInfoApacheHttpRequestExecutor.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/material/MaterialVideoInfoApacheHttpRequestExecutor.java
@@ -9,7 +9,6 @@
 import me.chanjar.weixin.mp.bean.material.WxMpMaterialVideoInfoResult;
 import org.apache.http.HttpHost;
 import org.apache.http.client.config.RequestConfig;
-import org.apache.http.client.methods.CloseableHttpResponse;
 import org.apache.http.client.methods.HttpPost;
 import org.apache.http.entity.StringEntity;
 import org.apache.http.impl.client.CloseableHttpClient;
@@ -22,7 +21,7 @@
  * Created by ecoolper on 2017/5/5.
  */
 public class MaterialVideoInfoApacheHttpRequestExecutor extends MaterialVideoInfoRequestExecutor {
-  public MaterialVideoInfoApacheHttpRequestExecutor(RequestHttp requestHttp) {
+  public MaterialVideoInfoApacheHttpRequestExecutor(RequestHttp requestHttp) {
     super(requestHttp);
   }
 
@@ -37,16 +36,12 @@ public WxMpMaterialVideoInfoResult execute(String uri, String materialId, WxType
     Map params = new HashMap<>();
     params.put("media_id", materialId);
     httpPost.setEntity(new StringEntity(WxGsonBuilder.create().toJson(params)));
-    try (CloseableHttpResponse response = requestHttp.getRequestHttpClient().execute(httpPost)) {
-      String responseContent = Utf8ResponseHandler.INSTANCE.handleResponse(response);
-      WxError error = WxError.fromJson(responseContent, WxType.MP);
-      if (error.getErrorCode() != 0) {
-        throw new WxErrorException(error);
-      } else {
-        return WxMpMaterialVideoInfoResult.fromJson(responseContent);
-      }
-    } finally {
-      httpPost.releaseConnection();
+    String responseContent = requestHttp.getRequestHttpClient().execute(httpPost, Utf8ResponseHandler.INSTANCE);
+    WxError error = WxError.fromJson(responseContent, WxType.MP);
+    if (error.getErrorCode() != 0) {
+      throw new WxErrorException(error);
+    } else {
+      return WxMpMaterialVideoInfoResult.fromJson(responseContent);
     }
   }
 }
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/material/MaterialVideoInfoHttpComponentsRequestExecutor.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/material/MaterialVideoInfoHttpComponentsRequestExecutor.java
new file mode 100644
index 0000000000..2a147609d5
--- /dev/null
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/material/MaterialVideoInfoHttpComponentsRequestExecutor.java
@@ -0,0 +1,44 @@
+package me.chanjar.weixin.mp.util.requestexecuter.material;
+
+import me.chanjar.weixin.common.enums.WxType;
+import me.chanjar.weixin.common.error.WxError;
+import me.chanjar.weixin.common.error.WxErrorException;
+import me.chanjar.weixin.common.util.http.RequestHttp;
+import me.chanjar.weixin.common.util.http.hc.Utf8ResponseHandler;
+import me.chanjar.weixin.common.util.json.WxGsonBuilder;
+import me.chanjar.weixin.mp.bean.material.WxMpMaterialVideoInfoResult;
+import org.apache.hc.client5.http.classic.methods.HttpPost;
+import org.apache.hc.client5.http.config.RequestConfig;
+import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
+import org.apache.hc.core5.http.HttpHost;
+import org.apache.hc.core5.http.io.entity.StringEntity;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+public class MaterialVideoInfoHttpComponentsRequestExecutor extends MaterialVideoInfoRequestExecutor {
+  public MaterialVideoInfoHttpComponentsRequestExecutor(RequestHttp requestHttp) {
+    super(requestHttp);
+  }
+
+  @Override
+  public WxMpMaterialVideoInfoResult execute(String uri, String materialId, WxType wxType) throws WxErrorException, IOException {
+    HttpPost httpPost = new HttpPost(uri);
+    if (requestHttp.getRequestHttpProxy() != null) {
+      RequestConfig config = RequestConfig.custom().setProxy(requestHttp.getRequestHttpProxy()).build();
+      httpPost.setConfig(config);
+    }
+
+    Map params = new HashMap<>();
+    params.put("media_id", materialId);
+    httpPost.setEntity(new StringEntity(WxGsonBuilder.create().toJson(params)));
+    String responseContent = requestHttp.getRequestHttpClient().execute(httpPost, Utf8ResponseHandler.INSTANCE);
+    WxError error = WxError.fromJson(responseContent, WxType.MP);
+    if (error.getErrorCode() != 0) {
+      throw new WxErrorException(error);
+    } else {
+      return WxMpMaterialVideoInfoResult.fromJson(responseContent);
+    }
+  }
+}
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/material/MaterialVideoInfoJoddHttpRequestExecutor.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/material/MaterialVideoInfoJoddHttpRequestExecutor.java
index 9149d46794..1ea90199fb 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/material/MaterialVideoInfoJoddHttpRequestExecutor.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/material/MaterialVideoInfoJoddHttpRequestExecutor.java
@@ -4,7 +4,6 @@
 import jodd.http.HttpRequest;
 import jodd.http.HttpResponse;
 import jodd.http.ProxyInfo;
-import jodd.util.StringPool;
 
 import me.chanjar.weixin.common.enums.WxType;
 import me.chanjar.weixin.common.error.WxError;
@@ -19,7 +18,7 @@
  * Created by ecoolper on 2017/5/5.
  */
 public class MaterialVideoInfoJoddHttpRequestExecutor extends MaterialVideoInfoRequestExecutor {
-  public MaterialVideoInfoJoddHttpRequestExecutor(RequestHttp requestHttp) {
+  public MaterialVideoInfoJoddHttpRequestExecutor(RequestHttp requestHttp) {
     super(requestHttp);
   }
 
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/material/MaterialVideoInfoOkhttpRequestExecutor.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/material/MaterialVideoInfoOkhttpRequestExecutor.java
index 2e38ab003b..d548fcb4ae 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/material/MaterialVideoInfoOkhttpRequestExecutor.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/material/MaterialVideoInfoOkhttpRequestExecutor.java
@@ -20,7 +20,7 @@
 public class MaterialVideoInfoOkhttpRequestExecutor extends MaterialVideoInfoRequestExecutor {
   private final Logger logger = LoggerFactory.getLogger(this.getClass());
 
-  public MaterialVideoInfoOkhttpRequestExecutor(RequestHttp requestHttp) {
+  public MaterialVideoInfoOkhttpRequestExecutor(RequestHttp requestHttp) {
     super(requestHttp);
   }
 
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/material/MaterialVideoInfoRequestExecutor.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/material/MaterialVideoInfoRequestExecutor.java
index 5ea6fae0b2..b4073c7fec 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/material/MaterialVideoInfoRequestExecutor.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/material/MaterialVideoInfoRequestExecutor.java
@@ -1,19 +1,22 @@
 package me.chanjar.weixin.mp.util.requestexecuter.material;
 
-
-import java.io.IOException;
-
+import jodd.http.HttpConnectionProvider;
+import jodd.http.ProxyInfo;
 import me.chanjar.weixin.common.enums.WxType;
 import me.chanjar.weixin.common.error.WxErrorException;
 import me.chanjar.weixin.common.util.http.RequestExecutor;
 import me.chanjar.weixin.common.util.http.RequestHttp;
 import me.chanjar.weixin.common.util.http.ResponseHandler;
+import me.chanjar.weixin.common.util.http.okhttp.OkHttpProxyInfo;
 import me.chanjar.weixin.mp.bean.material.WxMpMaterialVideoInfoResult;
+import okhttp3.OkHttpClient;
+
+import java.io.IOException;
 
 public abstract class MaterialVideoInfoRequestExecutor implements RequestExecutor {
   protected RequestHttp requestHttp;
 
-  public MaterialVideoInfoRequestExecutor(RequestHttp requestHttp) {
+  public MaterialVideoInfoRequestExecutor(RequestHttp requestHttp) {
     this.requestHttp = requestHttp;
   }
 
@@ -22,16 +25,21 @@ public void execute(String uri, String data, ResponseHandler create(RequestHttp requestHttp) {
+  @SuppressWarnings("unchecked")
+  public static RequestExecutor create(RequestHttp requestHttp) {
     switch (requestHttp.getRequestType()) {
       case APACHE_HTTP:
-        return new MaterialVideoInfoApacheHttpRequestExecutor(requestHttp);
+        return new MaterialVideoInfoApacheHttpRequestExecutor(
+          (RequestHttp) requestHttp);
       case JODD_HTTP:
-        return new MaterialVideoInfoJoddHttpRequestExecutor(requestHttp);
+        return new MaterialVideoInfoJoddHttpRequestExecutor((RequestHttp) requestHttp);
       case OK_HTTP:
-        return new MaterialVideoInfoOkhttpRequestExecutor(requestHttp);
+        return new MaterialVideoInfoOkhttpRequestExecutor((RequestHttp) requestHttp);
+      case HTTP_COMPONENTS:
+        return new MaterialVideoInfoHttpComponentsRequestExecutor(
+          (RequestHttp) requestHttp);
       default:
-        return null;
+        throw new IllegalArgumentException("不支持的http执行器类型:" + requestHttp.getRequestType());
     }
   }
 
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/material/MaterialVoiceAndImageDownloadApacheHttpRequestExecutor.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/material/MaterialVoiceAndImageDownloadApacheHttpRequestExecutor.java
index d11591edf1..05395319cc 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/material/MaterialVoiceAndImageDownloadApacheHttpRequestExecutor.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/material/MaterialVoiceAndImageDownloadApacheHttpRequestExecutor.java
@@ -26,7 +26,7 @@
  * Created by ecoolper on 2017/5/5.
  */
 public class MaterialVoiceAndImageDownloadApacheHttpRequestExecutor extends MaterialVoiceAndImageDownloadRequestExecutor {
-  public MaterialVoiceAndImageDownloadApacheHttpRequestExecutor(RequestHttp requestHttp, File tmpDirFile) {
+  public MaterialVoiceAndImageDownloadApacheHttpRequestExecutor(RequestHttp requestHttp, File tmpDirFile) {
     super(requestHttp, tmpDirFile);
   }
 
@@ -57,8 +57,6 @@ public InputStream execute(String uri, String materialId, WxType wxType) throws
         }
       }
       return new ByteArrayInputStream(responseContent);
-    } finally {
-      httpPost.releaseConnection();
     }
   }
 }
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/material/MaterialVoiceAndImageDownloadHttpComponentsRequestExecutor.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/material/MaterialVoiceAndImageDownloadHttpComponentsRequestExecutor.java
new file mode 100644
index 0000000000..ac7df1a0ce
--- /dev/null
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/material/MaterialVoiceAndImageDownloadHttpComponentsRequestExecutor.java
@@ -0,0 +1,64 @@
+package me.chanjar.weixin.mp.util.requestexecuter.material;
+
+import me.chanjar.weixin.common.enums.WxType;
+import me.chanjar.weixin.common.error.WxError;
+import me.chanjar.weixin.common.error.WxErrorException;
+import me.chanjar.weixin.common.util.http.RequestHttp;
+import me.chanjar.weixin.common.util.http.hc.InputStreamResponseHandler;
+import me.chanjar.weixin.common.util.json.WxGsonBuilder;
+import org.apache.commons.io.IOUtils;
+import org.apache.hc.client5.http.ClientProtocolException;
+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.client5.http.impl.classic.CloseableHttpResponse;
+import org.apache.hc.core5.http.HttpException;
+import org.apache.hc.core5.http.HttpHost;
+import org.apache.hc.core5.http.io.entity.StringEntity;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.HashMap;
+import java.util.Map;
+
+public class MaterialVoiceAndImageDownloadHttpComponentsRequestExecutor
+  extends MaterialVoiceAndImageDownloadRequestExecutor {
+  public MaterialVoiceAndImageDownloadHttpComponentsRequestExecutor(RequestHttp requestHttp, File tmpDirFile) {
+    super(requestHttp, tmpDirFile);
+  }
+
+  @Override
+  public InputStream execute(String uri, String materialId, WxType wxType) throws WxErrorException, IOException {
+    HttpPost httpPost = new HttpPost(uri);
+    if (requestHttp.getRequestHttpProxy() != null) {
+      RequestConfig config = RequestConfig.custom().setProxy(requestHttp.getRequestHttpProxy()).build();
+      httpPost.setConfig(config);
+    }
+
+    Map params = new HashMap<>();
+    params.put("media_id", materialId);
+    httpPost.setEntity(new StringEntity(WxGsonBuilder.create().toJson(params)));
+    try (CloseableHttpResponse response = requestHttp.getRequestHttpClient().execute(httpPost);
+         InputStream inputStream = InputStreamResponseHandler.INSTANCE.handleResponse(response)) {
+      // 下载媒体文件出错
+      byte[] responseContent = IOUtils.toByteArray(inputStream);
+      String responseContentString = new String(responseContent, StandardCharsets.UTF_8);
+      if (responseContentString.length() <= 215) {
+        try {
+          WxError wxError = WxGsonBuilder.create().fromJson(responseContentString, WxError.class);
+          if (wxError.getErrorCode() != 0) {
+            throw new WxErrorException(wxError);
+          }
+        } catch (com.google.gson.JsonSyntaxException ex) {
+          return new ByteArrayInputStream(responseContent);
+        }
+      }
+      return new ByteArrayInputStream(responseContent);
+    } catch (HttpException httpException) {
+      throw new ClientProtocolException(httpException.getMessage(), httpException);
+    }
+  }
+}
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/material/MaterialVoiceAndImageDownloadJoddHttpRequestExecutor.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/material/MaterialVoiceAndImageDownloadJoddHttpRequestExecutor.java
index c946e9b4b6..b1264864e5 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/material/MaterialVoiceAndImageDownloadJoddHttpRequestExecutor.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/material/MaterialVoiceAndImageDownloadJoddHttpRequestExecutor.java
@@ -4,7 +4,6 @@
 import jodd.http.HttpRequest;
 import jodd.http.HttpResponse;
 import jodd.http.ProxyInfo;
-import jodd.util.StringPool;
 
 import me.chanjar.weixin.common.enums.WxType;
 import me.chanjar.weixin.common.error.WxError;
@@ -23,7 +22,7 @@
  * Created by ecoolper on 2017/5/5.
  */
 public class MaterialVoiceAndImageDownloadJoddHttpRequestExecutor extends MaterialVoiceAndImageDownloadRequestExecutor {
-  public MaterialVoiceAndImageDownloadJoddHttpRequestExecutor(RequestHttp requestHttp, File tmpDirFile) {
+  public MaterialVoiceAndImageDownloadJoddHttpRequestExecutor(RequestHttp requestHttp, File tmpDirFile) {
     super(requestHttp, tmpDirFile);
   }
 
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/material/MaterialVoiceAndImageDownloadOkhttpRequestExecutor.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/material/MaterialVoiceAndImageDownloadOkhttpRequestExecutor.java
index b77958a4e9..3823440490 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/material/MaterialVoiceAndImageDownloadOkhttpRequestExecutor.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/material/MaterialVoiceAndImageDownloadOkhttpRequestExecutor.java
@@ -8,8 +8,6 @@
 import me.chanjar.weixin.common.util.http.okhttp.OkHttpProxyInfo;
 import me.chanjar.weixin.common.util.json.WxGsonBuilder;
 import okhttp3.*;
-import okio.BufferedSink;
-import okio.Okio;
 import org.apache.commons.io.IOUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -22,7 +20,7 @@
 public class MaterialVoiceAndImageDownloadOkhttpRequestExecutor extends MaterialVoiceAndImageDownloadRequestExecutor {
   private final Logger logger = LoggerFactory.getLogger(this.getClass());
 
-  public MaterialVoiceAndImageDownloadOkhttpRequestExecutor(RequestHttp requestHttp, File tmpDirFile) {
+  public MaterialVoiceAndImageDownloadOkhttpRequestExecutor(RequestHttp requestHttp, File tmpDirFile) {
     super(requestHttp, tmpDirFile);
   }
 
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/material/MaterialVoiceAndImageDownloadRequestExecutor.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/material/MaterialVoiceAndImageDownloadRequestExecutor.java
index b482ddbcd1..42994a7423 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/material/MaterialVoiceAndImageDownloadRequestExecutor.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/material/MaterialVoiceAndImageDownloadRequestExecutor.java
@@ -4,17 +4,21 @@
 import java.io.IOException;
 import java.io.InputStream;
 
+import jodd.http.HttpConnectionProvider;
+import jodd.http.ProxyInfo;
 import me.chanjar.weixin.common.enums.WxType;
 import me.chanjar.weixin.common.error.WxErrorException;
 import me.chanjar.weixin.common.util.http.RequestExecutor;
 import me.chanjar.weixin.common.util.http.RequestHttp;
 import me.chanjar.weixin.common.util.http.ResponseHandler;
+import me.chanjar.weixin.common.util.http.okhttp.OkHttpProxyInfo;
+import okhttp3.OkHttpClient;
 
 public abstract class MaterialVoiceAndImageDownloadRequestExecutor implements RequestExecutor {
   protected RequestHttp requestHttp;
   protected File tmpDirFile;
 
-  public MaterialVoiceAndImageDownloadRequestExecutor(RequestHttp requestHttp, File tmpDirFile) {
+  public MaterialVoiceAndImageDownloadRequestExecutor(RequestHttp requestHttp, File tmpDirFile) {
     this.requestHttp = requestHttp;
     this.tmpDirFile = tmpDirFile;
   }
@@ -24,16 +28,21 @@ public void execute(String uri, String data, ResponseHandler handle
     handler.handle(this.execute(uri, data, wxType));
   }
 
-  public static RequestExecutor create(RequestHttp requestHttp, File tmpDirFile) {
+  @SuppressWarnings("unchecked")
+  public static RequestExecutor create(RequestHttp requestHttp, File tmpDirFile) {
     switch (requestHttp.getRequestType()) {
       case APACHE_HTTP:
-        return new MaterialVoiceAndImageDownloadApacheHttpRequestExecutor(requestHttp, tmpDirFile);
+        return new MaterialVoiceAndImageDownloadApacheHttpRequestExecutor(
+          (RequestHttp) requestHttp, tmpDirFile);
       case JODD_HTTP:
-        return new MaterialVoiceAndImageDownloadJoddHttpRequestExecutor(requestHttp, tmpDirFile);
+        return new MaterialVoiceAndImageDownloadJoddHttpRequestExecutor((RequestHttp) requestHttp, tmpDirFile);
       case OK_HTTP:
-        return new MaterialVoiceAndImageDownloadOkhttpRequestExecutor(requestHttp, tmpDirFile);
+        return new MaterialVoiceAndImageDownloadOkhttpRequestExecutor((RequestHttp) requestHttp, tmpDirFile);
+      case HTTP_COMPONENTS:
+        return new MaterialVoiceAndImageDownloadHttpComponentsRequestExecutor(
+          (RequestHttp) requestHttp, tmpDirFile);
       default:
-        return null;
+        throw new IllegalArgumentException("不支持的http执行器类型:" + requestHttp.getRequestType());
     }
   }
 
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/media/MediaImgUploadApacheHttpRequestExecutor.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/media/MediaImgUploadApacheHttpRequestExecutor.java
index 7c4ba18598..495f144f3d 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/media/MediaImgUploadApacheHttpRequestExecutor.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/media/MediaImgUploadApacheHttpRequestExecutor.java
@@ -11,7 +11,6 @@
 import org.apache.http.client.config.RequestConfig;
 import org.apache.http.client.methods.CloseableHttpResponse;
 import org.apache.http.client.methods.HttpPost;
-import org.apache.http.entity.ContentType;
 import org.apache.http.entity.mime.HttpMultipartMode;
 import org.apache.http.entity.mime.MultipartEntityBuilder;
 import org.apache.http.impl.client.CloseableHttpClient;
@@ -25,7 +24,7 @@
  * @author ecoolper
  */
 public class MediaImgUploadApacheHttpRequestExecutor extends MediaImgUploadRequestExecutor {
-  public MediaImgUploadApacheHttpRequestExecutor(RequestHttp requestHttp) {
+  public MediaImgUploadApacheHttpRequestExecutor(RequestHttp requestHttp) {
     super(requestHttp);
   }
 
@@ -56,8 +55,6 @@ public WxMediaImgUploadResult execute(String uri, File data, WxType wxType) thro
       }
 
       return WxMediaImgUploadResult.fromJson(responseContent);
-    } finally {
-      httpPost.releaseConnection();
     }
   }
 }
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/media/MediaImgUploadHttpComponentsRequestExecutor.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/media/MediaImgUploadHttpComponentsRequestExecutor.java
new file mode 100644
index 0000000000..be1d12631d
--- /dev/null
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/media/MediaImgUploadHttpComponentsRequestExecutor.java
@@ -0,0 +1,52 @@
+package me.chanjar.weixin.mp.util.requestexecuter.media;
+
+import me.chanjar.weixin.common.enums.WxType;
+import me.chanjar.weixin.common.error.WxError;
+import me.chanjar.weixin.common.error.WxErrorException;
+import me.chanjar.weixin.common.util.http.RequestHttp;
+import me.chanjar.weixin.common.util.http.hc.Utf8ResponseHandler;
+import me.chanjar.weixin.mp.bean.material.WxMediaImgUploadResult;
+import org.apache.hc.client5.http.classic.methods.HttpPost;
+import org.apache.hc.client5.http.config.RequestConfig;
+import org.apache.hc.client5.http.entity.mime.HttpMultipartMode;
+import org.apache.hc.client5.http.entity.mime.MultipartEntityBuilder;
+import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
+import org.apache.hc.core5.http.HttpEntity;
+import org.apache.hc.core5.http.HttpHost;
+
+import java.io.File;
+import java.io.IOException;
+
+public class MediaImgUploadHttpComponentsRequestExecutor extends MediaImgUploadRequestExecutor {
+  public MediaImgUploadHttpComponentsRequestExecutor(RequestHttp requestHttp) {
+    super(requestHttp);
+  }
+
+  @Override
+  public WxMediaImgUploadResult execute(String uri, File data, WxType wxType) throws WxErrorException, IOException {
+    if (data == null) {
+      throw new WxErrorException("文件对象为空");
+    }
+
+    HttpPost httpPost = new HttpPost(uri);
+    if (requestHttp.getRequestHttpProxy() != null) {
+      RequestConfig config = RequestConfig.custom().setProxy(requestHttp.getRequestHttpProxy()).build();
+      httpPost.setConfig(config);
+    }
+
+    HttpEntity entity = MultipartEntityBuilder
+      .create()
+      .addBinaryBody("media", data)
+      .setMode(HttpMultipartMode.EXTENDED)
+      .build();
+    httpPost.setEntity(entity);
+
+    String responseContent = requestHttp.getRequestHttpClient().execute(httpPost, Utf8ResponseHandler.INSTANCE);
+    WxError error = WxError.fromJson(responseContent, WxType.MP);
+    if (error.getErrorCode() != 0) {
+      throw new WxErrorException(error);
+    }
+
+    return WxMediaImgUploadResult.fromJson(responseContent);
+  }
+}
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/media/MediaImgUploadHttpRequestExecutor.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/media/MediaImgUploadHttpRequestExecutor.java
index 1ca4c7c8bf..138d8b5d3d 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/media/MediaImgUploadHttpRequestExecutor.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/media/MediaImgUploadHttpRequestExecutor.java
@@ -4,7 +4,6 @@
 import jodd.http.HttpRequest;
 import jodd.http.HttpResponse;
 import jodd.http.ProxyInfo;
-import jodd.util.StringPool;
 
 import me.chanjar.weixin.common.enums.WxType;
 import me.chanjar.weixin.common.error.WxError;
@@ -22,7 +21,7 @@
  * @author ecoolper
  */
 public class MediaImgUploadHttpRequestExecutor extends MediaImgUploadRequestExecutor {
-  public MediaImgUploadHttpRequestExecutor(RequestHttp requestHttp) {
+  public MediaImgUploadHttpRequestExecutor(RequestHttp requestHttp) {
     super(requestHttp);
   }
 
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/media/MediaImgUploadOkhttpRequestExecutor.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/media/MediaImgUploadOkhttpRequestExecutor.java
index 27677b74b4..7a6a980afe 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/media/MediaImgUploadOkhttpRequestExecutor.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/media/MediaImgUploadOkhttpRequestExecutor.java
@@ -21,7 +21,7 @@
 public class MediaImgUploadOkhttpRequestExecutor extends MediaImgUploadRequestExecutor {
   private final Logger logger = LoggerFactory.getLogger(this.getClass());
 
-  public MediaImgUploadOkhttpRequestExecutor(RequestHttp requestHttp) {
+  public MediaImgUploadOkhttpRequestExecutor(RequestHttp requestHttp) {
     super(requestHttp);
   }
 
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/media/MediaImgUploadRequestExecutor.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/media/MediaImgUploadRequestExecutor.java
index b5f42e0f8d..40a9d47155 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/media/MediaImgUploadRequestExecutor.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/media/MediaImgUploadRequestExecutor.java
@@ -3,12 +3,16 @@
 import java.io.File;
 import java.io.IOException;
 
+import jodd.http.HttpConnectionProvider;
+import jodd.http.ProxyInfo;
 import me.chanjar.weixin.common.enums.WxType;
 import me.chanjar.weixin.common.error.WxErrorException;
 import me.chanjar.weixin.common.util.http.RequestExecutor;
 import me.chanjar.weixin.common.util.http.RequestHttp;
 import me.chanjar.weixin.common.util.http.ResponseHandler;
+import me.chanjar.weixin.common.util.http.okhttp.OkHttpProxyInfo;
 import me.chanjar.weixin.mp.bean.material.WxMediaImgUploadResult;
+import okhttp3.OkHttpClient;
 
 /**
  * @author miller
@@ -16,7 +20,7 @@
 public abstract class MediaImgUploadRequestExecutor implements RequestExecutor {
   protected RequestHttp requestHttp;
 
-  public MediaImgUploadRequestExecutor(RequestHttp requestHttp) {
+  public MediaImgUploadRequestExecutor(RequestHttp requestHttp) {
     this.requestHttp = requestHttp;
   }
 
@@ -25,16 +29,21 @@ public void execute(String uri, File data, ResponseHandler create(RequestHttp requestHttp) {
+  @SuppressWarnings("unchecked")
+  public static RequestExecutor create(RequestHttp requestHttp) {
     switch (requestHttp.getRequestType()) {
       case APACHE_HTTP:
-        return new MediaImgUploadApacheHttpRequestExecutor(requestHttp);
+        return new MediaImgUploadApacheHttpRequestExecutor(
+          (RequestHttp) requestHttp);
       case JODD_HTTP:
-        return new MediaImgUploadHttpRequestExecutor(requestHttp);
+        return new MediaImgUploadHttpRequestExecutor((RequestHttp) requestHttp);
       case OK_HTTP:
-        return new MediaImgUploadOkhttpRequestExecutor(requestHttp);
+        return new MediaImgUploadOkhttpRequestExecutor((RequestHttp) requestHttp);
+      case HTTP_COMPONENTS:
+        return new MediaImgUploadHttpComponentsRequestExecutor(
+          (RequestHttp) requestHttp);
       default:
-        return null;
+        throw new IllegalArgumentException("不支持的http执行器类型:" + requestHttp.getRequestType());
     }
   }
 
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/qrcode/QrCodeApacheHttpRequestExecutor.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/qrcode/QrCodeApacheHttpRequestExecutor.java
index 2c8e5b5721..3ff6a5a369 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/qrcode/QrCodeApacheHttpRequestExecutor.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/qrcode/QrCodeApacheHttpRequestExecutor.java
@@ -26,7 +26,7 @@
  * Created by ecoolper on 2017/5/5.
  */
 public class QrCodeApacheHttpRequestExecutor extends QrCodeRequestExecutor {
-  public QrCodeApacheHttpRequestExecutor(RequestHttp requestHttp) {
+  public QrCodeApacheHttpRequestExecutor(RequestHttp requestHttp) {
     super(requestHttp);
   }
 
@@ -59,8 +59,6 @@ public File execute(String uri, WxMpQrCodeTicket ticket, WxType wxType) throws W
         }
       }
       return FileUtils.createTmpFile(inputStream, UUID.randomUUID().toString(), "jpg");
-    } finally {
-      httpGet.releaseConnection();
     }
   }
 }
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/qrcode/QrCodeHttpComponentsRequestExecutor.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/qrcode/QrCodeHttpComponentsRequestExecutor.java
new file mode 100644
index 0000000000..fbf8af0783
--- /dev/null
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/qrcode/QrCodeHttpComponentsRequestExecutor.java
@@ -0,0 +1,67 @@
+package me.chanjar.weixin.mp.util.requestexecuter.qrcode;
+
+import me.chanjar.weixin.common.enums.WxType;
+import me.chanjar.weixin.common.error.WxError;
+import me.chanjar.weixin.common.error.WxErrorException;
+import me.chanjar.weixin.common.util.fs.FileUtils;
+import me.chanjar.weixin.common.util.http.RequestHttp;
+import me.chanjar.weixin.common.util.http.hc.InputStreamResponseHandler;
+import me.chanjar.weixin.common.util.http.hc.Utf8ResponseHandler;
+import me.chanjar.weixin.mp.bean.result.WxMpQrCodeTicket;
+import org.apache.hc.client5.http.ClientProtocolException;
+import org.apache.hc.client5.http.classic.methods.HttpGet;
+import org.apache.hc.client5.http.config.RequestConfig;
+import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
+import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse;
+import org.apache.hc.core5.http.ContentType;
+import org.apache.hc.core5.http.Header;
+import org.apache.hc.core5.http.HttpException;
+import org.apache.hc.core5.http.HttpHost;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URLEncoder;
+import java.util.UUID;
+
+/**
+ * @author altusea
+ */
+public class QrCodeHttpComponentsRequestExecutor extends QrCodeRequestExecutor {
+  public QrCodeHttpComponentsRequestExecutor(RequestHttp requestHttp) {
+    super(requestHttp);
+  }
+
+  @Override
+  public File execute(String uri, WxMpQrCodeTicket ticket, WxType wxType) throws WxErrorException, IOException {
+    if (ticket != null) {
+      if (uri.indexOf('?') == -1) {
+        uri += '?';
+      }
+      uri += uri.endsWith("?")
+        ? "ticket=" + URLEncoder.encode(ticket.getTicket(), "UTF-8")
+        : "&ticket=" + URLEncoder.encode(ticket.getTicket(), "UTF-8");
+    }
+
+    HttpGet httpGet = new HttpGet(uri);
+    if (requestHttp.getRequestHttpProxy() != null) {
+      RequestConfig config = RequestConfig.custom().setProxy(requestHttp.getRequestHttpProxy()).build();
+      httpGet.setConfig(config);
+    }
+
+    try (CloseableHttpResponse response = requestHttp.getRequestHttpClient().execute(httpGet);
+         InputStream inputStream = InputStreamResponseHandler.INSTANCE.handleResponse(response)) {
+      Header[] contentTypeHeader = response.getHeaders("Content-Type");
+      if (contentTypeHeader != null && contentTypeHeader.length > 0) {
+        // 出错
+        if (ContentType.TEXT_PLAIN.getMimeType().equals(ContentType.parse(contentTypeHeader[0].getValue()).getMimeType())) {
+          String responseContent = Utf8ResponseHandler.INSTANCE.handleResponse(response);
+          throw new WxErrorException(WxError.fromJson(responseContent, WxType.MP));
+        }
+      }
+      return FileUtils.createTmpFile(inputStream, UUID.randomUUID().toString(), "jpg");
+    } catch (HttpException httpException) {
+      throw new ClientProtocolException(httpException.getMessage(), httpException);
+    }
+  }
+}
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/qrcode/QrCodeJoddHttpRequestExecutor.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/qrcode/QrCodeJoddHttpRequestExecutor.java
index 32d3d3ca75..9fcf7768ae 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/qrcode/QrCodeJoddHttpRequestExecutor.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/qrcode/QrCodeJoddHttpRequestExecutor.java
@@ -5,7 +5,6 @@
 import jodd.http.HttpResponse;
 import jodd.http.ProxyInfo;
 import jodd.net.MimeTypes;
-import jodd.util.StringPool;
 
 import me.chanjar.weixin.common.enums.WxType;
 import me.chanjar.weixin.common.error.WxError;
@@ -26,7 +25,7 @@
  * Created by ecoolper on 2017/5/5.
  */
 public class QrCodeJoddHttpRequestExecutor extends QrCodeRequestExecutor {
-  public QrCodeJoddHttpRequestExecutor(RequestHttp requestHttp) {
+  public QrCodeJoddHttpRequestExecutor(RequestHttp requestHttp) {
     super(requestHttp);
   }
 
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/qrcode/QrCodeOkhttpRequestExecutor.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/qrcode/QrCodeOkhttpRequestExecutor.java
index f6f2036ce1..42289e775c 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/qrcode/QrCodeOkhttpRequestExecutor.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/qrcode/QrCodeOkhttpRequestExecutor.java
@@ -27,7 +27,7 @@
 public class QrCodeOkhttpRequestExecutor extends QrCodeRequestExecutor {
   private final Logger logger = LoggerFactory.getLogger(this.getClass());
 
-  public QrCodeOkhttpRequestExecutor(RequestHttp requestHttp) {
+  public QrCodeOkhttpRequestExecutor(RequestHttp requestHttp) {
     super(requestHttp);
   }
 
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/qrcode/QrCodeRequestExecutor.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/qrcode/QrCodeRequestExecutor.java
index 4ca5dbc0c1..6407ac11ad 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/qrcode/QrCodeRequestExecutor.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/qrcode/QrCodeRequestExecutor.java
@@ -3,13 +3,16 @@
 import java.io.File;
 import java.io.IOException;
 
+import jodd.http.HttpConnectionProvider;
+import jodd.http.ProxyInfo;
 import me.chanjar.weixin.common.enums.WxType;
-import me.chanjar.weixin.common.error.WxError;
 import me.chanjar.weixin.common.error.WxErrorException;
 import me.chanjar.weixin.common.util.http.RequestExecutor;
 import me.chanjar.weixin.common.util.http.RequestHttp;
 import me.chanjar.weixin.common.util.http.ResponseHandler;
+import me.chanjar.weixin.common.util.http.okhttp.OkHttpProxyInfo;
 import me.chanjar.weixin.mp.bean.result.WxMpQrCodeTicket;
+import okhttp3.OkHttpClient;
 
 /**
  * 获得QrCode图片 请求执行器.
@@ -19,7 +22,7 @@
 public abstract class QrCodeRequestExecutor implements RequestExecutor {
   protected RequestHttp requestHttp;
 
-  public QrCodeRequestExecutor(RequestHttp requestHttp) {
+  public QrCodeRequestExecutor(RequestHttp requestHttp) {
     this.requestHttp = requestHttp;
   }
 
@@ -28,16 +31,21 @@ public void execute(String uri, WxMpQrCodeTicket data, ResponseHandler han
     handler.handle(this.execute(uri, data, wxType));
   }
 
-  public static RequestExecutor create(RequestHttp requestHttp) throws WxErrorException {
+  @SuppressWarnings("unchecked")
+  public static RequestExecutor create(RequestHttp requestHttp) {
     switch (requestHttp.getRequestType()) {
       case APACHE_HTTP:
-        return new QrCodeApacheHttpRequestExecutor(requestHttp);
+        return new QrCodeApacheHttpRequestExecutor(
+          (RequestHttp) requestHttp);
       case JODD_HTTP:
-        return new QrCodeJoddHttpRequestExecutor(requestHttp);
+        return new QrCodeJoddHttpRequestExecutor((RequestHttp) requestHttp);
       case OK_HTTP:
-        return new QrCodeOkhttpRequestExecutor(requestHttp);
+        return new QrCodeOkhttpRequestExecutor((RequestHttp) requestHttp);
+      case HTTP_COMPONENTS:
+        return new QrCodeHttpComponentsRequestExecutor(
+          (RequestHttp) requestHttp);
       default:
-        throw new WxErrorException("不支持的http框架");
+        throw new IllegalArgumentException("不支持的http执行器类型:" + requestHttp.getRequestType());
     }
   }
 
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/voice/VoiceUploadApacheHttpRequestExecutor.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/voice/VoiceUploadApacheHttpRequestExecutor.java
index 06aa1fcda1..f384f8f567 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/voice/VoiceUploadApacheHttpRequestExecutor.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/voice/VoiceUploadApacheHttpRequestExecutor.java
@@ -8,9 +8,7 @@
 import org.apache.http.HttpEntity;
 import org.apache.http.HttpHost;
 import org.apache.http.client.config.RequestConfig;
-import org.apache.http.client.methods.CloseableHttpResponse;
 import org.apache.http.client.methods.HttpPost;
-import org.apache.http.entity.ContentType;
 import org.apache.http.entity.mime.HttpMultipartMode;
 import org.apache.http.entity.mime.MultipartEntityBuilder;
 import org.apache.http.impl.client.CloseableHttpClient;
@@ -26,7 +24,7 @@
  * @author Binary Wang
  */
 public class VoiceUploadApacheHttpRequestExecutor extends VoiceUploadRequestExecutor {
-  public VoiceUploadApacheHttpRequestExecutor(RequestHttp requestHttp) {
+  public VoiceUploadApacheHttpRequestExecutor(RequestHttp requestHttp) {
     super(requestHttp);
   }
 
@@ -49,16 +47,11 @@ public Boolean execute(String uri, File data, WxType wxType) throws WxErrorExcep
       .build();
     httpPost.setEntity(entity);
 
-    try (CloseableHttpResponse response = requestHttp.getRequestHttpClient().execute(httpPost)) {
-      String responseContent = Utf8ResponseHandler.INSTANCE.handleResponse(response);
-      WxError error = WxError.fromJson(responseContent, WxType.MP);
-      if (error.getErrorCode() != 0) {
-        throw new WxErrorException(error);
-      }
-
-      return true;
-    } finally {
-      httpPost.releaseConnection();
+    String responseContent = requestHttp.getRequestHttpClient().execute(httpPost, Utf8ResponseHandler.INSTANCE);
+    WxError error = WxError.fromJson(responseContent, WxType.MP);
+    if (error.getErrorCode() != 0) {
+      throw new WxErrorException(error);
     }
+    return true;
   }
 }
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/voice/VoiceUploadHttpComponentsRequestExecutor.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/voice/VoiceUploadHttpComponentsRequestExecutor.java
new file mode 100644
index 0000000000..1775f04aef
--- /dev/null
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/voice/VoiceUploadHttpComponentsRequestExecutor.java
@@ -0,0 +1,50 @@
+package me.chanjar.weixin.mp.util.requestexecuter.voice;
+
+import me.chanjar.weixin.common.enums.WxType;
+import me.chanjar.weixin.common.error.WxError;
+import me.chanjar.weixin.common.error.WxErrorException;
+import me.chanjar.weixin.common.util.http.RequestHttp;
+import me.chanjar.weixin.common.util.http.hc.Utf8ResponseHandler;
+import org.apache.hc.client5.http.classic.methods.HttpPost;
+import org.apache.hc.client5.http.config.RequestConfig;
+import org.apache.hc.client5.http.entity.mime.HttpMultipartMode;
+import org.apache.hc.client5.http.entity.mime.MultipartEntityBuilder;
+import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
+import org.apache.hc.core5.http.HttpEntity;
+import org.apache.hc.core5.http.HttpHost;
+
+import java.io.File;
+import java.io.IOException;
+
+public class VoiceUploadHttpComponentsRequestExecutor extends VoiceUploadRequestExecutor {
+  public VoiceUploadHttpComponentsRequestExecutor(RequestHttp requestHttp) {
+    super(requestHttp);
+  }
+
+  @Override
+  public Boolean execute(String uri, File data, WxType wxType) throws WxErrorException, IOException {
+    if (data == null) {
+      throw new WxErrorException("文件对象为空");
+    }
+
+    HttpPost httpPost = new HttpPost(uri);
+    if (requestHttp.getRequestHttpProxy() != null) {
+      RequestConfig config = RequestConfig.custom().setProxy(requestHttp.getRequestHttpProxy()).build();
+      httpPost.setConfig(config);
+    }
+
+    HttpEntity entity = MultipartEntityBuilder
+      .create()
+      .addBinaryBody("media", data)
+      .setMode(HttpMultipartMode.EXTENDED)
+      .build();
+    httpPost.setEntity(entity);
+
+    String responseContent = requestHttp.getRequestHttpClient().execute(httpPost, Utf8ResponseHandler.INSTANCE);
+    WxError error = WxError.fromJson(responseContent, WxType.MP);
+    if (error.getErrorCode() != 0) {
+      throw new WxErrorException(error);
+    }
+    return true;
+  }
+}
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/voice/VoiceUploadRequestExecutor.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/voice/VoiceUploadRequestExecutor.java
index fa48c953f6..e8eb7cb9e9 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/voice/VoiceUploadRequestExecutor.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/voice/VoiceUploadRequestExecutor.java
@@ -1,14 +1,14 @@
 package me.chanjar.weixin.mp.util.requestexecuter.voice;
 
-import java.io.File;
-import java.io.IOException;
-
 import me.chanjar.weixin.common.enums.WxType;
 import me.chanjar.weixin.common.error.WxErrorException;
 import me.chanjar.weixin.common.util.http.RequestExecutor;
 import me.chanjar.weixin.common.util.http.RequestHttp;
 import me.chanjar.weixin.common.util.http.ResponseHandler;
 
+import java.io.File;
+import java.io.IOException;
+
 /**
  * 
  *  Created by BinaryWang on 2018/6/9.
@@ -19,7 +19,7 @@
 public abstract class VoiceUploadRequestExecutor implements RequestExecutor {
   protected RequestHttp requestHttp;
 
-  public VoiceUploadRequestExecutor(RequestHttp requestHttp) {
+  public VoiceUploadRequestExecutor(RequestHttp requestHttp) {
     this.requestHttp = requestHttp;
   }
 
@@ -28,14 +28,17 @@ public void execute(String uri, File data, ResponseHandler handler, WxT
     handler.handle(this.execute(uri, data, wxType));
   }
 
-  public static RequestExecutor create(RequestHttp requestHttp) {
+  @SuppressWarnings("unchecked")
+  public static RequestExecutor create(RequestHttp requestHttp) {
     switch (requestHttp.getRequestType()) {
       case APACHE_HTTP:
-        return new VoiceUploadApacheHttpRequestExecutor(requestHttp);
-      case JODD_HTTP:
-      case OK_HTTP:
+        return new VoiceUploadApacheHttpRequestExecutor(
+          (RequestHttp) requestHttp);
+      case HTTP_COMPONENTS:
+        return new VoiceUploadHttpComponentsRequestExecutor(
+          (RequestHttp) requestHttp);
       default:
-        return null;
+        throw new IllegalArgumentException("不支持的http执行器类型:" + requestHttp.getRequestType());
     }
   }
 
diff --git a/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/impl/BaseWxMpServiceImplTest.java b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/impl/BaseWxMpServiceImplTest.java
index 89b2224053..4beced7c7c 100644
--- a/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/impl/BaseWxMpServiceImplTest.java
+++ b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/impl/BaseWxMpServiceImplTest.java
@@ -8,7 +8,7 @@
 import me.chanjar.weixin.common.error.WxError;
 import me.chanjar.weixin.common.error.WxErrorException;
 import me.chanjar.weixin.common.error.WxMpErrorMsgEnum;
-import me.chanjar.weixin.common.util.http.HttpType;
+import me.chanjar.weixin.common.util.http.HttpClientType;
 import me.chanjar.weixin.common.util.http.RequestExecutor;
 import me.chanjar.weixin.mp.api.WxMpService;
 import me.chanjar.weixin.mp.api.test.ApiTestModule;
@@ -237,7 +237,7 @@ public Object getRequestHttpProxy() {
       }
 
       @Override
-      public HttpType getRequestType() {
+      public HttpClientType getRequestType() {
         return null;
       }
     };
diff --git a/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/impl/WxMpDraftServiceImplTest.java b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/impl/WxMpDraftServiceImplTest.java
index 15966d6727..77288d8d3b 100644
--- a/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/impl/WxMpDraftServiceImplTest.java
+++ b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/impl/WxMpDraftServiceImplTest.java
@@ -1,6 +1,7 @@
 package me.chanjar.weixin.mp.api.impl;
 
 import com.google.inject.Inject;
+import me.chanjar.weixin.common.api.WxConsts;
 import me.chanjar.weixin.common.error.WxErrorException;
 import me.chanjar.weixin.mp.api.WxMpService;
 import me.chanjar.weixin.mp.api.test.ApiTestModule;
@@ -23,15 +24,15 @@
 public class WxMpDraftServiceImplTest {
 
   /**
-   * 1.先上传一个永久图片素材:me.chanjar.weixin.mp.api.impl.WxMpMaterialServiceImplTest.testUploadMaterial
+   * 1.先上传一个永久图片素材:{@link me.chanjar.weixin.mp.api.impl.WxMpMaterialServiceImplTest#testUploadMaterial}
    * 2.后续图文需要设置一个永久素材id
    */
-  final String thumbMediaId = "zUUtT8ZYeXzZ4slFbtnAkh7Yd-f45DbFoF9ERzVC6s4";
+  final String thumbMediaId = "-V3dxNv-eyJlImuJjWrmaTPt76BS6jHrL6-cGBlFPaXxAuv0qeJYV2p6Ezirr0zS";
 
   /**
    * 新增草稿后返回的id,后续查询、修改、删除,获取等需要使用
    */
-  final String mediaId = "zUUtT8ZYeXzZ4slFbtnAkpgGKyqnTsjtUvMdVBRWJVk";
+  final String mediaId = "-V3dxNv-eyJlImuJjWrmaZLwMkTKfDEhzq5NURU02H-k1qHMJ0lh9p0UU46w3rbd";
 
   @Inject
   protected WxMpService wxService;
@@ -114,6 +115,7 @@ public void testListDraft() throws WxErrorException {
     ,"total_count":1,"item_count":1}
 
     */
+    System.out.println(draftList);
     assertThat(draftList).isNotNull();
   }
 
@@ -124,5 +126,101 @@ public void testCountDraft() throws WxErrorException {
     assertThat(countDraft).isNotNull();
   }
 
+  //-----以下是图片类型草稿测试
+
+  /**
+   * 先上传一个永久图片素材:{@link me.chanjar.weixin.mp.api.impl.WxMpMaterialServiceImplTest#testUploadMaterial}
+   * 这里的图片,使用的是 mm.jpeg
+   */
+  @Test
+  public void testAddDraftPic() throws WxErrorException {
+    List draftArticleList = new ArrayList<>();
+    ArrayList imageItems = new ArrayList<>();
+    imageItems.add(new WxMpDraftImageInfo.ImageItem(thumbMediaId));
+
+    ArrayList cropPercents = new ArrayList<>();
+    cropPercents.add(new WxMpDraftCoverInfo.CropPercent("1_1", "0.1", "0", "1", "0.9"));
+
+    WxMpDraftArticles draftArticle = WxMpDraftArticles.builder()
+      .articleType(WxConsts.ArticleType.NEWS_PIC)
+      .title("新建图片草稿")
+      .content("图片消息的具体内容")
+      // 打开评论、所有人可评论
+      .needOpenComment(1).onlyFansCanComment(0)
+      .imageInfo(WxMpDraftImageInfo.builder().imageList(imageItems).build())
+      .coverInfo(WxMpDraftCoverInfo.builder().cropPercentList(cropPercents).build())
+      .productInfo(WxMpDraftProductInfo.builder().footerProductInfo(new WxMpDraftProductInfo.FooterProductInfo("")).build())
+      .build();
+    draftArticleList.add(draftArticle);
+
+    WxMpAddDraft addDraft = WxMpAddDraft.builder().articles(draftArticleList).build();
+    String mediaId = this.wxService.getDraftService().addDraft(addDraft);
+    System.out.println(mediaId);
+    assertThat(mediaId).isNotNull();
+  }
+
+  @Test
+  public void testGetDraftPic() throws WxErrorException {
+    final WxMpDraftInfo draftInfo = this.wxService.getDraftService().getDraft(mediaId);
+    assertThat(draftInfo).isNotNull();
+    System.out.println(draftInfo.toJson());
+    // 【响应数据】:{
+    //     "news_item": [
+    //         {
+    //             "article_type": "newspic",
+    //             "title": "新建图片草稿",
+    //             "content": "图片消息的具体内容",
+    //             "thumb_media_id": "-V3dxNv-eyJlImuJjWrmaTPt76BS6jHrL6-cGBlFPaXxAuv0qeJYV2p6Ezirr0zS",
+    //             "need_open_comment": 1,
+    //             "only_fans_can_comment": 0,
+    //             "url": "http://mp.weixin.qq.com/s?__biz=MzkyNTg4NDM1NA==&tempkey=MTMyM18rUktkOHFIQm5Kd3U5Rk1yS2NRYWtyZWUyNDNwS2MxZTZ3VXBKTkVScExpUFdGYzN2X0IzOEl1NGxEMGFpYld6NmdvbE9UUzlyYUdiVklvWTQ2YlRzSkkzQlpWMEZpcG9JRWp5LWZCVVNoWURodUlfWnE4VWZVQnlPd2VaUkg5SGREYUd3TW1wQkhlbTFuenBvRzFIbUxhMEJVbEo0Z3oyd2tnSGJBfn4%3D&chksm=423e8b9e75490288e8388c9ee91d6dad462bbce654742edd316622ab2b2fcfc593a4db58577b#rd",
+    //             "thumb_url": "http://mmbiz.qpic.cn/sz_mmbiz_jpg/s7FE7rYN42QgPuJeXX9MfNuJBiaoalrWv8fj4AEqnK0WBM3KzqS0DsqHIW4epA3cx1PGjpco87BTssgQibvSNBIQ/0?wx_fmt=jpeg",
+    //             "image_info": {
+    //                 "image_list": [
+    //                     {
+    //                         "image_media_id": "-V3dxNv-eyJlImuJjWrmaTPt76BS6jHrL6-cGBlFPaXxAuv0qeJYV2p6Ezirr0zS"
+    //                     }
+    //                 ]
+    //             }
+    //         }
+    //     ]
+    // }
+  }
+
+  @Test
+  public void testUpdateDraftPic() throws WxErrorException {
+    ArrayList imageItems = new ArrayList<>();
+    imageItems.add(new WxMpDraftImageInfo.ImageItem(thumbMediaId));
+    ArrayList cropPercents = new ArrayList<>();
+    cropPercents.add(new WxMpDraftCoverInfo.CropPercent("1_1", "0.3", "0", "1", "0.7"));
+
+    WxMpDraftArticles draftArticle = WxMpDraftArticles.builder()
+      .articleType(WxConsts.ArticleType.NEWS_PIC)
+      .title("修改图片草稿")
+      .content("修改后的图片消息的具体内容")
+      // 打开评论、所有人可评论
+      .needOpenComment(1).onlyFansCanComment(0)
+      .imageInfo(WxMpDraftImageInfo.builder().imageList(imageItems).build())
+      .coverInfo(WxMpDraftCoverInfo.builder().cropPercentList(cropPercents).build())
+      .productInfo(WxMpDraftProductInfo.builder().footerProductInfo(new WxMpDraftProductInfo.FooterProductInfo("")).build())
+      .build();
+
+    WxMpUpdateDraft updateDraft = WxMpUpdateDraft.builder()
+      .mediaId(mediaId)
+      .index(0)
+      .articles(draftArticle)
+      .build();
+    Boolean updateDraftResult = this.wxService.getDraftService().updateDraft(updateDraft);
+    assertThat(updateDraftResult).isTrue();
+  }
+
+  @Test
+  public void testDelDraftPic() throws WxErrorException {
+    Boolean delDraftResult = this.wxService.getDraftService().delDraft(mediaId);
+    System.out.println(delDraftResult);
+    // 【响应数据】:{"errcode":0,"errmsg":"ok"}
+    assertThat(delDraftResult).isTrue();
+  }
+
 }
 
diff --git a/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/impl/WxMpImgProcServiceImplTest.java b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/impl/WxMpImgProcServiceImplTest.java
index 21ca3236f5..f15a231e57 100644
--- a/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/impl/WxMpImgProcServiceImplTest.java
+++ b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/impl/WxMpImgProcServiceImplTest.java
@@ -61,14 +61,14 @@ public void testSuperResolution2() throws Exception {
 
   @Test
   public void testAiCrop() throws WxErrorException {
-    final WxImgProcAiCropResult result = this.mpService.getImgProcService().aiCrop("https://gitee.com/binary/weixin-java-tools/raw/master/images/qrcodes/mp.png");
+    final WxImgProcAiCropResult result = this.mpService.getImgProcService().aiCrop("https://gitee.com/binary/weixin-java-tools/images/banners/wiki.jpg");
     assertThat(result).isNotNull();
     System.out.println(result);
   }
 
   @Test
   public void testAiCrop2() throws WxErrorException {
-    final WxImgProcAiCropResult result = this.mpService.getImgProcService().aiCrop("https://gitee.com/binary/weixin-java-tools/raw/master/images/qrcodes/mp.png", "1,2.35");
+    final WxImgProcAiCropResult result = this.mpService.getImgProcService().aiCrop("https://gitee.com/binary/weixin-java-tools/images/banners/wiki.jpg", "1,2.35");
     assertThat(result).isNotNull();
     System.out.println(result);
   }
diff --git a/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/impl/WxMpMapConfigImplTest.java b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/impl/WxMpMapConfigImplTest.java
new file mode 100644
index 0000000000..167c0e019c
--- /dev/null
+++ b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/impl/WxMpMapConfigImplTest.java
@@ -0,0 +1,58 @@
+package me.chanjar.weixin.mp.api.impl;
+
+import com.google.inject.Inject;
+import me.chanjar.weixin.common.error.WxErrorException;
+import me.chanjar.weixin.mp.api.WxMpService;
+import me.chanjar.weixin.mp.api.test.ApiTestModule;
+import me.chanjar.weixin.mp.config.impl.WxMpMapConfigImpl;
+import me.chanjar.weixin.mp.util.WxMpConfigStorageHolder;
+import org.testng.annotations.Guice;
+import org.testng.annotations.Test;
+import static org.testng.Assert.assertEquals;
+
+/**
+ * 测试 ConcurrentHashMap 保存配置信息
+ * @author jimmyjimmy-sw
+ */
+@Test
+@Guice(modules = ApiTestModule.class)
+public class WxMpMapConfigImplTest {
+
+  @Inject
+  private WxMpService wxService;
+
+  /**
+   * 测试多租户保存 WxMpMapConfigImpl 到 WxMpService,切换之后能获取到租户各自AppId对应的token
+   * @throws WxErrorException
+   */
+  @Test
+  public void testAppidSwitch() throws WxErrorException {
+    // 保存租户A的配置信息,并获取token
+    WxMpMapConfigImpl configAppA = new WxMpMapConfigImpl();
+    String appidA = "APPID_A";
+    configAppA.setAppId(appidA);
+    configAppA.setSecret("APP_SECRET_A");
+    configAppA.useStableAccessToken(true);
+    String tokenA = "TOKEN_A";
+    configAppA.updateAccessToken(tokenA,60 * 60 * 1);
+    wxService.addConfigStorage(appidA, configAppA);
+    WxMpConfigStorageHolder.set(appidA);
+    assertEquals(this.wxService.getAccessToken(),tokenA);
+
+    // 保存租户B的配置信息,并获取token
+    WxMpMapConfigImpl configAppB = new WxMpMapConfigImpl();
+    String appidB = "APPID_B";
+    configAppB.setAppId(appidB);
+    configAppB.setSecret("APP_SECRET_B");
+    configAppB.useStableAccessToken(true);
+    String tokenB = "TOKEN_B";
+    configAppB.updateAccessToken(tokenB,60 * 60 * 1);
+    wxService.addConfigStorage(appidB, configAppB);
+    WxMpConfigStorageHolder.set(appidB);
+    assertEquals(this.wxService.getAccessToken(),tokenB);
+
+    // 上下文切换到租户A 获取租户A的token
+    WxMpConfigStorageHolder.set(appidA);
+    assertEquals(this.wxService.getAccessToken(),tokenA);
+  }
+}
diff --git a/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/impl/WxMpMaterialServiceImplTest.java b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/impl/WxMpMaterialServiceImplTest.java
index 707f1df311..8068a5a302 100644
--- a/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/impl/WxMpMaterialServiceImplTest.java
+++ b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/impl/WxMpMaterialServiceImplTest.java
@@ -135,11 +135,6 @@ public void testAddNews() throws WxErrorException {
 
     wxMpMaterialNewsMultiple.addArticle(article1);
     wxMpMaterialNewsMultiple.addArticle(article2);
-
-    WxMpMaterialUploadResult resSingle = this.wxService.getMaterialService().materialNewsUpload(wxMpMaterialNewsSingle);
-    this.singleNewsMediaId = resSingle.getMediaId();
-    WxMpMaterialUploadResult resMulti = this.wxService.getMaterialService().materialNewsUpload(wxMpMaterialNewsMultiple);
-    this.multiNewsMediaId = resMulti.getMediaId();
   }
 
   @Test(dependsOnMethods = {"testAddNews"})
@@ -201,8 +196,6 @@ public void testUpdateNewsInfo() throws WxErrorException {
     wxMpMaterialArticleUpdateSingle.setMediaId(this.singleNewsMediaId);
     wxMpMaterialArticleUpdateSingle.setArticles(articleSingle);
     wxMpMaterialArticleUpdateSingle.setIndex(0);
-    boolean resultSingle = this.wxService.getMaterialService().materialNewsUpdate(wxMpMaterialArticleUpdateSingle);
-    assertTrue(resultSingle);
     wxMpMaterialNewsSingle = this.wxService.getMaterialService()
       .materialNewsInfo(this.singleNewsMediaId);
     assertNotNull(wxMpMaterialNewsSingle);
@@ -218,8 +211,6 @@ public void testUpdateNewsInfo() throws WxErrorException {
     wxMpMaterialArticleUpdateMulti.setMediaId(this.multiNewsMediaId);
     wxMpMaterialArticleUpdateMulti.setArticles(articleMulti);
     wxMpMaterialArticleUpdateMulti.setIndex(1);
-    boolean resultMulti = this.wxService.getMaterialService().materialNewsUpdate(wxMpMaterialArticleUpdateMulti);
-    assertTrue(resultMulti);
     wxMpMaterialNewsMultiple = this.wxService.getMaterialService()
       .materialNewsInfo(this.multiNewsMediaId);
     assertNotNull(wxMpMaterialNewsMultiple);
diff --git a/weixin-java-open/README.md b/weixin-java-open/README.md
index dd69161849..6ca65dfef3 100644
--- a/weixin-java-open/README.md
+++ b/weixin-java-open/README.md
@@ -1,3 +1,38 @@
+# 微信开放平台模块 (weixin-java-open)
+
+## 模块说明
+
+本模块主要用于**微信第三方平台**的开发,适用于以下场景:
+
+### 适用场景
+1. **第三方平台开发**:作为第三方平台,代替多个公众号或小程序进行管理和开发
+2. **代公众号实现业务**:通过授权代替公众号进行消息管理、素材管理等操作
+3. **代小程序实现业务**:通过授权代替小程序进行代码管理、基本信息设置等操作
+
+### 移动应用开发说明
+
+**如果您要开发移动应用(iOS/Android App)并接入微信功能,请注意:**
+
+- **微信登录**:
+  - 移动应用的微信登录(网页授权)需要在**微信开放平台**(open.weixin.qq.com)创建移动应用
+  - 服务端处理 OAuth 授权时使用本模块 `weixin-java-open`
+  - 移动端需集成微信官方SDK(iOS/Android),本项目仅提供服务端SDK
+
+- **微信支付**:
+  - 使用 `weixin-java-pay` 模块,参考 [微信支付文档](../weixin-java-pay/)
+  - 移动应用支付使用 APP 支付类型(TradeType.APP)
+
+- **微信分享**:
+  - 需集成微信官方移动端SDK,本项目不涉及客户端功能
+
+**参考资料**:
+- [微信开放平台官方文档](https://open.weixin.qq.com/)
+- [移动应用接入指南](https://developers.weixin.qq.com/doc/oplatform/Mobile_App/Access_Guide/iOS.html)
+
+---
+
+## 代码示例
+
 消息机制未实现,下面为通知回调中设置的代码部分
 
 以下代码可通过腾讯全网发布测试用例
diff --git a/weixin-java-open/pom.xml b/weixin-java-open/pom.xml
index f7fac62e64..0e815f1076 100644
--- a/weixin-java-open/pom.xml
+++ b/weixin-java-open/pom.xml
@@ -7,7 +7,7 @@
   
     com.github.binarywang
     wx-java
-    4.7.2.B
+    4.7.8.B
   
 
   weixin-java-open
@@ -48,6 +48,11 @@
       okhttp
       provided
     
+    
+      org.apache.httpcomponents.client5
+      httpclient5
+      provided
+    
 
     
       org.testng
diff --git a/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/WxOpenMaAuthAndIcpService.java b/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/WxOpenMaAuthAndIcpService.java
new file mode 100644
index 0000000000..de2c0a0de2
--- /dev/null
+++ b/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/WxOpenMaAuthAndIcpService.java
@@ -0,0 +1,32 @@
+package me.chanjar.weixin.open.api;
+
+import me.chanjar.weixin.common.error.WxErrorException;
+import me.chanjar.weixin.open.bean.authandicp.WxOpenQueryAuthAndIcpResult;
+import me.chanjar.weixin.open.bean.authandicp.WxOpenSubmitAuthAndIcpParam;
+import me.chanjar.weixin.open.bean.authandicp.WxOpenSubmitAuthAndIcpResult;
+
+/**
+ * 微信第三方平台 小程序认证及备案
+ * @author 痴货
+ * @createTime 2025/06/18 23:00
+ */
+public interface WxOpenMaAuthAndIcpService {
+
+  String QUERY_AUTH_AND_ICP = "https://api.weixin.qq.com/wxa/sec/query_auth_and_icp";
+
+  String SUBMIT_AUTH_AND_ICP = "https://api.weixin.qq.com/wxa/sec/submit_auth_and_icp";
+
+  /**
+   * 查询小程序认证及备案进度。
+   * @param procedureId 小程序认证及备案任务流程id
+   * @return 小程序认证及备案进度
+   */
+  WxOpenQueryAuthAndIcpResult queryAuthAndIcp(String procedureId) throws WxErrorException;
+
+  /**
+   * 提交小程序认证及备案信息。
+   * @param param 提交小程序认证及备案信息参数
+   * @return 提交结果
+   */
+  WxOpenSubmitAuthAndIcpResult submitAuthAndIcp(WxOpenSubmitAuthAndIcpParam param) throws WxErrorException;
+}
diff --git a/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/WxOpenMaBasicService.java b/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/WxOpenMaBasicService.java
index cae5faa783..3952f7dbf9 100644
--- a/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/WxOpenMaBasicService.java
+++ b/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/WxOpenMaBasicService.java
@@ -2,9 +2,9 @@
 
 import me.chanjar.weixin.common.error.WxErrorException;
 import me.chanjar.weixin.open.bean.ma.WxFastMaCategory;
-import me.chanjar.weixin.open.bean.ma.WxOpenMaApplyOrderPathInfo;
 import me.chanjar.weixin.open.bean.result.*;
 
+import java.io.UnsupportedEncodingException;
 import java.util.List;
 
 /**
@@ -54,19 +54,23 @@ public interface WxOpenMaBasicService {
    */
   String OPEN_GET_ALL_CATEGORIES = "https://api.weixin.qq.com/cgi-bin/wxopen/getallcategories";
   /**
-   * 8.2 添加类目
+   * 8.2 获取不同类型主体可设置的类目
+   */
+  String OPEN_GET_ALL_CATEGORIES_BY_TYPE = "https://api.weixin.qq.com/cgi-bin/wxopen/getcategoriesbytype";
+  /**
+   * 8.3 添加类目
    */
   String OPEN_ADD_CATEGORY = "https://api.weixin.qq.com/cgi-bin/wxopen/addcategory";
   /**
-   * 8.3 删除类目
+   * 8.4 删除类目
    */
   String OPEN_DELETE_CATEGORY = "https://api.weixin.qq.com/cgi-bin/wxopen/deletecategory";
   /**
-   * 8.4 获取账号已经设置的所有类目
+   * 8.5 获取账号已经设置的所有类目
    */
   String OPEN_GET_CATEGORY = "https://api.weixin.qq.com/cgi-bin/wxopen/getcategory";
   /**
-   * 8.5 修改类目
+   * 8.6 修改类目
    */
   String OPEN_MODIFY_CATEGORY = "https://api.weixin.qq.com/cgi-bin/wxopen/modifycategory";
 
@@ -75,6 +79,8 @@ public interface WxOpenMaBasicService {
    */
   String OPEN_GET_ORDER_PATH_INFO = "https://api.weixin.qq.com/wxa/security/getorderpathinfo";
 
+  String URL_COMPONENT_REBIND_ADMIN = "https://mp.weixin.qq.com/wxopen/componentrebindadmin?appid=%s&component_appid=%s&redirect_uri=%s";
+
 
   /**
    * 1.获取小程序的信息
@@ -145,6 +151,14 @@ WxFastMaSetNickameResult setNickname(String nickname, String idCard, String lice
    */
   WxOpenResult modifySignature(String signature) throws WxErrorException;
 
+  /**
+   * 7.1 获取换绑管理员URL
+   * @param redirectUri 跳转URL
+   * @param appId 公众号的 appid
+   * @return 换绑管理员URL
+   */
+  String getComponentRebindAdminUrl(String redirectUri, String appId) throws UnsupportedEncodingException;
+
   /**
    * 7.3 管理员换绑
    *
@@ -168,7 +182,12 @@ WxFastMaSetNickameResult setNickname(String nickname, String idCard, String lice
   String getAllCategories() throws WxErrorException;
 
   /**
-   * 8.2添加类目
+   * 8.2获取不同类型主体可设置的类目
+   */
+  WxOpenGetAllCategoriesByTypeResult getAllCategoriesByType(String verifyType) throws WxErrorException;
+
+  /**
+   * 8.3添加类目
    *
    * @param categoryList 类目列表
    * @return .
@@ -177,7 +196,7 @@ WxFastMaSetNickameResult setNickname(String nickname, String idCard, String lice
   WxOpenResult addCategory(List categoryList) throws WxErrorException;
 
   /**
-   * 8.3删除类目
+   * 8.4删除类目
    *
    * @param first  一级类目ID
    * @param second 二级类目ID
@@ -187,7 +206,7 @@ WxFastMaSetNickameResult setNickname(String nickname, String idCard, String lice
   WxOpenResult deleteCategory(int first, int second) throws WxErrorException;
 
   /**
-   * 8.4获取账号已经设置的所有类目
+   * 8.5获取账号已经设置的所有类目
    *
    * @return .
    * @throws WxErrorException .
@@ -195,7 +214,7 @@ WxFastMaSetNickameResult setNickname(String nickname, String idCard, String lice
   WxFastMaBeenSetCategoryResult getCategory() throws WxErrorException;
 
   /**
-   * 8.5修改类目
+   * 8.6修改类目
    *
    * @param category 实体
    * @return .
diff --git a/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/WxOpenMaIcpService.java b/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/WxOpenMaIcpService.java
index 9b936b1572..8176ea0231 100644
--- a/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/WxOpenMaIcpService.java
+++ b/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/WxOpenMaIcpService.java
@@ -119,14 +119,6 @@ public interface WxOpenMaIcpService {
    */
   WxOpenIcpVerifyTaskResult queryIcpVerifyTask(String taskId) throws WxErrorException;
 
-  /**
-   * 发起小程序管理员人脸核身
-   *
-   * @return 人脸核验任务结果
-   * @throws WxErrorException e
-   */
-  WxOpenIcpCreateIcpVerifyTaskResult createIcpVerifyTask() throws WxErrorException;
-
   /**
    * 发起小程序管理员人脸核身
    *
@@ -227,21 +219,4 @@ public interface WxOpenMaIcpService {
    */
   File getIcpMedia(String mediaId) throws WxErrorException;
 
-  /**
-   * 申请小程序认证及备案
-   *
-   * @param param 参数
-   * @return r
-   * @throws WxErrorException e
-   */
-  WxOpenSubmitAuthAndIcpResult submitAuthAndIcp(WxOpenSubmitAuthAndIcpParam param) throws WxErrorException;
-
-  /**
-   * 查询小程序认证及备案进度
-   * @param procedureId 小程序认证及备案任务流程id
-   * @return r
-   * @throws WxErrorException e
-   */
-  WxOpenQueryAuthAndIcpResult queryAuthAndIcp(String procedureId) throws WxErrorException;
-
 }
diff --git a/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/WxOpenMaService.java b/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/WxOpenMaService.java
index 7a3bbca44a..fef85d7f2e 100644
--- a/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/WxOpenMaService.java
+++ b/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/WxOpenMaService.java
@@ -731,6 +731,13 @@ WxOpenMaDomainResult modifyDomain(String action, List requestDomains, Li
    */
   WxOpenMaIcpService getIcpService();
 
+  /**
+   * 小程序认证及备案服务
+   *
+   * @return 小程序认证及备案服务
+   */
+  WxOpenMaAuthAndIcpService getAuthAndIcpService();
+
   /**
    * 小程序用户隐私保护指引服务
    *
diff --git a/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/WxOpenService.java b/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/WxOpenService.java
index 2305be311b..f9806d2c9e 100644
--- a/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/WxOpenService.java
+++ b/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/WxOpenService.java
@@ -1,6 +1,5 @@
 package me.chanjar.weixin.open.api;
 
-import me.chanjar.weixin.common.bean.result.WxMediaUploadResult;
 import me.chanjar.weixin.common.bean.result.WxMinishopImageUploadResult;
 import me.chanjar.weixin.common.error.WxErrorException;
 
diff --git a/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenComponentServiceImpl.java b/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenComponentServiceImpl.java
index 1c0e7f16f6..80da912bef 100644
--- a/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenComponentServiceImpl.java
+++ b/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenComponentServiceImpl.java
@@ -150,7 +150,7 @@ public boolean checkSignature(String timestamp, String nonce, String signature)
       return SHA1.gen(getWxOpenConfigStorage().getComponentToken(), timestamp, nonce)
         .equals(signature);
     } catch (Exception e) {
-      log.error("Checking signature failed, and the reason is :" + e.getMessage());
+      log.error("Checking signature failed, and the reason is :{}", e.getMessage());
       return false;
     }
   }
@@ -705,7 +705,7 @@ public String checkAuditStatus(String wxName) throws WxErrorException {
     jsonObject.addProperty("wx_name", wxName);
     String url = CHECK_SHOP_AUDITSTATUS_URL + "?access_token=" + getComponentAccessToken(false);
     String response = post(url, jsonObject.toString());
-    log.info("CHECK_SHOP_AUDITSTATUS_URL: " + response);
+    log.info("CHECK_SHOP_AUDITSTATUS_URL: {}", response);
     return response;
   }
 
@@ -715,7 +715,7 @@ public String checkAuditStatus(String appId, String wxName) throws WxErrorExcept
     jsonObject.addProperty("wx_name", wxName);
     String url = CHECK_SHOP_AUDITSTATUS_URL + "?access_token=" + getAuthorizerAccessToken(appId, false);
     String response = post(url, jsonObject.toString());
-    log.info("CHECK_SHOP_AUDITSTATUS_URL: " + response);
+    log.info("CHECK_SHOP_AUDITSTATUS_URL: {}", response);
     return response;
   }
 
@@ -757,7 +757,7 @@ public WxOpenResult submitBasicInfo(String appId, MinishopNameInfo nameInfo, Min
   @Override
   public WxMinishopImageUploadResult uploadMinishopImagePicFile(String appId, Integer height, Integer width, File file) throws WxErrorException {
     String url = WxOpenMinishopService.UPLOAD_IMG_MINISHOP_FILE_URL + "?access_token=" + getAuthorizerAccessToken(appId, false) + "&height=" + height + "&width=" + width;
-    log.info("upload url: " + url);
+    log.info("upload url: {}", url);
 //    String response = (url, file);
     WxMinishopImageUploadResult result = getWxOpenService().uploadMinishopMediaFile(url, file);
 
@@ -770,13 +770,13 @@ public MinishopCategories getMinishopCategories(String appId, Integer fCatId) th
     jsonObject.addProperty("f_cat_id", fCatId);
     String url = MINISHOP_CATEGORY_GET_URL + "?access_token=" + getAuthorizerAccessToken(appId, false);
     String response = getWxOpenService().post(url, jsonObject.toString());
-    log.info("response: " + response);
+    log.info("response: {}", response);
     JsonObject respJson = GsonParser.parse(response);
     MinishopCategories categories = new MinishopCategories();
     categories.setErrcode(respJson.get(WxConsts.ERR_CODE).getAsInt());
     if (categories.getErrcode() == 0) {
       JsonArray catListJson = respJson.getAsJsonArray("cat_list");
-      if (catListJson != null || catListJson.size() > 0) {
+      if (catListJson != null || !catListJson.isEmpty()) {
         List categoryList = new ArrayList<>();
         for (int i = 0; i < catListJson.size(); i++) {
           JsonObject catJson = catListJson.get(i).getAsJsonObject();
@@ -806,7 +806,7 @@ public MinishopBrandList getMinishopBrands(String appId) throws WxErrorException
     brandList.setErrcode(respJson.get(WxConsts.ERR_CODE).getAsInt());
     if (brandList.getErrcode() == 0) {
       JsonArray brandArrayJson = respJson.get("brands").getAsJsonArray();
-      if (brandArrayJson.size() > 0) {
+      if (!brandArrayJson.isEmpty()) {
         List brands = new ArrayList<>();
         for (int i = 0; i < brandArrayJson.size(); i++) {
           JsonObject brandJson = brandArrayJson.get(i).getAsJsonObject();
@@ -843,7 +843,7 @@ public MinishopDeliveryTemplateResult getMinishopDeliveryTemplate(String appId)
     templateResult.setErrCode(respJson.get(WxConsts.ERR_CODE).getAsInt());
     if (templateResult.getErrCode() == 0) {
       JsonArray templateArrayJson = respJson.get("template_list").getAsJsonArray();
-      if (templateArrayJson.size() > 0) {
+      if (!templateArrayJson.isEmpty()) {
         List templateList = new ArrayList<>();
         for (int i = 0; i < templateArrayJson.size(); i++) {
           JsonObject templateJson = templateArrayJson.get(i).getAsJsonObject();
@@ -876,7 +876,7 @@ public MinishopShopCatList getMinishopCatList(String appId) throws WxErrorExcept
     shopCatList.setErrcode(respJson.get(WxConsts.ERR_CODE).getAsInt());
     if (shopCatList.getErrcode() == 0) {
       JsonArray shopcatArrayJson = respJson.get("shopcat_list").getAsJsonArray();
-      if (shopcatArrayJson.size() > 0) {
+      if (!shopcatArrayJson.isEmpty()) {
         List shopCats = new ArrayList<>();
         for (int i = 0; i < shopcatArrayJson.size(); i++) {
           JsonObject shopCatJson = shopcatArrayJson.get(i).getAsJsonObject();
@@ -906,7 +906,7 @@ public WxMinishopAddGoodsSpuResult> getMinishopD
     String response = getWxOpenService().post(url, jsonObject.toString());
 
     JsonObject respObj = GsonParser.parse(response);
-    WxMinishopAddGoodsSpuResult result = new WxMinishopAddGoodsSpuResult();
+    WxMinishopAddGoodsSpuResult> result = new WxMinishopAddGoodsSpuResult<>();
     result.setErrcode(respObj.get(WxConsts.ERR_CODE).getAsInt());
     if (result.getErrcode() == 0) {
       JsonArray companyArray = respObj.get("company_list").getAsJsonArray();
@@ -931,7 +931,7 @@ public Integer minishopCreateCoupon(String appId, WxMinishopCoupon couponInfo) t
     JsonObject jsonObject = couponInfo.toJsonObject();
     String response = getWxOpenService().post(url, jsonObject.toString());
     JsonObject respJson = GsonParser.parse(response);
-    Integer couponId = -1;
+    int couponId = -1;
     if (respJson.get(WxConsts.ERR_CODE).getAsInt() == 0) {
       JsonObject dataJson = respJson.get("data").getAsJsonObject();
       couponId = dataJson.get("coupon_id").getAsInt();
@@ -965,7 +965,7 @@ public Integer minishopUpdateCoupon(String appId, WxMinishopCoupon couponInfo) t
     JsonObject jsonObject = couponInfo.toJsonObject();
     String response = getWxOpenService().post(url, jsonObject.toString());
     JsonObject respJson = GsonParser.parse(response);
-    Integer couponId = -1;
+    int couponId = -1;
     if (respJson.get(WxConsts.ERR_CODE).getAsInt() == 0) {
       JsonObject dataJson = respJson.get("data").getAsJsonObject();
       couponId = dataJson.get("coupon_id").getAsInt();
@@ -994,7 +994,7 @@ public WxMinishopAddGoodsSpuResult minishopGoodsAddSp
     String response = getWxOpenService().post(url, jsonObject.toString());
 
     JsonObject respObj = GsonParser.parse(response);
-    WxMinishopAddGoodsSpuResult result = new WxMinishopAddGoodsSpuResult();
+    WxMinishopAddGoodsSpuResult result = new WxMinishopAddGoodsSpuResult<>();
     result.setErrcode(respObj.get(WxConsts.ERR_CODE).getAsInt());
 
     if (result.getErrcode() == 0) {
@@ -1032,7 +1032,7 @@ public WxMinishopAddGoodsSpuResult minishopGoodsUpdat
     String response = getWxOpenService().post(url, jsonObject.toString());
 
     JsonObject respObj = GsonParser.parse(response);
-    WxMinishopAddGoodsSpuResult result = new WxMinishopAddGoodsSpuResult();
+    WxMinishopAddGoodsSpuResult result = new WxMinishopAddGoodsSpuResult<>();
     result.setErrcode(respObj.get(WxConsts.ERR_CODE).getAsInt());
     if (result.getErrcode() == 0) {
       JsonObject dataObj = respObj.get("data").getAsJsonObject();
@@ -1082,7 +1082,7 @@ public WxMinishopAddGoodsSpuResult minishiopGoodsAddS
     String response = getWxOpenService().post(url, jsonObject.toString());
 
     JsonObject respObj = GsonParser.parse(response);
-    WxMinishopAddGoodsSpuResult result = new WxMinishopAddGoodsSpuResult();
+    WxMinishopAddGoodsSpuResult result = new WxMinishopAddGoodsSpuResult<>();
     result.setErrcode(respObj.get(WxConsts.ERR_CODE).getAsInt());
     if (result.getErrcode() == 0) {
       JsonObject dataObj = respObj.get("data").getAsJsonObject();
@@ -1187,7 +1187,7 @@ public Integer addLimitDiscountGoods(String appId, LimitDiscountGoods limitDisco
     JsonObject jsonObject = limitDiscountGoods.toJsonObject();
     String response = getWxOpenService().post(url, jsonObject.toString());
     JsonObject respObj = GsonParser.parse(response);
-    Integer taskId = 0;
+    int taskId = 0;
     if (respObj.get(WxConsts.ERR_CODE).getAsInt() == 0) {
       taskId = respObj.get("task_id").getAsInt();
     }
@@ -1208,7 +1208,7 @@ public List getLimitDiscountList(String appId, Integer statu
       //成功获取到秒杀活动列表
 
       JsonArray jsonArray = respObj.get("limited_discount_list").getAsJsonArray();
-      if (jsonArray != null && jsonArray.size() > 0) {
+      if (jsonArray != null && !jsonArray.isEmpty()) {
         for (int i = 0; i < jsonArray.size(); i++) {
           JsonObject goodsObj = jsonArray.get(i).getAsJsonObject();
           LimitDiscountGoods discountGoods = new LimitDiscountGoods();
@@ -1219,12 +1219,12 @@ public List getLimitDiscountList(String appId, Integer statu
 
           List skuList = new ArrayList<>();
           JsonArray skuArray = goodsObj.get("limited_discount_sku_list").getAsJsonArray();
-          if (skuArray != null && skuArray.size() > 0) {
+          if (skuArray != null && !skuArray.isEmpty()) {
             for (int j = 0; j < skuArray.size(); j++) {
               JsonObject skuObj = skuArray.get(i).getAsJsonObject();
               LimitDiscountSku sku = new LimitDiscountSku();
               sku.setSkuId(skuObj.get("sku_id").getAsLong());
-              sku.setSalePrice(new BigDecimal(skuObj.get("sale_price").getAsDouble() / 100));
+              sku.setSalePrice(BigDecimal.valueOf(skuObj.get("sale_price").getAsDouble() / 100));
               sku.setSaleStock(skuObj.get("sale_stock").getAsInt());
               skuList.add(sku);
             }
diff --git a/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenFastMaServiceImpl.java b/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenFastMaServiceImpl.java
index 911488ffae..84ff7b495c 100644
--- a/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenFastMaServiceImpl.java
+++ b/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenFastMaServiceImpl.java
@@ -8,10 +8,10 @@
 import me.chanjar.weixin.open.api.WxOpenComponentService;
 import me.chanjar.weixin.open.api.WxOpenFastMaService;
 import me.chanjar.weixin.open.bean.ma.WxFastMaCategory;
-import me.chanjar.weixin.open.bean.ma.WxOpenMaApplyOrderPathInfo;
 import me.chanjar.weixin.open.bean.result.*;
 import me.chanjar.weixin.open.util.json.WxOpenGsonBuilder;
 
+import java.io.UnsupportedEncodingException;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -100,6 +100,11 @@ public WxOpenResult modifySignature(String signature) throws WxErrorException {
     return WxOpenGsonBuilder.create().fromJson(response, WxOpenResult.class);
   }
 
+  @Override
+  public String getComponentRebindAdminUrl(String redirectUri, String appId) {
+    return "";
+  }
+
   @Override
   public WxOpenResult componentRebindAdmin(String taskid) throws WxErrorException {
     JsonObject params = new JsonObject();
@@ -113,6 +118,11 @@ public String getAllCategories() throws WxErrorException {
     return get(OPEN_GET_ALL_CATEGORIES, "");
   }
 
+  @Override
+  public WxOpenGetAllCategoriesByTypeResult getAllCategoriesByType(String verifyType)  {
+    return null;
+  }
+
   @Override
   public WxOpenResult addCategory(List categoryList) throws WxErrorException {
     Map map = new HashMap<>();
diff --git a/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenInMemoryConfigStorage.java b/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenInMemoryConfigStorage.java
index 4b195badc3..3fe68973d2 100644
--- a/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenInMemoryConfigStorage.java
+++ b/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenInMemoryConfigStorage.java
@@ -65,6 +65,16 @@ public class WxOpenInMemoryConfigStorage implements WxOpenConfigStorage {
    */
   private int maxRetryTimes = 5;
 
+  /**
+   * 自定义API主机地址,用于替换默认的 https://api.weixin.qq.com
+   */
+  private String apiHostUrl;
+
+  /**
+   * 自定义获取AccessToken地址,用于向自定义统一服务获取AccessToken
+   */
+  private String accessTokenUrl;
+
   private ApacheHttpClientBuilder apacheHttpClientBuilder;
 
   private Map authorizerRefreshTokens = new ConcurrentHashMap<>();
@@ -105,17 +115,7 @@ public Lock getComponentAccessTokenLock() {
 
   @Override
   public Lock getLockByKey(String key) {
-    Lock lock = locks.get(key);
-    if (lock == null) {
-      synchronized (this) {
-        lock = locks.get(key);
-        if (lock == null) {
-          lock = new ReentrantLock();
-          locks.put(key, lock);
-        }
-      }
-    }
-    return lock;
+    return locks.computeIfAbsent(key, e -> new ReentrantLock());
   }
 
   @Override
@@ -300,6 +300,13 @@ private WxOpenInnerConfigStorage(WxOpenConfigStorage wxOpenConfigStorage, String
       this.accessTokenLock = wxOpenConfigStorage.getLockByKey(appId + ":accessTokenLock");
       this.jsapiTicketLock = wxOpenConfigStorage.getLockByKey(appId + ":jsapiTicketLock");
       this.cardApiTicketLock = wxOpenConfigStorage.getLockByKey(appId + ":cardApiTicketLock");
+      
+      // 自动获取外层配置的URL设置
+      if (wxOpenConfigStorage instanceof WxOpenInMemoryConfigStorage) {
+        WxOpenInMemoryConfigStorage parentConfig = (WxOpenInMemoryConfigStorage) wxOpenConfigStorage;
+        this.apiHostUrl = parentConfig.getApiHostUrl();
+        this.accessTokenUrl = parentConfig.getAccessTokenUrl();
+      }
     }
 
     @Override
@@ -647,5 +654,25 @@ public WxMpHostConfig getHostConfig() {
     public void setHostConfig(WxMpHostConfig hostConfig) {
       this.hostConfig = hostConfig;
     }
+
+    @Override
+    public String getApiHostUrl() {
+      return this.apiHostUrl;
+    }
+
+    @Override
+    public void setApiHostUrl(String apiHostUrl) {
+      this.apiHostUrl = apiHostUrl;
+    }
+
+    @Override
+    public String getAccessTokenUrl() {
+      return this.accessTokenUrl;
+    }
+
+    @Override
+    public void setAccessTokenUrl(String accessTokenUrl) {
+      this.accessTokenUrl = accessTokenUrl;
+    }
   }
 }
diff --git a/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenInRedisTemplateConfigStorage.java b/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenInRedisTemplateConfigStorage.java
index 267a65c367..42034a1ffc 100644
--- a/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenInRedisTemplateConfigStorage.java
+++ b/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenInRedisTemplateConfigStorage.java
@@ -3,11 +3,9 @@
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.locks.Lock;
 
-import org.springframework.data.redis.core.RedisTemplate;
 import org.springframework.data.redis.core.StringRedisTemplate;
 
 import lombok.NonNull;
-import me.chanjar.weixin.common.redis.JedisWxRedisOps;
 import me.chanjar.weixin.common.redis.RedisTemplateWxRedisOps;
 import me.chanjar.weixin.common.redis.WxRedisOps;
 
diff --git a/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenMaAuthAndIcpServiceImpl.java b/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenMaAuthAndIcpServiceImpl.java
new file mode 100644
index 0000000000..50e07d94d1
--- /dev/null
+++ b/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenMaAuthAndIcpServiceImpl.java
@@ -0,0 +1,40 @@
+package me.chanjar.weixin.open.api.impl;
+
+import cn.binarywang.wx.miniapp.api.WxMaService;
+import cn.binarywang.wx.miniapp.json.WxMaGsonBuilder;
+import com.google.gson.JsonObject;
+import me.chanjar.weixin.common.error.WxErrorException;
+import me.chanjar.weixin.open.api.WxOpenMaAuthAndIcpService;
+import me.chanjar.weixin.open.bean.authandicp.WxOpenQueryAuthAndIcpResult;
+import me.chanjar.weixin.open.bean.authandicp.WxOpenSubmitAuthAndIcpParam;
+import me.chanjar.weixin.open.bean.authandicp.WxOpenSubmitAuthAndIcpResult;
+import me.chanjar.weixin.open.util.json.WxOpenGsonBuilder;
+
+/**
+ * 微信第三方平台 小程序认证及备案
+ *
+ * @author 痴货
+ * @createTime 2025/06/18 23:00
+ */
+public class WxOpenMaAuthAndIcpServiceImpl implements WxOpenMaAuthAndIcpService {
+
+  private final WxMaService wxMaService;
+
+  public WxOpenMaAuthAndIcpServiceImpl(WxMaService wxMaService) {
+    this.wxMaService = wxMaService;
+  }
+
+  @Override
+  public WxOpenQueryAuthAndIcpResult queryAuthAndIcp(String procedureId) throws WxErrorException {
+    JsonObject params = new JsonObject();
+    params.addProperty("procedure_id", procedureId);
+    String response = wxMaService.post(QUERY_AUTH_AND_ICP, params);
+    return WxOpenGsonBuilder.create().fromJson(response, WxOpenQueryAuthAndIcpResult.class);
+  }
+
+  @Override
+  public WxOpenSubmitAuthAndIcpResult submitAuthAndIcp(WxOpenSubmitAuthAndIcpParam param) throws WxErrorException {
+    String response = wxMaService.post(SUBMIT_AUTH_AND_ICP, param);
+    return WxMaGsonBuilder.create().fromJson(response, WxOpenSubmitAuthAndIcpResult.class);
+  }
+}
diff --git a/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenMaBasicServiceImpl.java b/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenMaBasicServiceImpl.java
index 943d610113..56d209a322 100644
--- a/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenMaBasicServiceImpl.java
+++ b/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenMaBasicServiceImpl.java
@@ -3,13 +3,16 @@
 import cn.binarywang.wx.miniapp.api.WxMaService;
 import com.google.gson.JsonArray;
 import com.google.gson.JsonObject;
+import lombok.SneakyThrows;
 import me.chanjar.weixin.common.error.WxErrorException;
+import me.chanjar.weixin.open.api.WxOpenComponentService;
 import me.chanjar.weixin.open.api.WxOpenMaBasicService;
 import me.chanjar.weixin.open.bean.ma.WxFastMaCategory;
-import me.chanjar.weixin.open.bean.ma.WxOpenMaApplyOrderPathInfo;
 import me.chanjar.weixin.open.bean.result.*;
 import me.chanjar.weixin.open.util.json.WxOpenGsonBuilder;
 
+import java.io.UnsupportedEncodingException;
+import java.net.URLEncoder;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -22,9 +25,11 @@
 public class WxOpenMaBasicServiceImpl implements WxOpenMaBasicService {
 
   private final WxMaService wxMaService;
+  private final WxOpenComponentService wxOpenComponentService;
 
-  public WxOpenMaBasicServiceImpl(WxMaService wxMaService) {
+  public WxOpenMaBasicServiceImpl(WxMaService wxMaService, WxOpenComponentService wxOpenComponentService) {
     this.wxMaService = wxMaService;
+    this.wxOpenComponentService = wxOpenComponentService;
   }
 
 
@@ -82,6 +87,15 @@ public WxOpenResult modifySignature(String signature) throws WxErrorException {
     return WxOpenGsonBuilder.create().fromJson(response, WxOpenResult.class);
   }
 
+  @SneakyThrows
+  @Override
+  public String getComponentRebindAdminUrl(String redirectUri, String appId) {
+    String componentAppId = wxOpenComponentService.getWxOpenConfigStorage().getComponentAppId();
+    String encoded = URLEncoder.encode(redirectUri, "UTF-8");
+    return String.format(URL_COMPONENT_REBIND_ADMIN, appId, componentAppId, encoded);
+  }
+
+
   @Override
   public WxOpenResult componentRebindAdmin(String taskid) throws WxErrorException {
     JsonObject params = new JsonObject();
@@ -95,6 +109,14 @@ public String getAllCategories() throws WxErrorException {
     return wxMaService.get(OPEN_GET_ALL_CATEGORIES, "");
   }
 
+  @Override
+  public WxOpenGetAllCategoriesByTypeResult getAllCategoriesByType(String verifyType) throws WxErrorException {
+    JsonObject params = new JsonObject();
+    params.addProperty("verify_type", verifyType);
+    String response = wxMaService.post(OPEN_GET_ALL_CATEGORIES_BY_TYPE, params);
+    return WxOpenGsonBuilder.create().fromJson(response, WxOpenGetAllCategoriesByTypeResult.class);
+  }
+
   @Override
   public WxOpenResult addCategory(List categoryList) throws WxErrorException {
     Map map = new HashMap<>();
diff --git a/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenMaIcpServiceImpl.java b/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenMaIcpServiceImpl.java
index db9654f287..8a202ed27b 100644
--- a/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenMaIcpServiceImpl.java
+++ b/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenMaIcpServiceImpl.java
@@ -46,17 +46,6 @@ public WxOpenIcpVerifyTaskResult queryIcpVerifyTask(String taskId) throws WxErro
     return WxOpenGsonBuilder.create().fromJson(response, WxOpenIcpVerifyTaskResult.class);
   }
 
-  /**
-   * 发起小程序管理员人脸核身
-   *
-   * @return 人脸核验任务结果
-   * @throws WxErrorException e
-   */
-  @Override
-  public WxOpenIcpCreateIcpVerifyTaskResult createIcpVerifyTask() throws WxErrorException {
-    return createIcpVerifyTask(false);
-  }
-
   /**
    * 发起小程序管理员人脸核身
    *
@@ -227,31 +216,4 @@ public File getIcpMedia(String mediaId) throws WxErrorException {
     }
   }
 
-  /**
-   * 申请小程序认证及备案
-   *
-   * @param param 参数
-   * @return r
-   * @throws WxErrorException e
-   */
-  @Override
-  public WxOpenSubmitAuthAndIcpResult submitAuthAndIcp(WxOpenSubmitAuthAndIcpParam param) throws WxErrorException {
-    String response = wxMaService.post(SUBMIT_AUTH_AND_ICP, param);
-    return WxMaGsonBuilder.create().fromJson(response, WxOpenSubmitAuthAndIcpResult.class);
-  }
-
-  /**
-   * 查询小程序认证及备案进度
-   *
-   * @param procedureId 小程序认证及备案任务流程id
-   * @return r
-   * @throws WxErrorException e
-   */
-  @Override
-  public WxOpenQueryAuthAndIcpResult queryAuthAndIcp(String procedureId) throws WxErrorException {
-    JsonObject params = new JsonObject();
-    params.addProperty("procedure_id", procedureId);
-    String response = wxMaService.post(QUERY_AUTH_AND_ICP, params);
-    return WxOpenGsonBuilder.create().fromJson(response, WxOpenQueryAuthAndIcpResult.class);
-  }
 }
diff --git a/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenMaServiceImpl.java b/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenMaServiceImpl.java
index 08bfc92bf7..85ce41fd0c 100644
--- a/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenMaServiceImpl.java
+++ b/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenMaServiceImpl.java
@@ -56,17 +56,20 @@ public class WxOpenMaServiceImpl extends WxMaServiceImpl implements WxOpenMaServ
   private final WxOpenMaShoppingOrdersService shoppingOrdersService;
   @Getter
   private final WxOpenMaEmbeddedService embeddedService;
+  @Getter
+  private final WxOpenMaAuthAndIcpService authAndIcpService;
 
   public WxOpenMaServiceImpl(WxOpenComponentService wxOpenComponentService, String appId, WxMaConfig wxMaConfig) {
     this.wxOpenComponentService = wxOpenComponentService;
     this.appId = appId;
     this.wxMaConfig = wxMaConfig;
-    this.basicService = new WxOpenMaBasicServiceImpl(this);
+    this.basicService = new WxOpenMaBasicServiceImpl(this, wxOpenComponentService);
     this.authService = new WxOpenMaAuthServiceImpl(this);
     this.icpService = new WxOpenMaIcpServiceImpl(this);
     this.privacyService = new WxOpenMaPrivacyServiceImpl(this);
     this.shoppingOrdersService = new WxOpenMaShoppingOrdersServiceImpl(this);
     this.embeddedService = new WxOpenMaEmbeddedServiceImpl(this);
+    this.authAndIcpService = new WxOpenMaAuthAndIcpServiceImpl(this);
     initHttp();
   }
 
diff --git a/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenMinishopServiceImpl.java b/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenMinishopServiceImpl.java
index c6934d58d4..98e5d5fa82 100644
--- a/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenMinishopServiceImpl.java
+++ b/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenMinishopServiceImpl.java
@@ -2,13 +2,11 @@
 
 import cn.binarywang.wx.miniapp.api.impl.WxMaServiceImpl;
 import cn.binarywang.wx.miniapp.config.WxMaConfig;
-import com.google.gson.Gson;
 import com.google.gson.JsonObject;
 import lombok.extern.slf4j.Slf4j;
 import me.chanjar.weixin.common.error.WxErrorException;
 import me.chanjar.weixin.open.api.WxOpenComponentService;
 import me.chanjar.weixin.open.api.WxOpenMinishopService;
-import me.chanjar.weixin.open.api.WxOpenService;
 import me.chanjar.weixin.open.bean.minishop.*;
 import me.chanjar.weixin.open.bean.result.WxOpenResult;
 
@@ -24,7 +22,7 @@ public WxOpenMinishopServiceImpl(WxOpenComponentService wxOpenComponentService,
     this.wxOpenComponentService = wxOpenComponentService;
     this.appId = appId;
     this.wxMaConfig = wxMaConfig;
-    log.info("appId: " + appId);
+    log.info("appId: {}", appId);
     if (wxMaConfig == null) {
       log.error("WxMaConfig is null");
     }
@@ -58,7 +56,7 @@ public MinishopAuditStatus checkAuditStatus(String wxName) throws WxErrorExcepti
   @Override
   public String uploadImagePicFile(Integer height, Integer width, File file) throws WxErrorException {
     String url = UPLOAD_IMG_MINISHOP_FILE_URL + "?access_token="+getAccessToken(true)+"&height="+height+"&width="+width;
-    log.info("upload url: " + url);
+    log.info("upload url: {}", url);
     String response = post(url, file);
     return response;
   }
diff --git a/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenMpServiceImpl.java b/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenMpServiceImpl.java
index 576db4a22c..98411279f1 100644
--- a/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenMpServiceImpl.java
+++ b/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenMpServiceImpl.java
@@ -5,9 +5,7 @@
 import com.google.gson.JsonObject;
 import lombok.SneakyThrows;
 import me.chanjar.weixin.common.error.WxErrorException;
-import me.chanjar.weixin.mp.api.WxMpService;
 import me.chanjar.weixin.mp.api.impl.WxMpServiceImpl;
-import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage;
 import me.chanjar.weixin.mp.config.WxMpConfigStorage;
 import me.chanjar.weixin.open.api.WxOpenComponentService;
 import me.chanjar.weixin.open.api.WxOpenMpService;
@@ -16,9 +14,7 @@
 import me.chanjar.weixin.open.bean.result.WxOpenResult;
 
 import java.net.URLEncoder;
-import java.util.Map;
 import java.util.Objects;
-import java.util.function.Function;
 
 /**
  * @author 007
diff --git a/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenServiceAbstractImpl.java b/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenServiceAbstractImpl.java
index 845441c2d6..bad2241aa5 100644
--- a/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenServiceAbstractImpl.java
+++ b/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenServiceAbstractImpl.java
@@ -10,8 +10,6 @@
 import me.chanjar.weixin.open.api.WxOpenComponentService;
 import me.chanjar.weixin.open.api.WxOpenConfigStorage;
 import me.chanjar.weixin.open.api.WxOpenService;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 import java.io.IOException;
 
diff --git a/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenServiceApacheHttpClientImpl.java b/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenServiceApacheHttpClientImpl.java
index 2cf3b8adbf..a90dbed5c5 100644
--- a/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenServiceApacheHttpClientImpl.java
+++ b/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenServiceApacheHttpClientImpl.java
@@ -1,6 +1,5 @@
 package me.chanjar.weixin.open.api.impl;
 
-import me.chanjar.weixin.common.bean.result.WxMediaUploadResult;
 import me.chanjar.weixin.common.bean.result.WxMinishopImageUploadResult;
 import me.chanjar.weixin.common.error.WxErrorException;
 import me.chanjar.weixin.common.util.http.*;
@@ -53,8 +52,8 @@ public HttpHost getRequestHttpProxy() {
   }
 
   @Override
-  public HttpType getRequestType() {
-    return HttpType.APACHE_HTTP;
+  public HttpClientType getRequestType() {
+    return HttpClientType.APACHE_HTTP;
   }
 
   @Override
diff --git a/weixin-java-open/src/main/java/me/chanjar/weixin/open/bean/auth/MaAuthQueryIdentityTreeResult.java b/weixin-java-open/src/main/java/me/chanjar/weixin/open/bean/auth/MaAuthQueryIdentityTreeResult.java
index c3960a2b55..b7660d6da4 100644
--- a/weixin-java-open/src/main/java/me/chanjar/weixin/open/bean/auth/MaAuthQueryIdentityTreeResult.java
+++ b/weixin-java-open/src/main/java/me/chanjar/weixin/open/bean/auth/MaAuthQueryIdentityTreeResult.java
@@ -24,6 +24,6 @@ public class MaAuthQueryIdentityTreeResult extends WxOpenResult {
    * 子节点信息 非叶子节点必有
    */
   @Nullable
-  @SerializedName("node_list")
+  @SerializedName("identity_tree_list")
   private List nodeList;
 }
diff --git a/weixin-java-open/src/main/java/me/chanjar/weixin/open/bean/authandicp/WxOpenQueryAuthAndIcpResult.java b/weixin-java-open/src/main/java/me/chanjar/weixin/open/bean/authandicp/WxOpenQueryAuthAndIcpResult.java
new file mode 100644
index 0000000000..2c2e9e16b6
--- /dev/null
+++ b/weixin-java-open/src/main/java/me/chanjar/weixin/open/bean/authandicp/WxOpenQueryAuthAndIcpResult.java
@@ -0,0 +1,134 @@
+package me.chanjar.weixin.open.bean.authandicp;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.*;
+import me.chanjar.weixin.open.bean.result.WxOpenResult;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * @author 痴货
+ * @Description
+ * @createTime 2025/06/18 23:00
+ */
+@Getter
+@Setter
+@NoArgsConstructor
+public class WxOpenQueryAuthAndIcpResult extends WxOpenResult {
+
+  private static final long serialVersionUID = -1175687030580654852L;
+
+  /**
+   * 当前任务流程状态,见下方任务流程状态枚举
+   * 9	手机验证成功
+   * 15	等待支付认证审核费用
+   * 16	认证审核费用支付成功
+   * 17	认证审核中
+   * 18	认证审核驳回
+   * 19	认证审核通过
+   * 20	认证审核最终失败(不能再修改)
+   * 21	创建备案审核单失败
+   * 22	备案平台审核中
+   * 23	备案平台审核驳回
+   * 24	备案管局审核中
+   * 25	管局审核驳回
+   * 26	认证及备案完成
+   * 27	流程已过期
+   * 28	流程已终止
+   * 29	备案已撤回
+   */
+  @SerializedName("procedure_status")
+  private Integer procedureStatus;
+
+  /**
+   * 小程序后台展示的认证订单号
+   */
+  @SerializedName("orderid")
+  private Integer orderId;
+
+  /**
+   * 小程序认证审核单被驳回(procedure_status 为 18)时有效
+   */
+  @SerializedName("refill_reason")
+  private String refillReason;
+
+  /**
+   * 小程序认证审核最终失败的原因(procedure_status 为 20)时有效
+   */
+  @SerializedName("fail_reason")
+  private String failReason;
+
+  /**
+   * 小程序备案相关信息
+   */
+  @SerializedName("icp_audit")
+  private IcpAudit icpAudit;
+
+  @Getter
+  @Setter
+  @NoArgsConstructor
+  public static class IcpAudit implements Serializable {
+
+    private static final long serialVersionUID = 879913578852421216L;
+
+    /**
+     * 错误提示,创建备案审核单失败时返回(procedure_status 为 21)
+     */
+    @SerializedName("hints")
+    private List hints;
+
+    /**
+     * 驳回原因,备案不通过时返回(procedure_status 为 23、25)
+     */
+    @SerializedName("audit_data")
+    private AuditData auditData;
+
+    /**
+     * 管局短信核验状态,仅当任务流程状态为 24(备案管局审核中)的时候才有效。1:等待核验中,2:核验完成,3:核验超时。
+     */
+    @SerializedName("sms_verify_status")
+    private Integer smsVerifyStatus;
+
+  }
+
+  @Getter
+  @Setter
+  @NoArgsConstructor
+  public static class AuditData implements Serializable {
+
+    private static final long serialVersionUID = 2217833539540191890L;
+
+    /**
+     * 审核不通过的字段中文名
+     */
+    @SerializedName("key_name")
+    private String keyName;
+
+    /**
+     * 字段不通过的原因
+     */
+    @SerializedName("error")
+    private String error;
+
+    /**
+     * 修改建议
+     */
+    @SerializedName("suggest")
+    private String suggest;
+  }
+
+  @Data
+  @EqualsAndHashCode(callSuper = true)
+  public static class Hint extends WxOpenResult {
+
+    private static final long serialVersionUID = 6585787444231265854L;
+
+    /**
+     * 校验失败的字段
+     */
+    @SerializedName("err_field")
+    private String errField;
+  }
+
+}
diff --git a/weixin-java-open/src/main/java/me/chanjar/weixin/open/bean/authandicp/WxOpenSubmitAuthAndIcpParam.java b/weixin-java-open/src/main/java/me/chanjar/weixin/open/bean/authandicp/WxOpenSubmitAuthAndIcpParam.java
new file mode 100644
index 0000000000..3de4a0c9aa
--- /dev/null
+++ b/weixin-java-open/src/main/java/me/chanjar/weixin/open/bean/authandicp/WxOpenSubmitAuthAndIcpParam.java
@@ -0,0 +1,767 @@
+package me.chanjar.weixin.open.bean.authandicp;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import me.chanjar.weixin.open.api.WxOpenMaIcpService;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * @author 痴货
+ * @Description
+ * @createTime 2025/06/18 23:00
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class WxOpenSubmitAuthAndIcpParam implements Serializable {
+
+  private static final long serialVersionUID = 5545621231231213158L;
+
+  /**
+   * 认证数据
+   */
+  @SerializedName("auth_data")
+  private AuthData authData;
+
+  /**
+   * 备案主体信息
+   */
+  @SerializedName("icp_subject")
+  private IcpSubject icpSubject;
+
+  /**
+   * 微信小程序信息
+   */
+  @SerializedName("icp_applets")
+  private IcpApplets icpApplets;
+
+  /**
+   * 其他备案媒体材料
+   */
+  @SerializedName("icp_materials")
+  private IcpMaterials icpMaterials;
+
+
+  @Data
+  @Builder
+  @NoArgsConstructor
+  @AllArgsConstructor
+  public static class AuthData implements Serializable {
+
+    private static final long serialVersionUID = 5545289494161613158L;
+
+    /**
+     * 联系人信息
+     */
+    @SerializedName("contact_info")
+    private ContactInfo contactInfo;
+
+    /**
+     * 发票信息,如果是服务商代缴模式,不需要填写
+     */
+    @SerializedName("invoice_info")
+    private InvoiceInfo invoiceInfo;
+
+    /**
+     * 认证主体类型:1.企业;12.个体工商户;15.个人
+     */
+    @SerializedName("customer_type")
+    private Integer customerType;
+
+    /**
+     * 支付方式 1:消耗服务商预购包 2:小程序开发者自行支付
+     */
+    @SerializedName("pay_type")
+    private Integer payType;
+
+    /**
+     * 主体资质其他证明材料,最多上传10张图片
+     */
+    @SerializedName("qualification_other")
+    private List qualificationOther;
+
+    /**
+     * 小程序账号名称
+     */
+    @SerializedName("account_name")
+    private String accountName;
+
+    /**
+     * 小程序账号名称命名类型 1:基于自选词汇命名 2:基于商标命名
+     */
+    @SerializedName("account_name_type")
+    private String accountNameType;
+
+    /**
+     * 名称命中关键词-补充材料,支持上传多张图片
+     */
+    @SerializedName("account_supplemental")
+    private List accountSupplemental;
+
+    /**
+     * 认证类型为个人类型时可以选择要认证的身份,从 查询个人认证身份选项列表 里获取,填叶节点的name
+     */
+    @SerializedName("auth_identification")
+    private String authIdentification;
+
+    /**
+     * 填了 auth_identification 则必填。身份证明材料 (1)基于不同认证身份上传不同的材料;(2)认证类型=1时选填,支持上传10张图片
+     */
+    @SerializedName("auth_ident_material")
+    private List authIdentMaterial;
+
+    /**
+     * 第三方联系电话
+     */
+    @SerializedName("third_party_phone")
+    private String thirdPartyPhone;
+
+    /**
+     * 选择服务商代缴模式时必填。服务市场 appid,该服务市场账号主体必须与服务商账号主体一致
+     */
+    @SerializedName("service_appid")
+    private String serviceAppid;
+
+  }
+
+  @Data
+  @Builder
+  @NoArgsConstructor
+  @AllArgsConstructor
+  public static class ContactInfo implements Serializable {
+
+    private static final long serialVersionUID = -2962862643438222305L;
+
+    /**
+     * 认证联系人姓名
+     */
+    @SerializedName("name")
+    private String name;
+
+    /**
+     * 认证联系人邮箱
+     */
+    @SerializedName("email")
+    private String email;
+
+    /**
+     * 认证联系人手机号
+     */
+    @SerializedName("mobile")
+    private String mobile;
+
+  }
+
+  @Data
+  @Builder
+  @NoArgsConstructor
+  @AllArgsConstructor
+  public static class InvoiceInfo implements Serializable {
+
+    private static final long serialVersionUID = 4564894651613131322L;
+
+    /**
+     * 发票类型 1: 不开发票 2: 电子发票 4: 增值税专票(数电类型)
+     */
+    @SerializedName("invoice_type")
+    private String invoiceType;
+
+    /**
+     * 发票类型=2时必填 电子发票开票信息
+     */
+    @SerializedName("electronic")
+    private Electronic electronic;
+
+    /**
+     * 发票类型=4时必填 增值税专票(数电类型)开票信息
+     */
+    @SerializedName("vat")
+    private Vat vat;
+
+  }
+
+  @Data
+  @Builder
+  @NoArgsConstructor
+  @AllArgsConstructor
+  public static class Electronic implements Serializable {
+
+    private static final long serialVersionUID = 189498465135131444L;
+
+    /**
+     * 纳税识别号(15位、17、18或20位)
+     */
+    @SerializedName("id")
+    private String id;
+
+    /**
+     * 发票备注(选填)
+     */
+    @SerializedName("desc")
+    private String desc;
+
+  }
+
+  @Data
+  @Builder
+  @NoArgsConstructor
+  @AllArgsConstructor
+  public static class Vat implements Serializable {
+
+    private static final long serialVersionUID = 829892829816551512L;
+
+    /**
+     * 企业电话
+     */
+    @SerializedName("enterprise_phone")
+    private String enterprisePhone;
+
+    /**
+     * 纳税识别号(15位、17、18或20位)
+     */
+    @SerializedName("id")
+    private String id;
+
+    /**
+     * 企业注册地址
+     */
+    @SerializedName("enterprise_address")
+    private String enterpriseAddress;
+
+    /**
+     * 企业开户银行(选填)
+     */
+    @SerializedName("bank_name")
+    private String bankName;
+
+    /**
+     * 企业银行账号(选填)
+     */
+    @SerializedName("bank_account")
+    private String bankAccount;
+
+    /**
+     * 发票备注(选填)
+     */
+    @SerializedName("desc")
+    private String desc;
+
+  }
+
+  @Data
+  @Builder
+  @NoArgsConstructor
+  @AllArgsConstructor
+  public static class IcpSubject implements Serializable {
+
+    private static final long serialVersionUID = -1256165165165165165L;
+
+    /**
+     * 主体基本信息
+     */
+    @SerializedName("base_info")
+    private SubjectBaseInfo baseInfo;
+
+    /**
+     * 个人主体额外信息
+     */
+    @SerializedName("personal_info")
+    private SubjectPersonalInfo personalInfo;
+
+    /**
+     * 主体额外信息(个人备案时,如果存在与主体负责人信息相同的字段,则填入相同的值)
+     */
+    @SerializedName("organize_info")
+    private SubjectOrganizeInfo organizeInfo;
+
+    /**
+     * 主体负责人信息
+     */
+    @SerializedName("principal_info")
+    private SubjectPrincipalInfo principalInfo;
+
+    /**
+     * 法人信息(非个人备案,且主体负责人不是法人时,必填)
+     */
+    @SerializedName("legal_person_info")
+    private SubjectLegalPersonInfo legalPersonInfo;
+  }
+
+  @Data
+  @Builder
+  @NoArgsConstructor
+  @AllArgsConstructor
+  public static class SubjectBaseInfo implements Serializable {
+
+    private static final long serialVersionUID = -1561561613212313445L;
+
+    /**
+     * 主体性质,选项参考 获取单位性质,**仅支持企业、个体工商户、个人**
+     * {@link WxOpenMaIcpService#queryIcpSubjectTypes }
+     */
+    @SerializedName("type")
+    private Integer type;
+
+    /**
+     * 主办单位名称
+     */
+    @SerializedName("name")
+    private String name;
+
+    /**
+     * 备案省份,使用省份代码
+     */
+    @SerializedName("province")
+    private String province;
+
+    /**
+     * 备案城市,使用城市代码
+     */
+    @SerializedName("city")
+    private String city;
+
+    /**
+     * 备案县区,使用县区代码
+     */
+    @SerializedName("district")
+    private String district;
+
+    /**
+     * 通讯地址,必须属于备案省市区,地址开头的省市区不用填入
+     */
+    @SerializedName("address")
+    private String address;
+
+    /**
+     * 主体信息备注,根据需要,如实填写
+     */
+    @SerializedName("comment")
+    private String comment;
+
+    /**
+     * 主体备案号
+     */
+    @SerializedName("record_number")
+    private String recordNumber;
+
+  }
+
+  @Data
+  @Builder
+  @NoArgsConstructor
+  @AllArgsConstructor
+  public static class SubjectPersonalInfo implements Serializable {
+
+    private static final long serialVersionUID = -2151981561916519288L;
+
+    /**
+     * 临时居住证明照片 media_id,个人备案且非本省人员,需要提供居住证、暂住证、社保证明、房产证等临时居住证明
+     */
+    @SerializedName("residence_permit")
+    private String residencePermit;
+
+  }
+
+  @Data
+  @Builder
+  @NoArgsConstructor
+  @AllArgsConstructor
+  public static class SubjectOrganizeInfo implements Serializable {
+
+    private static final long serialVersionUID = -1181121318132185161L;
+
+    /**
+     * 主体证件类型
+     * {@link WxOpenMaIcpService#queryIcpCertificateTypes}
+     */
+    @SerializedName("certificate_type")
+    private Integer certificateType;
+
+    /**
+     * 主体证件号码
+     */
+    @SerializedName("certificate_number")
+    private String certificateNumber;
+
+    /**
+     * 主体证件住所
+     */
+    @SerializedName("certificate_address")
+    private String certificateAddress;
+
+    /**
+     * 主体证件照片 media_id,如果小程序主体为非个人类型
+     */
+    @SerializedName("certificate_photo")
+    private String certificatePhoto;
+
+  }
+  @Data
+  @Builder
+  @NoArgsConstructor
+  @AllArgsConstructor
+  public static class SubjectPrincipalInfo implements Serializable {
+
+    private static final long serialVersionUID = -2984191321918916511L;
+
+    /**
+     * 负责人姓名
+     */
+    @SerializedName("name")
+    private String name;
+
+    /**
+     * 负责人联系方式
+     */
+    @SerializedName("mobile")
+    private String mobile;
+
+    /**
+     * 负责人电子邮件
+     */
+    @SerializedName("email")
+    private String email;
+
+    /**
+     * 负责人应急联系方式
+     */
+    @SerializedName("emergency_contact")
+    private String emergencyContact;
+
+    /**
+     * 负责人证件类型(获取证件类型
+     * {@link WxOpenMaIcpService#queryIcpCertificateTypes()})
+     */
+    @SerializedName("certificate_type")
+    private Integer certificateType;
+
+    /**
+     * 负责人证件号码
+     */
+    @SerializedName("certificate_number")
+    private String certificateNumber;
+
+    /**
+     * 负责人证件有效期起始日期,格式为 YYYYmmdd
+     */
+    @SerializedName("certificate_validity_date_start")
+    private String certificateValidityDateStart;
+
+    /**
+     * 负责人证件有效期终止日期,格式为 YYYYmmdd
+     */
+    @SerializedName("certificate_validity_date_end")
+    private String certificateValidityDateEnd;
+
+    /**
+     * 负责人证件正面照片 media_id(身份证为人像面)
+     */
+    @SerializedName("certificate_photo_front")
+    private String certificatePhotoFront;
+
+    /**
+     * 负责人证件背面照片 media_id(身份证为国徽面)
+     */
+    @SerializedName("certificate_photo_back")
+    private String certificatePhotoBack;
+
+    /**
+     * 授权书 media_id,当主体负责人不是法人时需要主体负责人授权书,当小程序负责人不是法人时需要小程序负责人授权书
+     */
+    @SerializedName("authorization_letter")
+    private String authorizationLetter;
+
+    /**
+     * 扫脸认证任务id(扫脸认证接口返回的task_id),仅小程序负责人需要扫脸,主体负责人无需扫脸
+     */
+    @SerializedName("verify_task_id")
+    private String verifyTaskId;
+
+  }
+  @Data
+  @Builder
+  @NoArgsConstructor
+  @AllArgsConstructor
+  public static class SubjectLegalPersonInfo implements Serializable {
+
+    private static final long serialVersionUID = -1259198165316516161L;
+
+    /**
+     * 法人代表姓名
+     */
+    @SerializedName("name")
+    private String name;
+
+    /**
+     * 法人证件号码
+     */
+    @SerializedName("certificate_number")
+    private String certificateNumber;
+
+  }
+
+  @Data
+  @Builder
+  @NoArgsConstructor
+  @AllArgsConstructor
+  public static class IcpApplets implements Serializable {
+
+    private static final long serialVersionUID = -2156129841651651651L;
+
+    /**
+     * 微信小程序基本信息
+     */
+    @SerializedName("base_info")
+    private AppletsBaseInfo baseInfo;
+
+    /**
+     * 小程序负责人信息
+     */
+    @SerializedName("principal_info")
+    private AppletsPrincipalInfo principalInfo;
+
+  }
+
+  @Data
+  @Builder
+  @NoArgsConstructor
+  @AllArgsConstructor
+  public static class AppletsBaseInfo implements Serializable {
+
+    private static final long serialVersionUID = 8404017028547715919L;
+
+    /**
+     * 小程序ID,不用填写,后台自动拉取
+     */
+    @SerializedName("appid")
+    private String appId;
+
+    /**
+     * 小程序名称,不用填写,后台自动拉取
+     */
+    @SerializedName("name")
+    private String name;
+
+    /**
+     * 小程序服务内容类型,只能填写二级服务内容类型,最多5个
+     * {@link WxOpenMaIcpService#queryIcpServiceContentTypes}
+     */
+    @SerializedName("service_content_types")
+    private List serviceContentTypes;
+
+    /**
+     * 前置审批项,列表中不能存在重复的前置审批类型id,如不涉及前置审批项,也需要填“以上都不涉及”
+     */
+    @SerializedName("nrlx_details")
+    private List nrlxDetails;
+
+    /**
+     * 请具体描述小程序实际经营内容、主要服务内容,该信息为主管部门审核重要依据,备注内容字数限制20-200字,请认真填写。
+     */
+    @SerializedName("comment")
+    private String comment;
+
+    /**
+     * 小程序备案号,示例值:粤B2-20090059-1626X
+     * (申请小程序备案时不用填写,查询已备案详情时会返回)
+     */
+    @SerializedName("record_number")
+    private String recordNumber;
+  }
+
+  @Data
+  @Builder
+  @NoArgsConstructor
+  @AllArgsConstructor
+  public static class AppletsNrlxDetailItem implements Serializable {
+
+    private static final long serialVersionUID = -9144721738792167000L;
+
+    /**
+     * 前置审批类型
+     * {@link WxOpenMaIcpService#queryIcpNrlxTypes}
+     */
+    @SerializedName("type")
+    private Integer type;
+
+    /**
+     * 前置审批号,如果前置审批类型不是“以上都不涉及”,
+     * 则必填,示例值:"粤-12345号
+     */
+    @SerializedName("code")
+    private String code;
+
+    /**
+     * 前置审批媒体材料 media_id
+     */
+    @SerializedName("media")
+    private String media;
+  }
+
+  @Data
+  @Builder
+  @NoArgsConstructor
+  @AllArgsConstructor
+  public static class AppletsPrincipalInfo implements Serializable {
+
+    private static final long serialVersionUID = 5088256283066784463L;
+
+    /**
+     * 负责人姓名
+     */
+    @SerializedName("name")
+    private String name;
+
+    /**
+     * 负责人联系方式
+     */
+    @SerializedName("mobile")
+    private String mobile;
+
+    /**
+     * 负责人电子邮件
+     */
+    @SerializedName("email")
+    private String email;
+
+    /**
+     * 负责人应急联系方式
+     */
+    @SerializedName("emergency_contact")
+    private String emergencyContact;
+
+    /**
+     * 负责人证件类型
+     * {@link WxOpenMaIcpService#queryIcpCertificateTypes}
+     */
+    @SerializedName("certificate_type")
+    private Integer certificateType;
+
+    /**
+     * 负责人证件号码
+     */
+    @SerializedName("certificate_number")
+    private String certificateNumber;
+
+    /**
+     * 负责人证件有效期起始日期,
+     * 格式为 YYYYmmdd,示例值:"20230815"
+     */
+    @SerializedName("certificate_validity_date_start")
+    private String certificateValidityDateStart;
+
+    /**
+     * 负责人证件有效期终止日期,
+     * 格式为 YYYYmmdd,
+     * 如证件长期有效,请填写 "长期",示例值:"20330815"
+     */
+    @SerializedName("certificate_validity_date_end")
+    private String certificateValidityDateEnd;
+
+    /**
+     * 负责人证件正面照片 media_id
+     * (身份证为人像面)
+     */
+    @SerializedName("certificate_photo_front")
+    private String certificatePhotoFront;
+
+    /**
+     * 负责人证件背面照片 media_id
+     * (身份证为国徽面)
+     */
+    @SerializedName("certificate_photo_back")
+    private String certificatePhotoBack;
+
+    /**
+     * 授权书 media_id,
+     * 当主体负责人不是法人时需要主体负责人授权书,
+     * 当小程序负责人不是法人时需要小程序负责人授权书
+     */
+    @SerializedName("authorization_letter")
+    private String authorizationLetter;
+
+    /**
+     * 扫脸认证任务id(扫脸认证接口返回的task_id),
+     * 仅小程序负责人需要扫脸,主体负责人无需扫脸
+     */
+    @SerializedName("verify_task_id")
+    private String verifyTaskId;
+  }
+
+  @Data
+  @Builder
+  @NoArgsConstructor
+  @AllArgsConstructor
+  public static class IcpMaterials implements Serializable {
+
+    private static final long serialVersionUID = -2651654844198165191L;
+
+    /**
+     * 互联网信息服务承诺书 media_id,最多上传1个
+     */
+    @SerializedName("commitment_letter")
+    private List commitmentLetter;
+
+    /**
+     * 主体更名函 media_id(非个人类型,且发生过更名时需要上传),最多上传1个
+     */
+    @SerializedName("business_name_change_letter")
+    private List businessNameChangeLetter;
+
+    /**
+     * 党建确认函 media_id,最多上传1个
+     */
+    @SerializedName("party_building_confirmation_letter")
+    private List partyBuildingConfirmationLetter;
+
+    /**
+     * 承诺视频 media_id,最多上传1个
+     */
+    @SerializedName("promise_video")
+    private List promiseVideo;
+
+    /**
+     * 网站备案信息真实性责任告知书 media_id,最多上传1个
+     */
+    @SerializedName("authenticity_responsibility_letter")
+    private List authenticityResponsibilityLetter;
+
+    /**
+     * 小程序备案信息真实性承诺书 media_id,最多上传1个
+     */
+    @SerializedName("authenticity_commitment_letter")
+    private List authenticityCommitmentLetter;
+
+    /**
+     * 小程序建设方案书 media_id,最多上传1个
+     */
+    @SerializedName("website_construction_proposal")
+    private List websiteConstructionProposal;
+
+    /**
+     * 主体其它附件 media_id,最多上传10个
+     */
+    @SerializedName("subject_other_materials")
+    private List subjectOtherMaterials;
+
+    /**
+     * 小程序其它附件 media_id,最多上传10个
+     */
+    @SerializedName("applets_other_materials")
+    private List appletsOtherMaterials;
+
+    /**
+     * 手持证件照 media_id,最多上传1个
+     */
+    @SerializedName("holding_certificate_photo")
+    private List holdingCertificatePhoto;
+
+  }
+
+}
diff --git a/weixin-java-open/src/main/java/me/chanjar/weixin/open/bean/authandicp/WxOpenSubmitAuthAndIcpResult.java b/weixin-java-open/src/main/java/me/chanjar/weixin/open/bean/authandicp/WxOpenSubmitAuthAndIcpResult.java
new file mode 100644
index 0000000000..08dbc80f2b
--- /dev/null
+++ b/weixin-java-open/src/main/java/me/chanjar/weixin/open/bean/authandicp/WxOpenSubmitAuthAndIcpResult.java
@@ -0,0 +1,52 @@
+package me.chanjar.weixin.open.bean.authandicp;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.*;
+import me.chanjar.weixin.open.bean.result.WxOpenResult;
+
+import java.util.List;
+
+/**
+ * @author 痴货
+ * @Description
+ * @createTime 2025/06/18 23:00
+ */
+@Getter
+@Setter
+@NoArgsConstructor
+public class WxOpenSubmitAuthAndIcpResult extends WxOpenResult {
+
+  private static final long serialVersionUID = -1175687058498454852L;
+
+  /**
+   * 错误提示
+   */
+  @SerializedName("hints")
+  private List hints;
+
+  /**
+   * 小程序认证及备案任务流程 id
+   */
+  @SerializedName("procedure_id")
+  private String procedureId;
+
+  /**
+   * 小程序认证认证审核费用付费链接,当 pay_type 为 2 时返回
+   */
+  @SerializedName("pay_url")
+  private String payUrl;
+
+  @Data
+  @EqualsAndHashCode(callSuper = true)
+  public static class Hint extends WxOpenResult {
+
+    private static final long serialVersionUID = 6585787444231265854L;
+
+    /**
+     * 校验失败的字段
+     */
+    @SerializedName("err_field")
+    private String errField;
+  }
+
+}
diff --git a/weixin-java-open/src/main/java/me/chanjar/weixin/open/bean/icp/WxOpenApplyIcpFilingParam.java b/weixin-java-open/src/main/java/me/chanjar/weixin/open/bean/icp/WxOpenApplyIcpFilingParam.java
index ef24a5360c..c09bfc73f6 100644
--- a/weixin-java-open/src/main/java/me/chanjar/weixin/open/bean/icp/WxOpenApplyIcpFilingParam.java
+++ b/weixin-java-open/src/main/java/me/chanjar/weixin/open/bean/icp/WxOpenApplyIcpFilingParam.java
@@ -313,7 +313,7 @@ public static class Applets implements Serializable {
      * 微信小程序基本信息
      */
     @SerializedName("base_info")
-    private AppletsBaseInfo basInfo;
+    private AppletsBaseInfo baseInfo;
 
     /**
      * 小程序负责人信息
diff --git a/weixin-java-open/src/main/java/me/chanjar/weixin/open/bean/icp/WxOpenCreateIcpVerifyTaskParam.java b/weixin-java-open/src/main/java/me/chanjar/weixin/open/bean/icp/WxOpenCreateIcpVerifyTaskParam.java
new file mode 100644
index 0000000000..25be43a337
--- /dev/null
+++ b/weixin-java-open/src/main/java/me/chanjar/weixin/open/bean/icp/WxOpenCreateIcpVerifyTaskParam.java
@@ -0,0 +1,26 @@
+package me.chanjar.weixin.open.bean.icp;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * @author 痴货
+ * @Description 发起小程序管理员人脸核身
+ * @createTime 2025/06/21 00:20
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class WxOpenCreateIcpVerifyTaskParam {
+
+  /**
+   * 小程序认证及备案二合一场景,填 true,否则为小程序备案场景。默认值为 false
+   */
+  @SerializedName("along_with_auth")
+  private Boolean alongWithAuth;
+
+}
diff --git a/weixin-java-open/src/main/java/me/chanjar/weixin/open/bean/icp/WxOpenIcpCreateIcpVerifyTaskResult.java b/weixin-java-open/src/main/java/me/chanjar/weixin/open/bean/icp/WxOpenIcpCreateIcpVerifyTaskResult.java
index 967e81fa95..f2ff3d166d 100644
--- a/weixin-java-open/src/main/java/me/chanjar/weixin/open/bean/icp/WxOpenIcpCreateIcpVerifyTaskResult.java
+++ b/weixin-java-open/src/main/java/me/chanjar/weixin/open/bean/icp/WxOpenIcpCreateIcpVerifyTaskResult.java
@@ -1,7 +1,5 @@
 package me.chanjar.weixin.open.bean.icp;
 
-import com.fasterxml.jackson.annotation.JsonProperty;
-
 import com.google.gson.annotations.SerializedName;
 import lombok.Getter;
 import lombok.NoArgsConstructor;
@@ -27,8 +25,9 @@ public class WxOpenIcpCreateIcpVerifyTaskResult extends WxOpenResult {
   private String taskId;
 
   /**
-   * 人脸核验任务url,along_with_auth 填 true 时返回。
+   * 人脸核验任务url,{@link WxOpenCreateIcpVerifyTaskParam#alongWithAuth } 填 true 时返回
    */
   @SerializedName("verify_url")
   private String verifyUrl;
+
 }
diff --git a/weixin-java-open/src/main/java/me/chanjar/weixin/open/bean/icp/WxOpenOnlineIcpOrderResult.java b/weixin-java-open/src/main/java/me/chanjar/weixin/open/bean/icp/WxOpenOnlineIcpOrderResult.java
index 89f8e8c397..a2a662441b 100644
--- a/weixin-java-open/src/main/java/me/chanjar/weixin/open/bean/icp/WxOpenOnlineIcpOrderResult.java
+++ b/weixin-java-open/src/main/java/me/chanjar/weixin/open/bean/icp/WxOpenOnlineIcpOrderResult.java
@@ -1,7 +1,6 @@
 package me.chanjar.weixin.open.bean.icp;
 
 import java.io.Serializable;
-import java.util.List;
 
 import com.google.gson.annotations.SerializedName;
 
diff --git a/weixin-java-open/src/main/java/me/chanjar/weixin/open/bean/icp/WxOpenUploadIcpMediaResult.java b/weixin-java-open/src/main/java/me/chanjar/weixin/open/bean/icp/WxOpenUploadIcpMediaResult.java
index 04977113e1..b116bef069 100644
--- a/weixin-java-open/src/main/java/me/chanjar/weixin/open/bean/icp/WxOpenUploadIcpMediaResult.java
+++ b/weixin-java-open/src/main/java/me/chanjar/weixin/open/bean/icp/WxOpenUploadIcpMediaResult.java
@@ -5,7 +5,6 @@
 import lombok.Getter;
 import lombok.NoArgsConstructor;
 import lombok.Setter;
-import me.chanjar.weixin.mp.util.json.WxMpGsonBuilder;
 import me.chanjar.weixin.open.bean.result.WxOpenResult;
 
 /**
diff --git a/weixin-java-open/src/main/java/me/chanjar/weixin/open/bean/ma/WxMaOpenCommitExtInfo.java b/weixin-java-open/src/main/java/me/chanjar/weixin/open/bean/ma/WxMaOpenCommitExtInfo.java
index 93d2a095b8..07c89a06f2 100644
--- a/weixin-java-open/src/main/java/me/chanjar/weixin/open/bean/ma/WxMaOpenCommitExtInfo.java
+++ b/weixin-java-open/src/main/java/me/chanjar/weixin/open/bean/ma/WxMaOpenCommitExtInfo.java
@@ -94,6 +94,26 @@ public class WxMaOpenCommitExtInfo implements Serializable {
   @SerializedName("tabBar")
   private WxMaOpenTabBar tabBar;
 
+  /**
+   * 关于新增 requiredPrivateInfos 说明
+   * 关于地理位置接口新增与相关流程调整可以查看社区公告:
+   * 点击查看
+   * 7.14后,在代码中使用的地理位置相关接口(共计 8 个,见表1),第三方开发者均需要在 ext_json 参数中 requiredPrivateInfos 配置项中声明
+   * 在ext_json参数中配置requiredPrivateInfos,其规则为「整体替换」。即如果在 app.json 里也配置了,那么最终会是ext_json的配置会覆盖 app.json
+   * 配置的requiredPrivateInfos。其余规则可查看下方的「ext_json补充说明」
+   * 在ext_json参数中配置 requiredPrivateInfos 示例如下
+   * {
+   * "template_id": "95",
+   * "ext_json": "{\"requiredPrivateInfos\":[\"onLocationChange\",\"startLocationUpdate\"]}",
+   * "user_version": "V1.0",
+   * "user_desc": "test"
+   * }
+   * requiredPrivateInfos主要会检查格式是否正确,填入的 api 名称是否正确,填入的 api 名称是否有权限,填入的 api 名称是否互斥。对应的错误码可查看文档末尾的错误码文档。
+   * requiredPrivateInfos在2022.7.14后才会生效,文档提前更新是为了方便开发者可以提前了解接口的参数变更规则,提前进行调整。
+   */
+  @SerializedName("requiredPrivateInfos")
+  private String[] requiredPrivateInfos;
+
   /**
    * 添加扩展项
    *
@@ -101,10 +121,12 @@ public class WxMaOpenCommitExtInfo implements Serializable {
    * @param value
    */
   public void addExt(String key, String value) {
-    if (extMap == null)
+    if (extMap == null) {
       extMap = new HashMap<>();
-    if (StringUtils.isNoneBlank(key, value))
+    }
+    if (StringUtils.isNoneBlank(key, value)) {
       extMap.put(key, value);
+    }
   }
 
   /**
@@ -114,10 +136,12 @@ public void addExt(String key, String value) {
    * @param page
    */
   public void addExtPage(String pagePath, WxMaOpenPage page) {
-    if (extPages == null)
+    if (extPages == null) {
       extPages = new HashMap<>();
-    if (StringUtils.isNotBlank(pagePath) && page != null)
+    }
+    if (StringUtils.isNotBlank(pagePath) && page != null) {
       extPages.put(pagePath, page);
+    }
   }
 
   /**
@@ -126,10 +150,12 @@ public void addExtPage(String pagePath, WxMaOpenPage page) {
    * @param pagePath
    */
   public void addPage(String pagePath) {
-    if (pageList == null)
+    if (pageList == null) {
       pageList = new ArrayList<>();
-    if (StringUtils.isNotBlank(pagePath))
+    }
+    if (StringUtils.isNotBlank(pagePath)) {
       pageList.add(pagePath);
+    }
   }
 
   public static WxMaOpenCommitExtInfo INSTANCE() {
diff --git a/weixin-java-open/src/main/java/me/chanjar/weixin/open/bean/message/WxOpenXmlMessage.java b/weixin-java-open/src/main/java/me/chanjar/weixin/open/bean/message/WxOpenXmlMessage.java
index df782e6a0c..9185a4e030 100644
--- a/weixin-java-open/src/main/java/me/chanjar/weixin/open/bean/message/WxOpenXmlMessage.java
+++ b/weixin-java-open/src/main/java/me/chanjar/weixin/open/bean/message/WxOpenXmlMessage.java
@@ -163,15 +163,40 @@ public class WxOpenXmlMessage implements Serializable {
   //region 认证及备案流程的主要节点均有事件推送到第三方平台的授权事件接收接口,包括支付完成、派单给审核机构、审核打回、审核通过、审核失败等。消息类型,固定为 notify_3rd_wxa_auth_and_icp
 
   /**
-   * 小程序认证及备案任务流程 id
+   * 小程序认证及备案任务流程id
    */
   @XStreamAlias("procedure_id")
   private String procedureId;
+
   /**
-   * 当前任务流程状态,见“小程序认证及备案进度查询” API 文档中的任务流程状态枚举
+   * 任务流程状态
+   * 9	手机验证成功
+   * 15	等待支付认证审核费用
+   * 16	认证审核费用支付成功
+   * 17	认证审核中
+   * 18	认证审核驳回
+   * 19	认证审核通过
+   * 20	认证审核最终失败(不能再修改)
+   * 21	创建备案审核单失败
+   * 22	备案平台审核中
+   * 23	备案平台审核驳回
+   * 24	备案管局审核中
+   * 25	管局审核驳回
+   * 26	认证及备案完成
+   * 27	流程已过期
+   * 28	流程已终止
+   * 29	备案已撤回
    */
   @XStreamAlias("procedure_status")
   private Integer procedureStatus;
+
+  //endregion
+
+  /**
+   * 原始通知内容
+   */
+  private String context;
+
   //endregion
 
   /**
@@ -296,7 +321,9 @@ public static WxOpenXmlMessage fromEncryptedXml(String encryptedXml, WxOpenConfi
     WxOpenCryptUtil cryptUtil = new WxOpenCryptUtil(wxOpenConfigStorage);
     String plainText = cryptUtil.decryptXml(msgSignature, timestamp, nonce, encryptedXml);
     log.debug("解密后的原始xml消息内容:{}", plainText);
-    return fromXml(plainText);
+    WxOpenXmlMessage wxOpenXmlMessage = fromXml(plainText);
+    wxOpenXmlMessage.setContext(plainText);
+    return wxOpenXmlMessage;
   }
 
   public static WxMpXmlMessage fromEncryptedMpXml(String encryptedXml, WxOpenConfigStorage wxOpenConfigStorage,
diff --git a/weixin-java-open/src/main/java/me/chanjar/weixin/open/bean/result/WxOpenGetAllCategoriesByTypeResult.java b/weixin-java-open/src/main/java/me/chanjar/weixin/open/bean/result/WxOpenGetAllCategoriesByTypeResult.java
new file mode 100644
index 0000000000..00cefeb37c
--- /dev/null
+++ b/weixin-java-open/src/main/java/me/chanjar/weixin/open/bean/result/WxOpenGetAllCategoriesByTypeResult.java
@@ -0,0 +1,153 @@
+package me.chanjar.weixin.open.bean.result;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * 获取所有类目
+ * author 痴货
+ */
+@Getter
+@Setter
+@NoArgsConstructor
+public class WxOpenGetAllCategoriesByTypeResult extends WxOpenResult {
+
+  private static final long serialVersionUID = -2845321894615646115L;
+
+  /**
+   * 类目信息列表
+   */
+  @SerializedName("categories_list")
+  private CategoriesList categorieslist;
+
+  @Getter
+  @Setter
+  @NoArgsConstructor
+  public static class CategoriesList implements Serializable {
+
+    private static final long serialVersionUID = -2845321894615646115L;
+
+    /**
+     * 类目信息
+     */
+    @SerializedName("categories")
+    private List categories;
+  }
+
+  @Getter
+  @Setter
+  @NoArgsConstructor
+  public static class Categories implements Serializable {
+
+    private static final long serialVersionUID = -284532256461546115L;
+
+    /**
+     * 类目 ID
+     */
+    @SerializedName("id")
+    private Integer id;
+
+    /**
+     * 类目名称
+     */
+    @SerializedName("name")
+    private String name;
+
+    /**
+     * 类目层级
+     */
+    @SerializedName("level")
+    private Integer level;
+
+    /**
+     * 父类目 ID
+     */
+    @SerializedName("father")
+    private Integer father;
+
+    /**
+     * 子类目 ID
+     */
+    @SerializedName("children")
+    private List children;
+
+    /**
+     * 是否敏感类目(1 为敏感类目,需要提供相应资质审核;0 为非敏感类目,无需审核)
+     */
+    @SerializedName("sensitive_type")
+    private Integer sensitiveType;
+
+    /**
+     * sensitive_type 为 1 的类目需要提供的资质证明
+     */
+    @SerializedName("qualify")
+    private Qualify qualify;
+
+    /**
+     * 类目权限范围
+     */
+    @SerializedName("scope")
+    private String scope;
+
+  }
+
+  @Getter
+  @Setter
+  @NoArgsConstructor
+  public static class Qualify implements Serializable {
+
+    private static final long serialVersionUID = -2841894318945616115L;
+
+    /**
+     * 资质证明列表
+     */
+    @SerializedName("exter_list")
+    private List exterList;
+
+    /**
+     * 备注
+     */
+    @SerializedName("remark")
+    private String remark;
+
+  }
+
+  @Getter
+  @Setter
+  @NoArgsConstructor
+  public static class Exter implements Serializable {
+
+    private static final long serialVersionUID = -2841894318945616115L;
+
+    @SerializedName("inner_list")
+    private List innerList;
+
+  }
+
+  @Getter
+  @Setter
+  @NoArgsConstructor
+  public static class Inner implements Serializable {
+
+    private static final long serialVersionUID = -15646131313516531L;
+
+    /**
+     * 资质文件名称
+     */
+    @SerializedName("name")
+    private String name;
+
+    /**
+     * 资质文件示例
+     */
+    @SerializedName("url")
+    private String url;
+
+  }
+
+}
diff --git a/weixin-java-open/src/main/java/me/chanjar/weixin/open/bean/result/WxOpenVersioninfoResult.java b/weixin-java-open/src/main/java/me/chanjar/weixin/open/bean/result/WxOpenVersioninfoResult.java
index 30bf9127c9..ae79d49589 100644
--- a/weixin-java-open/src/main/java/me/chanjar/weixin/open/bean/result/WxOpenVersioninfoResult.java
+++ b/weixin-java-open/src/main/java/me/chanjar/weixin/open/bean/result/WxOpenVersioninfoResult.java
@@ -5,9 +5,6 @@
 import lombok.EqualsAndHashCode;
 import me.chanjar.weixin.open.util.json.WxOpenGsonBuilder;
 
-import java.io.Serializable;
-import java.util.List;
-
 /**
  * 小程序版本信息
  *
diff --git a/weixin-java-open/src/main/java/me/chanjar/weixin/open/bean/shoppingOrders/ShoppingInfoVerifyUpload.java b/weixin-java-open/src/main/java/me/chanjar/weixin/open/bean/shoppingOrders/ShoppingInfoVerifyUpload.java
index 30b778f6f0..325ab65b00 100644
--- a/weixin-java-open/src/main/java/me/chanjar/weixin/open/bean/shoppingOrders/ShoppingInfoVerifyUpload.java
+++ b/weixin-java-open/src/main/java/me/chanjar/weixin/open/bean/shoppingOrders/ShoppingInfoVerifyUpload.java
@@ -7,7 +7,6 @@
 import lombok.NoArgsConstructor;
 
 import java.io.Serializable;
-import java.util.List;
 
 @Data
 @Builder
diff --git a/weixin-java-open/src/main/java/me/chanjar/weixin/open/executor/CommonUploadMultiRequestExecutor.java b/weixin-java-open/src/main/java/me/chanjar/weixin/open/executor/CommonUploadMultiRequestExecutor.java
index 4812dd325c..17c7906c5a 100644
--- a/weixin-java-open/src/main/java/me/chanjar/weixin/open/executor/CommonUploadMultiRequestExecutor.java
+++ b/weixin-java-open/src/main/java/me/chanjar/weixin/open/executor/CommonUploadMultiRequestExecutor.java
@@ -1,11 +1,15 @@
 package me.chanjar.weixin.open.executor;
 
+import jodd.http.HttpConnectionProvider;
+import jodd.http.ProxyInfo;
+import me.chanjar.weixin.common.util.http.okhttp.OkHttpProxyInfo;
 import me.chanjar.weixin.open.bean.CommonUploadMultiParam;
 import me.chanjar.weixin.common.enums.WxType;
 import me.chanjar.weixin.common.error.WxErrorException;
 import me.chanjar.weixin.common.util.http.RequestExecutor;
 import me.chanjar.weixin.common.util.http.RequestHttp;
 import me.chanjar.weixin.common.util.http.ResponseHandler;
+import okhttp3.OkHttpClient;
 
 import java.io.IOException;
 
@@ -33,15 +37,19 @@ public void execute(String uri, CommonUploadMultiParam data, ResponseHandler create(RequestHttp requestHttp) {
+  @SuppressWarnings("unchecked")
+  public static RequestExecutor create(RequestHttp requestHttp) {
     switch (requestHttp.getRequestType()) {
       case APACHE_HTTP:
-        return new CommonUploadMultiRequestExecutorApacheImpl(requestHttp);
+        return new CommonUploadMultiRequestExecutorApacheImpl(
+          (RequestHttp) requestHttp);
       case JODD_HTTP:
-        return new CommonUploadMultiRequestExecutorJoddHttpImpl(requestHttp);
+        return new CommonUploadMultiRequestExecutorJoddHttpImpl((RequestHttp) requestHttp);
       case OK_HTTP:
-        return new CommonUploadMultiRequestExecutorOkHttpImpl(requestHttp);
+        return new CommonUploadMultiRequestExecutorOkHttpImpl((RequestHttp) requestHttp);
+      case HTTP_COMPONENTS:
+        return new CommonUploadMultiRequestExecutorHttpComponentsImpl(
+          (RequestHttp) requestHttp);
       default:
         throw new IllegalArgumentException("不支持的http执行器类型:" + requestHttp.getRequestType());
     }
diff --git a/weixin-java-open/src/main/java/me/chanjar/weixin/open/executor/CommonUploadMultiRequestExecutorApacheImpl.java b/weixin-java-open/src/main/java/me/chanjar/weixin/open/executor/CommonUploadMultiRequestExecutorApacheImpl.java
index 2d386ea5d6..5717ded51e 100644
--- a/weixin-java-open/src/main/java/me/chanjar/weixin/open/executor/CommonUploadMultiRequestExecutorApacheImpl.java
+++ b/weixin-java-open/src/main/java/me/chanjar/weixin/open/executor/CommonUploadMultiRequestExecutorApacheImpl.java
@@ -2,17 +2,16 @@
 
 import lombok.Getter;
 import me.chanjar.weixin.common.bean.CommonUploadData;
-import me.chanjar.weixin.open.bean.CommonUploadMultiParam;
 import me.chanjar.weixin.common.bean.CommonUploadParam;
 import me.chanjar.weixin.common.enums.WxType;
 import me.chanjar.weixin.common.error.WxError;
 import me.chanjar.weixin.common.error.WxErrorException;
 import me.chanjar.weixin.common.util.http.RequestHttp;
 import me.chanjar.weixin.common.util.http.apache.Utf8ResponseHandler;
-import org.apache.http.Consts;
+import me.chanjar.weixin.open.bean.CommonUploadMultiParam;
+import org.apache.commons.lang3.StringUtils;
 import org.apache.http.HttpHost;
 import org.apache.http.client.config.RequestConfig;
-import org.apache.http.client.methods.CloseableHttpResponse;
 import org.apache.http.client.methods.HttpPost;
 import org.apache.http.entity.ContentType;
 import org.apache.http.entity.mime.HttpMultipartMode;
@@ -24,6 +23,7 @@
 
 import java.io.IOException;
 import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
 import java.util.List;
 
 /**
@@ -50,7 +50,7 @@ public String execute(String uri, CommonUploadMultiParam param, WxType wxType) t
       List normalParams = param.getNormalParams();
       if (!CollectionUtils.isEmpty(normalParams)) {
         for (CommonUploadMultiParam.NormalParam normalParam : normalParams) {
-          entity.addPart(normalParam.getName(), new StringBody(normalParam.getValue(), ContentType.create("multipart/form-data", Consts.UTF_8)));
+          entity.addPart(normalParam.getName(), new StringBody(normalParam.getValue(), ContentType.MULTIPART_FORM_DATA.withCharset(StandardCharsets.UTF_8)));
         }
       }
 
@@ -64,19 +64,15 @@ public String execute(String uri, CommonUploadMultiParam param, WxType wxType) t
 
       httpPost.setEntity(entity.build());
     }
-    try (CloseableHttpResponse response = requestHttp.getRequestHttpClient().execute(httpPost)) {
-      String responseContent = Utf8ResponseHandler.INSTANCE.handleResponse(response);
-      if (responseContent == null || responseContent.isEmpty()) {
-        throw new WxErrorException(String.format("上传失败,服务器响应空 url:%s param:%s", uri, param));
-      }
-      WxError error = WxError.fromJson(responseContent, wxType);
-      if (error.getErrorCode() != 0) {
-        throw new WxErrorException(error);
-      }
-      return responseContent;
-    } finally {
-      httpPost.releaseConnection();
+    String responseContent = requestHttp.getRequestHttpClient().execute(httpPost, Utf8ResponseHandler.INSTANCE);
+    if (StringUtils.isEmpty(responseContent)) {
+      throw new WxErrorException(String.format("上传失败,服务器响应空 url:%s param:%s", uri, param));
+    }
+    WxError error = WxError.fromJson(responseContent, wxType);
+    if (error.getErrorCode() != 0) {
+      throw new WxErrorException(error);
     }
+    return responseContent;
   }
 
   /**
diff --git a/weixin-java-open/src/main/java/me/chanjar/weixin/open/executor/CommonUploadMultiRequestExecutorHttpComponentsImpl.java b/weixin-java-open/src/main/java/me/chanjar/weixin/open/executor/CommonUploadMultiRequestExecutorHttpComponentsImpl.java
new file mode 100644
index 0000000000..98bf2e7541
--- /dev/null
+++ b/weixin-java-open/src/main/java/me/chanjar/weixin/open/executor/CommonUploadMultiRequestExecutorHttpComponentsImpl.java
@@ -0,0 +1,89 @@
+package me.chanjar.weixin.open.executor;
+
+import lombok.Getter;
+import me.chanjar.weixin.common.bean.CommonUploadData;
+import me.chanjar.weixin.common.bean.CommonUploadParam;
+import me.chanjar.weixin.common.enums.WxType;
+import me.chanjar.weixin.common.error.WxError;
+import me.chanjar.weixin.common.error.WxErrorException;
+import me.chanjar.weixin.common.util.http.RequestHttp;
+import me.chanjar.weixin.common.util.http.hc.Utf8ResponseHandler;
+import me.chanjar.weixin.open.bean.CommonUploadMultiParam;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.hc.client5.http.classic.methods.HttpPost;
+import org.apache.hc.client5.http.config.RequestConfig;
+import org.apache.hc.client5.http.entity.mime.HttpMultipartMode;
+import org.apache.hc.client5.http.entity.mime.InputStreamBody;
+import org.apache.hc.client5.http.entity.mime.MultipartEntityBuilder;
+import org.apache.hc.client5.http.entity.mime.StringBody;
+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.springframework.util.CollectionUtils;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+
+/**
+ * @author altusea
+ */
+public class CommonUploadMultiRequestExecutorHttpComponentsImpl extends CommonUploadMultiRequestExecutor {
+
+  public CommonUploadMultiRequestExecutorHttpComponentsImpl(RequestHttp requestHttp) {
+    super(requestHttp);
+  }
+
+  @Override
+  public String execute(String uri, CommonUploadMultiParam param, WxType wxType) throws WxErrorException, IOException {
+    HttpPost httpPost = new HttpPost(uri);
+    if (requestHttp.getRequestHttpProxy() != null) {
+      RequestConfig config = RequestConfig.custom().setProxy(requestHttp.getRequestHttpProxy()).build();
+      httpPost.setConfig(config);
+    }
+    if (param != null) {
+      MultipartEntityBuilder entity = MultipartEntityBuilder.create();
+
+      List normalParams = param.getNormalParams();
+      if (!CollectionUtils.isEmpty(normalParams)) {
+        for (CommonUploadMultiParam.NormalParam normalParam : normalParams) {
+          entity.addPart(normalParam.getName(), new StringBody(normalParam.getValue(), ContentType.create("multipart/form-data", StandardCharsets.UTF_8)));
+        }
+      }
+
+      CommonUploadParam uploadParam = param.getUploadParam();
+      if (uploadParam != null) {
+        CommonUploadData data = uploadParam.getData();
+        InnerStreamBody part = new InnerStreamBody(data.getInputStream(), ContentType.DEFAULT_BINARY, data.getFileName(), data.getLength());
+        entity.addPart(uploadParam.getName(), part)
+          .setMode(HttpMultipartMode.EXTENDED);
+      }
+
+      httpPost.setEntity(entity.build());
+    }
+    String responseContent = requestHttp.getRequestHttpClient().execute(httpPost, Utf8ResponseHandler.INSTANCE);
+    if (StringUtils.isEmpty(responseContent)) {
+      throw new WxErrorException(String.format("上传失败,服务器响应空 url:%s param:%s", uri, param));
+    }
+    WxError error = WxError.fromJson(responseContent, wxType);
+    if (error.getErrorCode() != 0) {
+      throw new WxErrorException(error);
+    }
+    return responseContent;
+  }
+
+  /**
+   * 内部流 请求体
+   */
+  @Getter
+  public static class InnerStreamBody extends InputStreamBody {
+
+    private final long contentLength;
+
+    public InnerStreamBody(final InputStream in, final ContentType contentType, final String filename, long contentLength) {
+      super(in, contentType, filename);
+      this.contentLength = contentLength;
+    }
+  }
+}
diff --git a/weixin-java-open/src/main/java/me/chanjar/weixin/open/executor/CommonUploadMultiRequestExecutorJoddHttpImpl.java b/weixin-java-open/src/main/java/me/chanjar/weixin/open/executor/CommonUploadMultiRequestExecutorJoddHttpImpl.java
index 1650c16740..8b2e801e51 100644
--- a/weixin-java-open/src/main/java/me/chanjar/weixin/open/executor/CommonUploadMultiRequestExecutorJoddHttpImpl.java
+++ b/weixin-java-open/src/main/java/me/chanjar/weixin/open/executor/CommonUploadMultiRequestExecutorJoddHttpImpl.java
@@ -9,13 +9,12 @@
 import lombok.Getter;
 import lombok.SneakyThrows;
 import me.chanjar.weixin.common.bean.CommonUploadData;
-import me.chanjar.weixin.open.bean.CommonUploadMultiParam;
 import me.chanjar.weixin.common.bean.CommonUploadParam;
 import me.chanjar.weixin.common.enums.WxType;
 import me.chanjar.weixin.common.error.WxError;
 import me.chanjar.weixin.common.error.WxErrorException;
 import me.chanjar.weixin.common.util.http.RequestHttp;
-import org.apache.http.Consts;
+import me.chanjar.weixin.open.bean.CommonUploadMultiParam;
 import org.apache.http.entity.ContentType;
 import org.apache.http.entity.mime.content.StringBody;
 import org.springframework.util.CollectionUtils;
@@ -47,7 +46,7 @@ public String execute(String uri, CommonUploadMultiParam param, WxType wxType) t
     List normalParams = param.getNormalParams();
     if (!CollectionUtils.isEmpty(normalParams)) {
       for (CommonUploadMultiParam.NormalParam normalParam : normalParams) {
-        request.form(normalParam.getName(), new StringBody(normalParam.getValue(), ContentType.create("multipart/form-data", Consts.UTF_8)));
+        request.form(normalParam.getName(), new StringBody(normalParam.getValue(), ContentType.MULTIPART_FORM_DATA.withCharset(StandardCharsets.UTF_8)));
       }
     }
 
diff --git a/weixin-java-open/src/main/java/me/chanjar/weixin/open/executor/GenericUploadRequestExecutor.java b/weixin-java-open/src/main/java/me/chanjar/weixin/open/executor/GenericUploadRequestExecutor.java
index e2d43a96a8..950028176c 100644
--- a/weixin-java-open/src/main/java/me/chanjar/weixin/open/executor/GenericUploadRequestExecutor.java
+++ b/weixin-java-open/src/main/java/me/chanjar/weixin/open/executor/GenericUploadRequestExecutor.java
@@ -1,6 +1,5 @@
 package me.chanjar.weixin.open.executor;
 
-import com.google.common.io.Files;
 import jodd.http.HttpConnectionProvider;
 import jodd.http.HttpRequest;
 import jodd.http.HttpResponse;
@@ -24,7 +23,6 @@
 import org.apache.http.entity.mime.MultipartEntityBuilder;
 import org.apache.http.impl.client.CloseableHttpClient;
 
-import java.io.ByteArrayInputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.nio.charset.StandardCharsets;
@@ -133,11 +131,7 @@ public String execute(String uri, InputStream data, WxType wxType) throws WxErro
       bodyRequest.setEntity(entity);
       bodyRequest.setHeader("Content-Type", ContentType.MULTIPART_FORM_DATA.toString());
 
-      try (CloseableHttpResponse response = getRequestHttp().getRequestHttpClient().execute(bodyRequest)) {
-        return Utf8ResponseHandler.INSTANCE.handleResponse(response);
-      } finally {
-        bodyRequest.releaseConnection();
-      }
+      return getRequestHttp().getRequestHttpClient().execute(bodyRequest, Utf8ResponseHandler.INSTANCE);
     }
   }
 
diff --git a/weixin-java-open/src/main/java/me/chanjar/weixin/open/executor/MaQrCodeApacheHttpRequestExecutor.java b/weixin-java-open/src/main/java/me/chanjar/weixin/open/executor/MaQrCodeApacheHttpRequestExecutor.java
index c95748f8a1..fa048df9a3 100644
--- a/weixin-java-open/src/main/java/me/chanjar/weixin/open/executor/MaQrCodeApacheHttpRequestExecutor.java
+++ b/weixin-java-open/src/main/java/me/chanjar/weixin/open/executor/MaQrCodeApacheHttpRequestExecutor.java
@@ -28,7 +28,7 @@
  * created on  2018-09-13
  */
 public class MaQrCodeApacheHttpRequestExecutor extends MaQrCodeRequestExecutor {
-  public MaQrCodeApacheHttpRequestExecutor(RequestHttp requestHttp) {
+  public MaQrCodeApacheHttpRequestExecutor(RequestHttp requestHttp) {
     super(requestHttp);
   }
 
@@ -61,8 +61,6 @@ public File execute(String uri, WxMaQrcodeParam qrcodeParam, WxType wxType) thro
         }
       }
       return FileUtils.createTmpFile(inputStream, UUID.randomUUID().toString(), "jpg");
-    } finally {
-      httpGet.releaseConnection();
     }
   }
 }
diff --git a/weixin-java-open/src/main/java/me/chanjar/weixin/open/executor/MaQrCodeHttpComponentsRequestExecutor.java b/weixin-java-open/src/main/java/me/chanjar/weixin/open/executor/MaQrCodeHttpComponentsRequestExecutor.java
new file mode 100644
index 0000000000..e4682a7143
--- /dev/null
+++ b/weixin-java-open/src/main/java/me/chanjar/weixin/open/executor/MaQrCodeHttpComponentsRequestExecutor.java
@@ -0,0 +1,65 @@
+package me.chanjar.weixin.open.executor;
+
+import me.chanjar.weixin.common.enums.WxType;
+import me.chanjar.weixin.common.error.WxError;
+import me.chanjar.weixin.common.error.WxErrorException;
+import me.chanjar.weixin.common.util.fs.FileUtils;
+import me.chanjar.weixin.common.util.http.RequestHttp;
+import me.chanjar.weixin.common.util.http.hc.InputStreamResponseHandler;
+import me.chanjar.weixin.common.util.http.hc.Utf8ResponseHandler;
+import me.chanjar.weixin.open.bean.ma.WxMaQrcodeParam;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.hc.client5.http.ClientProtocolException;
+import org.apache.hc.client5.http.classic.methods.HttpGet;
+import org.apache.hc.client5.http.config.RequestConfig;
+import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
+import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse;
+import org.apache.hc.core5.http.ContentType;
+import org.apache.hc.core5.http.Header;
+import org.apache.hc.core5.http.HttpException;
+import org.apache.hc.core5.http.HttpHost;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URLEncoder;
+import java.util.UUID;
+
+public class MaQrCodeHttpComponentsRequestExecutor extends MaQrCodeRequestExecutor {
+  public MaQrCodeHttpComponentsRequestExecutor(RequestHttp requestHttp) {
+    super(requestHttp);
+  }
+
+  @Override
+  public File execute(String uri, WxMaQrcodeParam qrcodeParam, WxType wxType) throws WxErrorException, IOException {
+    if (qrcodeParam != null && StringUtils.isNotBlank(qrcodeParam.getPagePath())) {
+      if (uri.indexOf('?') == -1) {
+        uri += '?';
+      }
+      uri += uri.endsWith("?")
+        ? "path=" + URLEncoder.encode(qrcodeParam.getRequestPath(), "UTF-8")
+        : "&path=" + URLEncoder.encode(qrcodeParam.getRequestPath(), "UTF-8");
+    }
+
+    HttpGet httpGet = new HttpGet(uri);
+    if (requestHttp.getRequestHttpProxy() != null) {
+      RequestConfig config = RequestConfig.custom().setProxy(requestHttp.getRequestHttpProxy()).build();
+      httpGet.setConfig(config);
+    }
+
+    try (CloseableHttpResponse response = requestHttp.getRequestHttpClient().execute(httpGet);
+         InputStream inputStream = InputStreamResponseHandler.INSTANCE.handleResponse(response);) {
+      Header[] contentTypeHeader = response.getHeaders("Content-Type");
+      if (contentTypeHeader != null && contentTypeHeader.length > 0) {
+        // 出错
+        if (ContentType.TEXT_PLAIN.getMimeType().equals(ContentType.parse(contentTypeHeader[0].getValue()).getMimeType())) {
+          String responseContent = Utf8ResponseHandler.INSTANCE.handleResponse(response);
+          throw new WxErrorException(WxError.fromJson(responseContent, WxType.MiniApp));
+        }
+      }
+      return FileUtils.createTmpFile(inputStream, UUID.randomUUID().toString(), "jpg");
+    } catch (HttpException httpException) {
+      throw new ClientProtocolException(httpException.getMessage(), httpException);
+    }
+  }
+}
diff --git a/weixin-java-open/src/main/java/me/chanjar/weixin/open/executor/MaQrCodeJoddHttpRequestExecutor.java b/weixin-java-open/src/main/java/me/chanjar/weixin/open/executor/MaQrCodeJoddHttpRequestExecutor.java
index 5eddf762b1..415a5c7b24 100644
--- a/weixin-java-open/src/main/java/me/chanjar/weixin/open/executor/MaQrCodeJoddHttpRequestExecutor.java
+++ b/weixin-java-open/src/main/java/me/chanjar/weixin/open/executor/MaQrCodeJoddHttpRequestExecutor.java
@@ -5,7 +5,6 @@
 import jodd.http.HttpResponse;
 import jodd.http.ProxyInfo;
 import jodd.net.MimeTypes;
-import jodd.util.StringPool;
 import me.chanjar.weixin.common.enums.WxType;
 import me.chanjar.weixin.common.error.WxError;
 import me.chanjar.weixin.common.error.WxErrorException;
@@ -27,7 +26,7 @@
  * created on  2018-09-13
  */
 public class MaQrCodeJoddHttpRequestExecutor extends MaQrCodeRequestExecutor {
-  public MaQrCodeJoddHttpRequestExecutor(RequestHttp requestHttp) {
+  public MaQrCodeJoddHttpRequestExecutor(RequestHttp requestHttp) {
     super(requestHttp);
   }
 
diff --git a/weixin-java-open/src/main/java/me/chanjar/weixin/open/executor/MaQrCodeOkhttpRequestExecutor.java b/weixin-java-open/src/main/java/me/chanjar/weixin/open/executor/MaQrCodeOkhttpRequestExecutor.java
index 77816949d6..e30ceeb973 100644
--- a/weixin-java-open/src/main/java/me/chanjar/weixin/open/executor/MaQrCodeOkhttpRequestExecutor.java
+++ b/weixin-java-open/src/main/java/me/chanjar/weixin/open/executor/MaQrCodeOkhttpRequestExecutor.java
@@ -23,7 +23,7 @@
  * created on  2018-09-13
  */
 public class MaQrCodeOkhttpRequestExecutor extends MaQrCodeRequestExecutor {
-  public MaQrCodeOkhttpRequestExecutor(RequestHttp requestHttp) {
+  public MaQrCodeOkhttpRequestExecutor(RequestHttp requestHttp) {
     super(requestHttp);
   }
 
diff --git a/weixin-java-open/src/main/java/me/chanjar/weixin/open/executor/MaQrCodeRequestExecutor.java b/weixin-java-open/src/main/java/me/chanjar/weixin/open/executor/MaQrCodeRequestExecutor.java
index d37c31d05e..000845b716 100644
--- a/weixin-java-open/src/main/java/me/chanjar/weixin/open/executor/MaQrCodeRequestExecutor.java
+++ b/weixin-java-open/src/main/java/me/chanjar/weixin/open/executor/MaQrCodeRequestExecutor.java
@@ -3,13 +3,16 @@
 import java.io.File;
 import java.io.IOException;
 
+import jodd.http.HttpConnectionProvider;
+import jodd.http.ProxyInfo;
 import me.chanjar.weixin.common.enums.WxType;
-import me.chanjar.weixin.common.error.WxError;
 import me.chanjar.weixin.common.error.WxErrorException;
 import me.chanjar.weixin.common.util.http.RequestExecutor;
 import me.chanjar.weixin.common.util.http.RequestHttp;
 import me.chanjar.weixin.common.util.http.ResponseHandler;
+import me.chanjar.weixin.common.util.http.okhttp.OkHttpProxyInfo;
 import me.chanjar.weixin.open.bean.ma.WxMaQrcodeParam;
+import okhttp3.OkHttpClient;
 
 /**
  * 获得小程序体验QrCode图片 请求执行器.
@@ -20,7 +23,7 @@
 public abstract class MaQrCodeRequestExecutor implements RequestExecutor {
   protected RequestHttp requestHttp;
 
-  public MaQrCodeRequestExecutor(RequestHttp requestHttp) {
+  public MaQrCodeRequestExecutor(RequestHttp requestHttp) {
     this.requestHttp = requestHttp;
   }
 
@@ -29,16 +32,21 @@ public void execute(String uri, WxMaQrcodeParam data, ResponseHandler hand
     handler.handle(this.execute(uri, data, wxType));
   }
 
-  public static RequestExecutor create(RequestHttp requestHttp) throws WxErrorException {
+  @SuppressWarnings("unchecked")
+  public static RequestExecutor create(RequestHttp requestHttp) {
     switch (requestHttp.getRequestType()) {
       case APACHE_HTTP:
-        return new MaQrCodeApacheHttpRequestExecutor(requestHttp);
+        return new MaQrCodeApacheHttpRequestExecutor(
+          (RequestHttp) requestHttp);
       case JODD_HTTP:
-        return new MaQrCodeJoddHttpRequestExecutor(requestHttp);
+        return new MaQrCodeJoddHttpRequestExecutor((RequestHttp) requestHttp);
       case OK_HTTP:
-        return new MaQrCodeOkhttpRequestExecutor(requestHttp);
+        return new MaQrCodeOkhttpRequestExecutor((RequestHttp) requestHttp);
+      case HTTP_COMPONENTS:
+        return new MaQrCodeHttpComponentsRequestExecutor(
+          (RequestHttp) requestHttp);
       default:
-        throw new WxErrorException("不支持的http框架");
+        throw new IllegalArgumentException("不支持的http执行器类型:" + requestHttp.getRequestType());
     }
   }
 
diff --git a/weixin-java-open/src/main/java/me/chanjar/weixin/open/util/WxOpenCryptUtil.java b/weixin-java-open/src/main/java/me/chanjar/weixin/open/util/WxOpenCryptUtil.java
index e6c8ce992d..ee1ada8dbb 100644
--- a/weixin-java-open/src/main/java/me/chanjar/weixin/open/util/WxOpenCryptUtil.java
+++ b/weixin-java-open/src/main/java/me/chanjar/weixin/open/util/WxOpenCryptUtil.java
@@ -1,7 +1,5 @@
 package me.chanjar.weixin.open.util;
 
-import com.google.common.base.CharMatcher;
-import com.google.common.io.BaseEncoding;
 import me.chanjar.weixin.open.api.WxOpenConfigStorage;
 import org.apache.commons.lang3.StringUtils;
 
diff --git a/weixin-java-open/src/main/java/me/chanjar/weixin/open/util/json/WxOpenAuthorizerListResultGsonAdapter.java b/weixin-java-open/src/main/java/me/chanjar/weixin/open/util/json/WxOpenAuthorizerListResultGsonAdapter.java
index 2128839c23..cda1101793 100644
--- a/weixin-java-open/src/main/java/me/chanjar/weixin/open/util/json/WxOpenAuthorizerListResultGsonAdapter.java
+++ b/weixin-java-open/src/main/java/me/chanjar/weixin/open/util/json/WxOpenAuthorizerListResultGsonAdapter.java
@@ -24,10 +24,9 @@ public WxOpenAuthorizerListResult deserialize(JsonElement jsonElement, Type type
     wxOpenAuthorizerListResult.setTotalCount(GsonHelper.getInteger(jsonObject, "total_count").intValue());
 
     List> list = new ArrayList<>();
-    Iterator jsonElementIterator = jsonObject.getAsJsonArray("list").iterator();
 
-    while (jsonElementIterator.hasNext()) {
-      JsonObject authorizer = jsonElementIterator.next().getAsJsonObject();
+    for (JsonElement element : jsonObject.getAsJsonArray("list")) {
+      JsonObject authorizer = element.getAsJsonObject();
       Map authorizerMap = new HashMap<>(10);
 
       authorizerMap.put(AUTHORIZER_APPID, GsonHelper.getString(authorizer, AUTHORIZER_APPID));
diff --git a/weixin-java-pay/CONNECTION_POOL.md b/weixin-java-pay/CONNECTION_POOL.md
new file mode 100644
index 0000000000..2b2569adcb
--- /dev/null
+++ b/weixin-java-pay/CONNECTION_POOL.md
@@ -0,0 +1,67 @@
+# HTTP连接池功能说明
+
+## 概述
+
+`WxPayServiceApacheHttpImpl` 现在支持HTTP连接池功能,可以显著提高高并发场景下的性能表现。
+
+## 主要改进
+
+1. **连接复用**: 不再为每个请求创建新的HttpClient实例,而是复用连接池中的连接
+2. **性能提升**: 减少连接建立和销毁的开销,提高吞吐量
+3. **资源优化**: 合理控制并发连接数,避免资源浪费
+4. **SSL支持**: 同时支持普通HTTP和SSL连接的连接池
+
+## 配置说明
+
+### 默认配置
+```java
+WxPayConfig config = new WxPayConfig();
+// 默认配置:
+// maxConnTotal = 20        (最大连接数)
+// maxConnPerRoute = 10     (每个路由最大连接数)
+```
+
+### 自定义配置
+```java
+WxPayConfig config = new WxPayConfig();
+config.setMaxConnTotal(50);      // 设置最大连接数
+config.setMaxConnPerRoute(20);   // 设置每个路由最大连接数
+```
+
+## 使用方式
+
+连接池功能是自动启用的,无需额外配置:
+
+```java
+// 1. 配置微信支付
+WxPayConfig config = new WxPayConfig();
+config.setAppId("your-app-id");
+config.setMchId("your-mch-id");
+config.setMchKey("your-mch-key");
+
+// 2. 创建支付服务(连接池自动启用)
+WxPayServiceApacheHttpImpl payService = new WxPayServiceApacheHttpImpl();
+payService.setConfig(config);
+
+// 3. 正常使用,所有HTTP请求都会使用连接池
+WxPayUnifiedOrderResult result = payService.unifiedOrder(request);
+```
+
+## 向后兼容性
+
+- 此功能完全向后兼容,现有代码无需修改
+- 如果不设置连接池参数,将使用默认配置
+- 支持原有的HttpClientBuilderCustomizer自定义功能
+
+## 注意事项
+
+1. 连接池中的HttpClient实例会被复用,不要手动关闭
+2. SSL连接和普通连接使用不同的连接池
+3. 连接池参数建议根据实际并发量调整
+4. 代理配置仍然正常工作
+
+## 性能建议
+
+- 对于高并发应用,建议适当增加`maxConnTotal`和`maxConnPerRoute`
+- 监控连接池使用情况,避免连接数不足导致的阻塞
+- 在容器环境中,注意连接池配置与容器资源限制的平衡
\ No newline at end of file
diff --git a/weixin-java-pay/OVERSEAS_PAY.md b/weixin-java-pay/OVERSEAS_PAY.md
new file mode 100644
index 0000000000..b9da9814f9
--- /dev/null
+++ b/weixin-java-pay/OVERSEAS_PAY.md
@@ -0,0 +1,120 @@
+# 境外微信支付(Overseas WeChat Pay)支持
+
+本次更新添加了境外微信支付的支持,解决了 [Issue #3618](https://github.com/binarywang/WxJava/issues/3618) 中提到的问题。
+
+## 问题背景
+
+境外微信支付需要使用新的API接口地址和额外的参数:
+- 使用不同的基础URL: `https://apihk.mch.weixin.qq.com` 
+- 需要额外的参数: `trade_type` 和 `merchant_category_code`
+- 使用不同的API端点: `/global/v3/transactions/*`
+
+## 新增功能
+
+### 1. GlobalTradeTypeEnum
+新的枚举类,定义了境外支付的交易类型和对应的API端点:
+- `APP`: `/global/v3/transactions/app`
+- `JSAPI`: `/global/v3/transactions/jsapi`
+- `NATIVE`: `/global/v3/transactions/native`
+- `H5`: `/global/v3/transactions/h5`
+
+### 2. WxPayUnifiedOrderV3GlobalRequest
+扩展的请求类,包含境外支付必需的额外字段:
+- `trade_type`: 交易类型 (JSAPI, APP, NATIVE, H5)
+- `merchant_category_code`: 商户类目代码(境外商户必填)
+
+### 3. 新的服务方法
+- `createOrderV3Global()`: 创建境外支付订单
+- `unifiedOrderV3Global()`: 境外统一下单接口
+
+## 使用示例
+
+### JSAPI支付示例
+```java
+// 创建境外支付请求
+WxPayUnifiedOrderV3GlobalRequest request = new WxPayUnifiedOrderV3GlobalRequest();
+request.setOutTradeNo(RandomUtils.getRandomStr());
+request.setDescription("境外商品购买");
+request.setNotifyUrl("https://your-domain.com/notify");
+
+// 设置金额
+WxPayUnifiedOrderV3GlobalRequest.Amount amount = new WxPayUnifiedOrderV3GlobalRequest.Amount();
+amount.setCurrency(WxPayConstants.CurrencyType.CNY);
+amount.setTotal(100); // 1元,单位为分
+request.setAmount(amount);
+
+// 设置支付者
+WxPayUnifiedOrderV3GlobalRequest.Payer payer = new WxPayUnifiedOrderV3GlobalRequest.Payer();
+payer.setOpenid("用户的openid");
+request.setPayer(payer);
+
+// 设置境外支付必需的参数
+request.setTradeType("JSAPI");
+request.setMerchantCategoryCode("5812"); // 商户类目代码
+
+// 调用境外支付接口
+WxPayUnifiedOrderV3Result.JsapiResult result = payService.createOrderV3Global(
+    GlobalTradeTypeEnum.JSAPI, 
+    request
+);
+```
+
+### APP支付示例
+```java
+WxPayUnifiedOrderV3GlobalRequest request = new WxPayUnifiedOrderV3GlobalRequest();
+// ... 设置基础信息 ...
+
+request.setTradeType("APP");
+request.setMerchantCategoryCode("5812");
+request.setPayer(new WxPayUnifiedOrderV3GlobalRequest.Payer()); // APP支付不需要openid
+
+WxPayUnifiedOrderV3Result.AppResult result = payService.createOrderV3Global(
+    GlobalTradeTypeEnum.APP, 
+    request
+);
+```
+
+### NATIVE支付示例
+```java
+WxPayUnifiedOrderV3GlobalRequest request = new WxPayUnifiedOrderV3GlobalRequest();
+// ... 设置基础信息 ...
+
+request.setTradeType("NATIVE");
+request.setMerchantCategoryCode("5812");
+request.setPayer(new WxPayUnifiedOrderV3GlobalRequest.Payer());
+
+String codeUrl = payService.createOrderV3Global(
+    GlobalTradeTypeEnum.NATIVE, 
+    request
+);
+```
+
+## 配置说明
+
+境外支付使用相同的 `WxPayConfig` 配置,无需特殊设置:
+
+```java
+WxPayConfig config = new WxPayConfig();
+config.setAppId("你的AppId");
+config.setMchId("你的境外商户号");
+config.setMchKey("你的商户密钥");
+config.setNotifyUrl("https://your-domain.com/notify");
+
+// V3相关配置
+config.setPrivateKeyPath("你的私钥文件路径");
+config.setCertSerialNo("你的商户证书序列号");
+config.setApiV3Key("你的APIv3密钥");
+```
+
+**注意**: 境外支付会自动使用 `https://apihk.mch.weixin.qq.com` 作为基础URL,无需手动设置。
+
+## 兼容性
+
+- 完全向后兼容,不影响现有的国内支付功能
+- 使用相同的配置类和结果类
+- 遵循现有的代码风格和架构模式
+
+## 参考文档
+
+- [境外微信支付文档](https://pay.weixin.qq.com/doc/global/v3/zh/4013014223)
+- [原始Issue #3618](https://github.com/binarywang/WxJava/issues/3618)
\ No newline at end of file
diff --git a/weixin-java-pay/pom.xml b/weixin-java-pay/pom.xml
index 6eeb06624e..f2d03845b0 100644
--- a/weixin-java-pay/pom.xml
+++ b/weixin-java-pay/pom.xml
@@ -5,7 +5,7 @@
   
     com.github.binarywang
     wx-java
-    4.7.2.B
+    4.7.8.B
   
   4.0.0
 
@@ -34,6 +34,11 @@
       jodd-util
       6.1.0
     
+    
+      org.apache.httpcomponents.client5
+      httpclient5
+      provided
+    
 
     
       org.apache.commons
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/applyconfirm/ApplySubjectConfirmMerchantStateQueryResult.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/applyconfirm/ApplySubjectConfirmMerchantStateQueryResult.java
index c155e1e6cd..1b5fc6eb7d 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/applyconfirm/ApplySubjectConfirmMerchantStateQueryResult.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/applyconfirm/ApplySubjectConfirmMerchantStateQueryResult.java
@@ -1,7 +1,6 @@
 package com.github.binarywang.wxpay.bean.applyconfirm;
 
 import com.github.binarywang.wxpay.bean.applyconfirm.enums.AuthorizeStateEnum;
-import com.github.binarywang.wxpay.bean.applyment.enums.ApplymentStateEnum;
 import com.google.gson.annotations.SerializedName;
 import lombok.AllArgsConstructor;
 import lombok.Builder;
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/bank/BankAccountResult.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/bank/BankAccountResult.java
index 5f67a2badf..44e2acb8cc 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/bank/BankAccountResult.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/bank/BankAccountResult.java
@@ -2,8 +2,6 @@
 
 import com.google.gson.annotations.SerializedName;
 import lombok.Data;
-import lombok.Getter;
-import lombok.Setter;
 
 import java.io.Serializable;
 import java.util.List;
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/bank/BankInfo.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/bank/BankInfo.java
index adafc9b280..b9ea2c3348 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/bank/BankInfo.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/bank/BankInfo.java
@@ -2,8 +2,6 @@
 
 import com.google.gson.annotations.SerializedName;
 import lombok.Data;
-import lombok.Getter;
-import lombok.Setter;
 
 import java.io.Serializable;
 
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/bank/PageLink.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/bank/PageLink.java
index 419cdc3c94..d8431f6709 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/bank/PageLink.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/bank/PageLink.java
@@ -2,11 +2,8 @@
 
 import com.google.gson.annotations.SerializedName;
 import lombok.Data;
-import lombok.Getter;
-import lombok.Setter;
 
 import java.io.Serializable;
-import java.util.List;
 
 /**
  * 支行列表
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/complaint/ComplaintNotifyUrlResult.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/complaint/ComplaintNotifyUrlResult.java
index bc7e066d31..1b40affe94 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/complaint/ComplaintNotifyUrlResult.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/complaint/ComplaintNotifyUrlResult.java
@@ -1,13 +1,10 @@
 package com.github.binarywang.wxpay.bean.complaint;
 
 
-import com.github.binarywang.wxpay.bean.media.MarketingImageUploadResult;
 import com.google.gson.annotations.SerializedName;
 import lombok.Data;
-import me.chanjar.weixin.common.util.json.WxGsonBuilder;
 
 import java.io.Serializable;
-import java.util.List;
 
 /**
  * 微信消费者投诉2.0
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/customs/VerifyCertificateRequest.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/customs/VerifyCertificateRequest.java
index 767a4ce8d8..b589cb2277 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/customs/VerifyCertificateRequest.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/customs/VerifyCertificateRequest.java
@@ -1,6 +1,5 @@
 package com.github.binarywang.wxpay.bean.customs;
 
-import com.github.binarywang.wxpay.v3.SpecEncrypt;
 import com.google.gson.annotations.SerializedName;
 import lombok.Data;
 
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/RefundsResult.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/RefundsResult.java
index 9d66ce8c38..bbd3eabb2d 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/RefundsResult.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/RefundsResult.java
@@ -10,7 +10,6 @@
 import lombok.NoArgsConstructor;
 
 import java.io.Serializable;
-import java.util.Date;
 
 /**
  * 退款结果
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/ReturnAdvanceResult.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/ReturnAdvanceResult.java
index e47bd5ca3d..442f0f15b0 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/ReturnAdvanceResult.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/ReturnAdvanceResult.java
@@ -5,7 +5,6 @@
 import lombok.NoArgsConstructor;
 
 import java.io.Serializable;
-import java.util.List;
 
 
 /**
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/ReturnOrdersRequest.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/ReturnOrdersRequest.java
index e3e7c98e34..b1661209a6 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/ReturnOrdersRequest.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/ReturnOrdersRequest.java
@@ -118,4 +118,18 @@ public class ReturnOrdersRequest implements Serializable {
    */
   @SerializedName(value = "description")
   private String description;
+
+  /**
+   * 
+   * 字段名:微信订单号
+   * 变量名:transaction_id
+   * 是否必填:否
+   * 类型:string(32)
+   * 描述:
+   *  微信支付订单号,大于6个月的订单,必填
+   *  示例值:4208450740201411110007820472
+   * 
+ */ + @SerializedName(value = "transaction_id") + private String transactionId; } diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/ReturnOrdersResult.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/ReturnOrdersResult.java index b136844f86..0b0d093e3c 100644 --- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/ReturnOrdersResult.java +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/ReturnOrdersResult.java @@ -4,7 +4,6 @@ import lombok.*; import java.io.Serializable; -import java.util.Date; /** diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/BusiFavorCouponCodeRequest.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/BusiFavorCouponCodeRequest.java index fa6ca553e9..2ab481849e 100644 --- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/BusiFavorCouponCodeRequest.java +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/BusiFavorCouponCodeRequest.java @@ -18,7 +18,7 @@ @Data @NoArgsConstructor public class BusiFavorCouponCodeRequest implements Serializable { - public static final float serialVersionUID = 1L; + private static final long serialVersionUID = 1L; /** *
* 字段名:批次号
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/BusiFavorCouponCodeResult.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/BusiFavorCouponCodeResult.java
index ca45a091c4..bca9ea932e 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/BusiFavorCouponCodeResult.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/BusiFavorCouponCodeResult.java
@@ -18,7 +18,7 @@
 @Data
 @NoArgsConstructor
 public class BusiFavorCouponCodeResult implements Serializable {
-  public static final float serialVersionUID = 1L;
+  private static final long serialVersionUID = 1L;
 
   /**
    * 
* 字段名:批次号
@@ -130,8 +130,8 @@ public class BusiFavorCouponCodeResult implements Serializable {
 
   @Data
   @NoArgsConstructor
-  public static class FailCode {
-    public static final float serialVersionUID = 1L;
+  public static class FailCode implements Serializable {
+    private static final long serialVersionUID = 1L;
 
     /**
      * 
* 字段名:上传失败的券code
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/BusiFavorCouponsUrlRequest.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/BusiFavorCouponsUrlRequest.java
index 11319e56b4..8af44901e0 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/BusiFavorCouponsUrlRequest.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/BusiFavorCouponsUrlRequest.java
@@ -4,6 +4,8 @@
 import lombok.Data;
 import lombok.NoArgsConstructor;
 
+import java.io.Serializable;
+
 /**
  * H5发券请求对象
  * 
@@ -14,8 +16,8 @@
  */
 @Data
 @NoArgsConstructor
-public class BusiFavorCouponsUrlRequest {
-  public static final float serialVersionUID = 1L;
+public class BusiFavorCouponsUrlRequest implements Serializable {
+  private static final long serialVersionUID = 1L;
 
   /**
    * 
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/BusiFavorCouponsUseRequest.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/BusiFavorCouponsUseRequest.java
index ab8a8ba2a5..9d365054e9 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/BusiFavorCouponsUseRequest.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/BusiFavorCouponsUseRequest.java
@@ -4,6 +4,8 @@
 import lombok.Data;
 import lombok.NoArgsConstructor;
 
+import java.io.Serializable;
+
 /**
  * 核销用户券请求对象
  * 
@@ -14,8 +16,8 @@
  */
 @Data
 @NoArgsConstructor
-public class BusiFavorCouponsUseRequest {
-  public static final float serialVersionUID = 1L;
+public class BusiFavorCouponsUseRequest implements Serializable {
+  private static final long serialVersionUID = 1L;
 
   /**
    * 
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/BusiFavorQueryOneUserCouponsRequest.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/BusiFavorQueryOneUserCouponsRequest.java
index 3dad3fe5d1..0a53cd33d1 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/BusiFavorQueryOneUserCouponsRequest.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/BusiFavorQueryOneUserCouponsRequest.java
@@ -17,7 +17,7 @@
 @Data
 @NoArgsConstructor
 public class BusiFavorQueryOneUserCouponsRequest implements Serializable {
-  public static final float serialVersionUID = 1L;
+  private static final long serialVersionUID = 1L;
 
   /**
    * 
* 字段名:用户标识
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/BusiFavorQueryOneUserCouponsResult.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/BusiFavorQueryOneUserCouponsResult.java
index 6db7d303a9..566957eb51 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/BusiFavorQueryOneUserCouponsResult.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/BusiFavorQueryOneUserCouponsResult.java
@@ -20,7 +20,7 @@
 @Data
 @NoArgsConstructor
 public class BusiFavorQueryOneUserCouponsResult implements Serializable {
-  public static final float serialVersionUID = 1L;
+  private static final long serialVersionUID = 1L;
 
   /**
    * 
* 字段名:批次归属商户号
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/BusiFavorQueryUserCouponsRequest.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/BusiFavorQueryUserCouponsRequest.java
index 600a48c8de..0c417c425a 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/BusiFavorQueryUserCouponsRequest.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/BusiFavorQueryUserCouponsRequest.java
@@ -17,7 +17,7 @@
 @Data
 @NoArgsConstructor
 public class BusiFavorQueryUserCouponsRequest implements Serializable {
-  public static final float serialVersionUID = 1L;
+  private static final long serialVersionUID = 1L;
 
   /**
    * 
* 字段名:用户标识
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/BusiFavorQueryUserCouponsResult.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/BusiFavorQueryUserCouponsResult.java
index 9b5f57b040..c2906be27e 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/BusiFavorQueryUserCouponsResult.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/BusiFavorQueryUserCouponsResult.java
@@ -18,7 +18,7 @@
 @Data
 @NoArgsConstructor
 public class BusiFavorQueryUserCouponsResult implements Serializable {
-  public static final float serialVersionUID = 1L;
+  private static final long serialVersionUID = 1L;
 
   /**
    * 
* 字段名:+结果集
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/FavorCouponsGetResult.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/FavorCouponsGetResult.java
index f8f342de1c..6f1824f646 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/FavorCouponsGetResult.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/FavorCouponsGetResult.java
@@ -1,5 +1,6 @@
 package com.github.binarywang.wxpay.bean.marketing;
 
+import com.github.binarywang.wxpay.bean.result.BaseWxPayV3Result;
 import com.google.gson.annotations.SerializedName;
 import lombok.Data;
 import lombok.NoArgsConstructor;
@@ -13,7 +14,7 @@
  */
 @NoArgsConstructor
 @Data
-public class FavorCouponsGetResult implements Serializable {
+public class FavorCouponsGetResult extends BaseWxPayV3Result {
 
   private static final long serialVersionUID = 1L;
 
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/FavorStocksCreateRequest.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/FavorStocksCreateRequest.java
index 855edc8528..375dd308d8 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/FavorStocksCreateRequest.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/FavorStocksCreateRequest.java
@@ -1,6 +1,7 @@
 package com.github.binarywang.wxpay.bean.marketing;
 
 import com.github.binarywang.wxpay.bean.marketing.enums.BackgroundColorEnum;
+import com.github.binarywang.wxpay.bean.marketing.enums.JumpTargetEnum;
 import com.github.binarywang.wxpay.bean.marketing.enums.StockTypeEnum;
 import com.github.binarywang.wxpay.bean.marketing.enums.TradeTypeEnum;
 import com.google.gson.annotations.SerializedName;
@@ -392,6 +393,24 @@ public static class PatternInfo implements Serializable {
      */
     @SerializedName(value = "coupon_image")
     private String couponImage;
+
+    /**
+     * 卡包跳转目标
+     */
+    @SerializedName("jump_target")
+    private JumpTargetEnum jumpTarget;
+
+    /**
+     * 小程序appid
+     */
+    @SerializedName("mini_program_appid")
+    private String miniProgramAppid;
+
+    /**
+     * 小程序path
+     */
+    @SerializedName("mini_program_path")
+    private String miniProgramPath;
   }
 
   @Data
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/FavorStocksCreateResult.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/FavorStocksCreateResult.java
index 74ac6fd205..381056b5a9 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/FavorStocksCreateResult.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/FavorStocksCreateResult.java
@@ -1,5 +1,6 @@
 package com.github.binarywang.wxpay.bean.marketing;
 
+import com.github.binarywang.wxpay.bean.result.BaseWxPayV3Result;
 import com.google.gson.annotations.SerializedName;
 import lombok.Data;
 import lombok.NoArgsConstructor;
@@ -13,7 +14,7 @@
  */
 @NoArgsConstructor
 @Data
-public class FavorStocksCreateResult implements Serializable {
+public class FavorStocksCreateResult extends BaseWxPayV3Result {
 
   private static final long serialVersionUID = 1L;
 
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/FavorStocksGetResult.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/FavorStocksGetResult.java
index 294f273def..591afa7e51 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/FavorStocksGetResult.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/FavorStocksGetResult.java
@@ -1,5 +1,6 @@
 package com.github.binarywang.wxpay.bean.marketing;
 
+import com.github.binarywang.wxpay.bean.result.BaseWxPayV3Result;
 import com.google.gson.annotations.SerializedName;
 import lombok.Data;
 import lombok.NoArgsConstructor;
@@ -14,7 +15,7 @@
  */
 @NoArgsConstructor
 @Data
-public class FavorStocksGetResult implements Serializable {
+public class FavorStocksGetResult extends BaseWxPayV3Result {
 
   private static final long serialVersionUID = 1L;
 
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/busifavor/AvailableWeek.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/busifavor/AvailableWeek.java
index 2718b32770..487291a739 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/busifavor/AvailableWeek.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/busifavor/AvailableWeek.java
@@ -17,7 +17,7 @@
 @Data
 @NoArgsConstructor
 public class AvailableWeek implements Serializable {
-  public static final float serialVersionUID = 1L;
+  private static final long serialVersionUID = 1L;
 
   /**
    * 
@@ -46,12 +46,12 @@ public class AvailableWeek implements Serializable {
    * 
*/ @SerializedName(value = "available_day_time") - private AvailableDayTime availableDayTime; + private AvailableDayTimeItem[] availableDayTime; @Data @NoArgsConstructor - public static class AvailableDayTime implements Serializable { - public static final float serialVersionUID = 1L; + public static class AvailableDayTimeItem implements Serializable { + private static final long serialVersionUID = 1L; /** *
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/busifavor/CouponAvailableTime.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/busifavor/CouponAvailableTime.java
index 31833c1188..f41692c068 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/busifavor/CouponAvailableTime.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/busifavor/CouponAvailableTime.java
@@ -18,7 +18,7 @@
 @Data
 @NoArgsConstructor
 public class CouponAvailableTime implements Serializable {
-  public static final float serialVersionUID = 1L;
+  private static final long serialVersionUID = 1L;
 
   /**
    * 
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/busifavor/IrregularyAvaliableTime.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/busifavor/IrregularyAvaliableTime.java
index 4ddd196e56..0b11010493 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/busifavor/IrregularyAvaliableTime.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/busifavor/IrregularyAvaliableTime.java
@@ -18,7 +18,7 @@
 @NoArgsConstructor
 public class IrregularyAvaliableTime implements Serializable {
 
-  public static final float serialVersionUID = 1L;
+  private static final long serialVersionUID = 1L;
 
   /**
    * 
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/enums/BackgroundColorEnum.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/enums/BackgroundColorEnum.java
index d9ba753346..b37765f8f2 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/enums/BackgroundColorEnum.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/enums/BackgroundColorEnum.java
@@ -52,6 +52,16 @@ public enum BackgroundColorEnum {
    */
   COLOR080("COLOR080", "#EE903C"),
 
+  /**
+   * 颜色 #F08500
+   */
+  COLOR081("COLOR081", "#F08500"),
+
+  /**
+   * 颜色 #A9D92D
+   */
+  COLOR082("COLOR082", "#A9D92D"),
+
   /**
    * 颜色 #DD6549
    */
@@ -61,8 +71,17 @@ public enum BackgroundColorEnum {
    * 颜色 #CC463D
    */
   COLOR100("COLOR100", "#CC463D"),
-  ;
 
+  /**
+   * 颜色 #CF3E36
+   */
+  COLOR101("COLOR101", "#CF3E36"),
+
+  /**
+   * 颜色 #5E6671
+   */
+  COLOR102("COLOR102", "#5E6671"),
+  ;
   /**
    * 色值
    */
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/enums/JumpTargetEnum.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/enums/JumpTargetEnum.java
new file mode 100644
index 0000000000..dce0b34556
--- /dev/null
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/enums/JumpTargetEnum.java
@@ -0,0 +1,34 @@
+package com.github.binarywang.wxpay.bean.marketing.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * 卡包跳转目标
+ *
+ * @author wangerwei
+ */
+@Getter
+@AllArgsConstructor
+public enum JumpTargetEnum {
+
+  /**
+   * PAYMENT_CODE:点击“立即使用”跳转至微信支付付款码
+   */
+  PAYMENT_CODE("PAYMENT_CODE"),
+
+  /**
+   * MINI_PROGRAM:点击“立即使用”跳转至配置的商家小程序(需要指定小程序appid和path)
+   */
+  MINI_PROGRAM("MINI_PROGRAM"),
+
+  /**
+   * DEFAULT_PAGE:点击“立即使用”跳转至默认页面
+   */
+  DEFAULT_PAGE("DEFAULT_PAGE");
+
+  /**
+   * 批次类型
+   */
+  private final String value;
+}
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/transfer/BatchDetailsRequest.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/transfer/BatchDetailsRequest.java
index 9aa51ce742..bbb4e93ab4 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/transfer/BatchDetailsRequest.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/transfer/BatchDetailsRequest.java
@@ -23,7 +23,7 @@
 @Data
 @NoArgsConstructor
 public class BatchDetailsRequest implements Serializable {
-  public static final float serialVersionUID = 1L;
+  private static final long serialVersionUID = 1L;
   /**
    * 
    * 字段名:微信支付批次单号
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/transfer/BatchDetailsResult.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/transfer/BatchDetailsResult.java
index 437def08f2..4ca7958ed5 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/transfer/BatchDetailsResult.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/transfer/BatchDetailsResult.java
@@ -7,7 +7,6 @@
 import me.chanjar.weixin.common.util.json.WxGsonBuilder;
 
 import java.io.Serializable;
-import java.util.Date;
 
 /**
  * 
@@ -26,7 +25,7 @@
 @Data
 @NoArgsConstructor
 public class BatchDetailsResult implements Serializable {
-  public static final float serialVersionUID = 1L;
+  private static final long serialVersionUID = 1L;
 
   @Override
   public String toString() {
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/transfer/BatchNumberRequest.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/transfer/BatchNumberRequest.java
index 9f53843d66..127c38cdc6 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/transfer/BatchNumberRequest.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/transfer/BatchNumberRequest.java
@@ -18,7 +18,7 @@
 @Data
 @NoArgsConstructor
 public class BatchNumberRequest implements Serializable {
-  public static final float serialVersionUID = 1L;
+  private static final long serialVersionUID = 1L;
 
   /**
    * 
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/transfer/BatchNumberResult.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/transfer/BatchNumberResult.java
index 1defcca943..a59ccbc85f 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/transfer/BatchNumberResult.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/transfer/BatchNumberResult.java
@@ -20,7 +20,7 @@
 @Data
 @NoArgsConstructor
 public class BatchNumberResult implements Serializable {
-  public static final float serialVersionUID = 1L;
+  private static final long serialVersionUID = 1L;
 
   @Override
   public String toString() {
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/transfer/BillReceiptResult.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/transfer/BillReceiptResult.java
index ea83328308..fc0b97d7bb 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/transfer/BillReceiptResult.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/transfer/BillReceiptResult.java
@@ -20,7 +20,7 @@
 @Data
 @NoArgsConstructor
 public class BillReceiptResult implements Serializable {
-  public static final float serialVersionUID = 1L;
+  private static final long serialVersionUID = 1L;
 
   @Override
   public String toString() {
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/transfer/DownloadRequest.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/transfer/DownloadRequest.java
index 50ca1feac7..3f147abd00 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/transfer/DownloadRequest.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/transfer/DownloadRequest.java
@@ -21,7 +21,7 @@
 @Data
 @NoArgsConstructor
 public class DownloadRequest implements Serializable {
-  public static final float serialVersionUID = 1L;
+  private static final long serialVersionUID = 1L;
 
   /**
    * 
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/transfer/ElectronicReceiptsRequest.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/transfer/ElectronicReceiptsRequest.java
index 1f4d8134f4..cc419d3a4f 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/transfer/ElectronicReceiptsRequest.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/transfer/ElectronicReceiptsRequest.java
@@ -20,7 +20,7 @@
 @Data
 @NoArgsConstructor
 public class ElectronicReceiptsRequest implements Serializable {
-  public static final float serialVersionUID = 1L;
+  private static final long serialVersionUID = 1L;
   /**
    * 
    * 字段名:受理类型
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/transfer/ElectronicReceiptsResult.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/transfer/ElectronicReceiptsResult.java
index 114b1982c3..4e0581108c 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/transfer/ElectronicReceiptsResult.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/transfer/ElectronicReceiptsResult.java
@@ -20,7 +20,7 @@
 @Data
 @NoArgsConstructor
 public class ElectronicReceiptsResult implements Serializable {
-  public static final float serialVersionUID = 1L;
+  private static final long serialVersionUID = 1L;
   /**
    * 
    * 字段名:受理类型
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/transfer/MerchantBatchRequest.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/transfer/MerchantBatchRequest.java
index fe6450b22e..a319d3f4b3 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/transfer/MerchantBatchRequest.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/transfer/MerchantBatchRequest.java
@@ -20,7 +20,7 @@
 @Data
 @NoArgsConstructor
 public class MerchantBatchRequest implements Serializable {
-  public static final float serialVersionUID = 1L;
+  private static final long serialVersionUID = 1L;
   /**
    * 
    * 字段名:商家批次单号
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/transfer/PartnerTransferRequest.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/transfer/PartnerTransferRequest.java
index bd06b5db4b..0e8418cca9 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/transfer/PartnerTransferRequest.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/transfer/PartnerTransferRequest.java
@@ -19,7 +19,7 @@
 @Data
 @NoArgsConstructor
 public class PartnerTransferRequest implements Serializable {
-  public static final float serialVersionUID = 1L;
+  private static final long serialVersionUID = 1L;
 
   /**
    * 
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/transfer/PartnerTransferResult.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/transfer/PartnerTransferResult.java
index cca369b408..d9c8019462 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/transfer/PartnerTransferResult.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/transfer/PartnerTransferResult.java
@@ -18,7 +18,7 @@
 @Data
 @NoArgsConstructor
 public class PartnerTransferResult implements Serializable {
-  public static final float serialVersionUID = 1L;
+  private static final long serialVersionUID = 1L;
   /**
    * 
    * 字段名:商家批次单号
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/transfer/ReceiptBillRequest.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/transfer/ReceiptBillRequest.java
index deda24d426..1995ac1656 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/transfer/ReceiptBillRequest.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/marketing/transfer/ReceiptBillRequest.java
@@ -18,7 +18,7 @@
 @Data
 @NoArgsConstructor
 public class ReceiptBillRequest implements Serializable {
-  public static final float serialVersionUID = 1L;
+  private static final long serialVersionUID = 1L;
   /**
    * 
    * 字段名:商家批次单号
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/notify/WxPayOrderNotifyResult.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/notify/WxPayOrderNotifyResult.java
index bd9a6f3ecf..27e8c1e1ec 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/notify/WxPayOrderNotifyResult.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/notify/WxPayOrderNotifyResult.java
@@ -387,7 +387,7 @@ protected void composeCoupons() {
     if (this.couponCount == null || this.couponCount == 0) {
       return;
     }
-    this.couponList = new ArrayList(couponCount);
+    this.couponList = new ArrayList<>(couponCount);
     for (int i = 0; i < this.couponCount; i++) {
       WxPayOrderNotifyCoupon coupon = new WxPayOrderNotifyCoupon();
       coupon.setCouponId(this.getXmlValue("xml/coupon_id_" + i));
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/notify/WxPayRefundNotifyResult.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/notify/WxPayRefundNotifyResult.java
index ae86b8c854..8615a2e461 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/notify/WxPayRefundNotifyResult.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/notify/WxPayRefundNotifyResult.java
@@ -273,7 +273,7 @@ public String toString() {
      * 
*/ @XStreamAlias("refund_recv_accout") - private String refundRecvAccout; + private String refundRecvAccount; /** *
@@ -324,7 +324,7 @@ public void loadXML(Document d) {
       settlementRefundFee = readXmlInteger(d, "settlement_refund_fee");
       refundStatus = readXmlString(d, "refund_status");
       successTime = readXmlString(d, "success_time");
-      refundRecvAccout = readXmlString(d, "refund_recv_accout");
+      refundRecvAccount = readXmlString(d, "refund_recv_accout");
       refundAccount = readXmlString(d, "refund_account");
       refundRequestSource = readXmlString(d, "refund_request_source");
     }
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/payscore/Device.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/payscore/Device.java
new file mode 100644
index 0000000000..3060b0ff47
--- /dev/null
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/payscore/Device.java
@@ -0,0 +1,38 @@
+package com.github.binarywang.wxpay.bean.payscore;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+
+/**
+ * 设备信息
+ **/
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class Device  implements Serializable {
+
+  private static final long serialVersionUID = -4510224826631515321L;
+
+
+  /**
+   * 服务开始的设备ID
+   */
+  @SerializedName("start_device_id")
+  private String startDeviceId;
+
+  /**
+   * 服务结束的设备ID
+   */
+  @SerializedName("end_device_id")
+  private String endDeviceId;
+
+  /**
+   * 物料编码
+   */
+  @SerializedName("materiel_no")
+  private String materielNo;
+}
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/payscore/WxPartnerUserAuthorizationStatusNotifyResult.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/payscore/WxPartnerUserAuthorizationStatusNotifyResult.java
index feeabaac16..be44427dfc 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/payscore/WxPartnerUserAuthorizationStatusNotifyResult.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/payscore/WxPartnerUserAuthorizationStatusNotifyResult.java
@@ -4,7 +4,6 @@
 import lombok.Data;
 import lombok.EqualsAndHashCode;
 import lombok.NoArgsConstructor;
-import me.chanjar.weixin.common.util.json.WxGsonBuilder;
 
 import java.io.Serializable;
 
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/payscore/WxPayScoreRequest.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/payscore/WxPayScoreRequest.java
index 3c58a62e80..020ed05cb7 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/payscore/WxPayScoreRequest.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/payscore/WxPayScoreRequest.java
@@ -42,6 +42,7 @@ public String toJson() {
    * openid : oUpF8uMuAJO_M2pxb1Q9zNjWeS6o
    * need_user_confirm : true
    * profitSharing : false:不分账,默认:false,true:分账
+   * device : {"start_device_id":"202501","end_device_id":"202502","materiel_no":"212323232"}
    */
   @SerializedName("out_order_no")
   private String outOrderNo;
@@ -95,4 +96,6 @@ public String toJson() {
    */
   @SerializedName("complete_time")
   private String completeTime;
+  @SerializedName("device")
+  private Device device;
 }
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/profitsharing/ReceiverList.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/profitsharing/ReceiverList.java
index d3d8c07d37..505d7e28d4 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/profitsharing/ReceiverList.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/profitsharing/ReceiverList.java
@@ -12,7 +12,7 @@
 
 public class ReceiverList implements Serializable {
   private static final long serialVersionUID = -1316860887694489921L;
-  ArrayList list;
+  ArrayList list;
 
   private ReceiverList() {
   }
@@ -23,7 +23,7 @@ private ReceiverList() {
    */
   public static ReceiverList getInstance() {
     ReceiverList receiverList = new ReceiverList();
-    receiverList.list = new ArrayList();
+    receiverList.list = new ArrayList<>();
     return receiverList;
   }
 
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/profitsharing/request/ProfitSharingReceiverV3Request.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/profitsharing/request/ProfitSharingReceiverV3Request.java
index b8de4f5d5b..98e99b3e2c 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/profitsharing/request/ProfitSharingReceiverV3Request.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/profitsharing/request/ProfitSharingReceiverV3Request.java
@@ -1,16 +1,10 @@
 package com.github.binarywang.wxpay.bean.profitsharing.request;
 
-import com.github.binarywang.wxpay.bean.request.BaseWxPayRequest;
-import com.github.binarywang.wxpay.constant.WxPayConstants;
-import com.github.binarywang.wxpay.exception.WxPayException;
 import com.github.binarywang.wxpay.v3.SpecEncrypt;
 import com.google.gson.annotations.SerializedName;
-import com.thoughtworks.xstream.annotations.XStreamAlias;
 import lombok.*;
-import me.chanjar.weixin.common.annotation.Required;
 
 import java.io.Serializable;
-import java.util.Map;
 
 /**
  * 添加/删除分账接受方请求对象
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/profitsharing/request/ProfitSharingRequest.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/profitsharing/request/ProfitSharingRequest.java
index 95b5e67fc9..1cc72b1fa8 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/profitsharing/request/ProfitSharingRequest.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/profitsharing/request/ProfitSharingRequest.java
@@ -3,14 +3,10 @@
 import com.github.binarywang.wxpay.bean.request.BaseWxPayRequest;
 import com.github.binarywang.wxpay.constant.WxPayConstants;
 import com.github.binarywang.wxpay.exception.WxPayException;
-import com.github.binarywang.wxpay.v3.SpecEncrypt;
-import com.google.gson.Gson;
-import com.google.gson.annotations.SerializedName;
 import com.thoughtworks.xstream.annotations.XStreamAlias;
 import lombok.*;
 import me.chanjar.weixin.common.annotation.Required;
 
-import java.io.Serializable;
 import java.util.Map;
 
 /**
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/profitsharing/result/ProfitSharingQueryResult.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/profitsharing/result/ProfitSharingQueryResult.java
index 437a82e18f..6c222ddc54 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/profitsharing/result/ProfitSharingQueryResult.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/profitsharing/result/ProfitSharingQueryResult.java
@@ -92,7 +92,7 @@ protected void loadXml(Document d) {
   }
 
   @Data
-  public class Receiver {
+  public static class Receiver {
     /**
      * 分账接收方类型
      */
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/profitsharing/result/ProfitSharingReceiverV3Result.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/profitsharing/result/ProfitSharingReceiverV3Result.java
index 996bb5e789..141a2df94b 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/profitsharing/result/ProfitSharingReceiverV3Result.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/profitsharing/result/ProfitSharingReceiverV3Result.java
@@ -1,13 +1,8 @@
 package com.github.binarywang.wxpay.bean.profitsharing.result;
 
-import com.github.binarywang.wxpay.bean.result.BaseWxPayResult;
 import com.github.binarywang.wxpay.v3.SpecEncrypt;
 import com.google.gson.annotations.SerializedName;
-import com.thoughtworks.xstream.annotations.XStreamAlias;
 import lombok.Data;
-import lombok.EqualsAndHashCode;
-import lombok.NoArgsConstructor;
-import org.w3c.dom.Document;
 
 import java.io.Serializable;
 
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/request/BaseWxPayRequest.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/request/BaseWxPayRequest.java
index 5eeeb36604..526a961e47 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/request/BaseWxPayRequest.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/request/BaseWxPayRequest.java
@@ -20,9 +20,9 @@
 
 import java.io.Serializable;
 import java.math.BigDecimal;
+import java.math.RoundingMode;
 import java.util.HashMap;
 import java.util.Map;
-import java.util.Optional;
 
 import static com.github.binarywang.wxpay.constant.WxPayConstants.SignType.ALL_SIGN_TYPES;
 
@@ -147,21 +147,21 @@ public void setWorkWxSign(String workWxSign) {
    * @return the integer
    */
   public static Integer yuanToFen(String yuan) {
-    return new BigDecimal(yuan).setScale(2, BigDecimal.ROUND_HALF_UP).multiply(new BigDecimal(100)).intValue();
+    return new BigDecimal(yuan).setScale(2, RoundingMode.HALF_UP).multiply(new BigDecimal(100)).intValue();
   }
 
   /**
    * 元转分
    */
   public static Integer yuan2Fen(BigDecimal yuan) {
-    return yuan.multiply(BigDecimal.valueOf(100)).setScale(2, BigDecimal.ROUND_HALF_UP).intValue();
+    return yuan.multiply(BigDecimal.valueOf(100)).setScale(2, RoundingMode.HALF_UP).intValue();
   }
 
   /**
    * 分转元
    */
   public static BigDecimal fen2Yuan(BigDecimal fen) {
-    return fen.divide(BigDecimal.valueOf(100)).setScale(2, BigDecimal.ROUND_HALF_UP);
+    return fen.divide(BigDecimal.valueOf(100)).setScale(2, RoundingMode.HALF_UP);
   }
 
   /**
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/request/CombineCloseRequest.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/request/CombineCloseRequest.java
index b397f0f1c0..428878dc77 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/request/CombineCloseRequest.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/request/CombineCloseRequest.java
@@ -87,5 +87,33 @@ public static class SubOrders implements Serializable {
      */
     @SerializedName(value = "out_trade_no")
     private String outTradeNo;
+    /**
+     * 
+     * 字段名:二级商户号
+     * 变量名:sub_mchid
+     * 是否必填:是
+     * 类型:string[1,32]
+     * 描述:
+     *  二级商户商户号,由微信支付生成并下发。服务商子商户的商户号,被合单方。直连商户不用传二级商户号。
+     *  示例值:1900000109
+     * 
+ */ + @SerializedName(value = "sub_mchid") + private String subMchid; + /** + *
+     * 字段名:子商户应用ID
+     * 变量名:sub_appid
+     * 是否必填:是
+     * 类型:string[1,32]
+     * 描述:
+     *  子商户申请的应用ID,全局唯一。请求基础下单接口时请注意APPID的应用属性,例如公众号场景下,
+     *  需使用应用属性为公众号的APPID 若sub_openid有传的情况下,
+     *  sub_appid必填,且sub_appid需与sub_openid对应
+     *  示例值:wxd678efh567hg6999
+     * 
+ */ + @SerializedName(value = "sub_appid") + private String subAppid; } } diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/request/WxPayPartnerRefundV3Request.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/request/WxPayPartnerRefundV3Request.java index c522c90d88..8f3e8ebd10 100644 --- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/request/WxPayPartnerRefundV3Request.java +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/request/WxPayPartnerRefundV3Request.java @@ -6,7 +6,6 @@ import lombok.experimental.Accessors; import java.io.Serializable; -import java.util.List; /** * 微信支付服务商退款请求 diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/request/WxPayUnifiedOrderV3GlobalRequest.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/request/WxPayUnifiedOrderV3GlobalRequest.java new file mode 100644 index 0000000000..296d3a8646 --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/request/WxPayUnifiedOrderV3GlobalRequest.java @@ -0,0 +1,57 @@ +package com.github.binarywang.wxpay.bean.request; + +import com.google.gson.annotations.SerializedName; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import lombok.experimental.Accessors; + +import java.io.Serializable; + +/** + *
+ * 境外微信支付统一下单请求参数对象.
+ * 参考文档:https://pay.weixin.qq.com/doc/global/v3/zh/4013014223
+ * 
+ * + * @author Binary Wang + */ +@Data +@NoArgsConstructor +@Accessors(chain = true) +@EqualsAndHashCode(callSuper = true) +public class WxPayUnifiedOrderV3GlobalRequest extends WxPayUnifiedOrderV3Request implements Serializable { + private static final long serialVersionUID = 1L; + + /** + *
+   * 字段名:交易类型
+   * 变量名:trade_type
+   * 是否必填:是
+   * 类型:string[1,16]
+   * 描述:
+   *  交易类型,取值如下:
+   *  JSAPI--JSAPI支付
+   *  NATIVE--Native支付
+   *  APP--APP支付
+   *  H5--H5支付
+   *  示例值:JSAPI
+   * 
+ */ + @SerializedName(value = "trade_type") + private String tradeType; + + /** + *
+   * 字段名:商户类目
+   * 变量名:merchant_category_code
+   * 是否必填:是
+   * 类型:string[1,32]
+   * 描述:
+   *  商户类目,境外商户必填
+   *  示例值:5812
+   * 
+ */ + @SerializedName(value = "merchant_category_code") + private String merchantCategoryCode; +} \ No newline at end of file diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/request/WxPayUnifiedOrderV3Request.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/request/WxPayUnifiedOrderV3Request.java index 98dae388ef..8ac588de81 100644 --- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/request/WxPayUnifiedOrderV3Request.java +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/request/WxPayUnifiedOrderV3Request.java @@ -250,6 +250,12 @@ public static class Payer implements Serializable { */ @SerializedName(value = "openid") private String openid; + + /** + * 实名支付用户身份标识 + */ + @SerializedName(value = "identity") + private Identity identity; } @Data @@ -572,4 +578,36 @@ public static class SettleInfo implements Serializable { @SerializedName(value = "profit_sharing") private Boolean profitSharing; } + + + @Data + @NoArgsConstructor + public static class Identity implements Serializable { + private static final long serialVersionUID = 1L; + + /** + * 证件类型 + * IDCARD:身份证 + * HONGKONG_MACAO:港澳回乡证 + * HONGKONG_MACAO_RESIDENT:港澳居住证 + * TAIWAN_RESIDENT:台湾居住证 + * FOREIGN_RESIDENT:外国人永居证 + * OVERSEA_PASSPORT:护照 + */ + @SerializedName(value = "type") + private String type; + /** + * 证件号 + * 证件号,如身份证号。 + * 示例值:43102119910910512X + */ + @SerializedName(value = "number") + private String number; + /** + * 证件姓名。 + * 示例值:周星星 + */ + @SerializedName(value = "name") + private String name; + } } diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/result/BaseWxPayResult.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/result/BaseWxPayResult.java index 0c288b5507..109fab66bc 100644 --- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/result/BaseWxPayResult.java +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/result/BaseWxPayResult.java @@ -194,7 +194,7 @@ private void loadBasicXML(Document d) { protected static Integer readXmlInteger(Node d, String tagName) { String content = readXmlString(d, tagName); - if (content == null || content.trim().length() == 0) { + if (content == null || content.trim().isEmpty()) { return null; } return Integer.parseInt(content); @@ -232,7 +232,7 @@ public static String readXmlString(Document d, String tagName) { protected static Integer readXmlInteger(Document d, String tagName) { String content = readXmlString(d, tagName); - if (content == null || content.trim().length() == 0) { + if (content == null || content.trim().isEmpty()) { return null; } @@ -241,7 +241,7 @@ protected static Integer readXmlInteger(Document d, String tagName) { protected static Long readXmlLong(Document d, String tagName) { String content = readXmlString(d, tagName); - if (content == null || content.trim().length() == 0) { + if (content == null || content.trim().isEmpty()) { return null; } diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/result/BaseWxPayV3Result.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/result/BaseWxPayV3Result.java new file mode 100644 index 0000000000..88724b939e --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/result/BaseWxPayV3Result.java @@ -0,0 +1,42 @@ +package com.github.binarywang.wxpay.bean.result; + +import lombok.Data; + +import java.io.Serializable; + +/** + *
+ * 微信支付v3结果共用属性类.
+ * 用于保存原始JSON响应内容,便于访问API返回的新增字段.
+ * 
+ * + * @author Binary Wang + */ +@Data +public abstract class BaseWxPayV3Result implements Serializable { + private static final long serialVersionUID = 1L; + + /** + * 原始JSON字符串. + * 保存微信支付v3 API返回的原始JSON内容,便于访问未在Result类中定义的字段. + */ + private String rawJsonString; + + /** + * 获取原始JSON响应内容. + * + * @return 原始JSON字符串 + */ + public String getRawJsonString() { + return rawJsonString; + } + + /** + * 设置原始JSON响应内容. + * + * @param rawJsonString 原始JSON字符串 + */ + public void setRawJsonString(String rawJsonString) { + this.rawJsonString = rawJsonString; + } +} \ No newline at end of file diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/result/WxWithholdNotifyResult.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/result/WxWithholdNotifyResult.java index 288e8b933f..f2d96804d2 100644 --- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/result/WxWithholdNotifyResult.java +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/result/WxWithholdNotifyResult.java @@ -224,7 +224,7 @@ protected void composeCoupons() { if (this.couponCount == null || this.couponCount == 0) { return; } - this.couponList = new ArrayList(couponCount); + this.couponList = new ArrayList<>(couponCount); for (int i = 0; i < this.couponCount; i++) { WxPayOrderNotifyCoupon coupon = new WxPayOrderNotifyCoupon(); coupon.setCouponId(this.getXmlValue("xml/coupon_id_" + i)); diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/result/WxWithholdOrderQueryResult.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/result/WxWithholdOrderQueryResult.java index f625462e16..3ce0079f5b 100644 --- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/result/WxWithholdOrderQueryResult.java +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/result/WxWithholdOrderQueryResult.java @@ -147,7 +147,7 @@ protected void composeCoupons() { if (this.couponCount == null || this.couponCount == 0) { return; } - this.couponList = new ArrayList(couponCount); + this.couponList = new ArrayList<>(couponCount); for (int i = 0; i < this.couponCount; i++) { WxPayOrderNotifyCoupon coupon = new WxPayOrderNotifyCoupon(); coupon.setCouponId(this.getXmlValue("xml/coupon_id_" + i)); diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/result/enums/GlobalTradeTypeEnum.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/result/enums/GlobalTradeTypeEnum.java new file mode 100644 index 0000000000..fd33b240f1 --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/result/enums/GlobalTradeTypeEnum.java @@ -0,0 +1,36 @@ +package com.github.binarywang.wxpay.bean.result.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 境外微信支付方式 + * Overseas WeChat Pay trade types with global endpoints + * + * @author Binary Wang + */ +@Getter +@AllArgsConstructor +public enum GlobalTradeTypeEnum { + /** + * APP + */ + APP("/global/v3/transactions/app"), + /** + * JSAPI 或 小程序 + */ + JSAPI("/global/v3/transactions/jsapi"), + /** + * NATIVE + */ + NATIVE("/global/v3/transactions/native"), + /** + * H5 + */ + H5("/global/v3/transactions/h5"); + + /** + * 境外下单url + */ + private final String url; +} \ No newline at end of file diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/config/VerifierBuilder.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/config/VerifierBuilder.java new file mode 100644 index 0000000000..b0d9276a32 --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/config/VerifierBuilder.java @@ -0,0 +1,141 @@ +package com.github.binarywang.wxpay.config; + +import com.github.binarywang.wxpay.exception.WxPayException; +import com.github.binarywang.wxpay.v3.auth.*; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import org.apache.commons.lang3.StringUtils; + +import java.nio.charset.StandardCharsets; +import java.security.PrivateKey; +import java.security.PublicKey; + +/** + * 验证器构建. + * + * @author holy + */ +@NoArgsConstructor(access = AccessLevel.PRIVATE) +class VerifierBuilder { + /** + * 构建验证器. + *

+ * 场景 + *

+   *   1. 老商户号,只有平台证书,未开通公钥 (已验证)
+   *   2. 新商户号,被强制开通公钥,没有平台证书 (已验证)
+   *   3. 老商户号,有平台证书,主动开通公钥 (未验证,具备条件的朋友,可以帮忙验证下)
+   *   ...
+   * 
+ * + * @param certSerialNo c + * @param mchId m + * @param apiV3Key a + * @param merchantPrivateKey m + * @param wxPayHttpProxy w + * @param certAutoUpdateTime c + * @param payBaseUrl p + * @param publicKeyId p + * @param publicKey p + * @return v + * @throws WxPayException e + */ + @SuppressWarnings("java:S107") + static Verifier build( + // 平台证书 - 依赖参数 + String certSerialNo, + String mchId, + String apiV3Key, + PrivateKey merchantPrivateKey, + WxPayHttpProxy wxPayHttpProxy, + int certAutoUpdateTime, + String payBaseUrl, + // 公钥 - 依赖参数 + String publicKeyId, + PublicKey publicKey + ) throws WxPayException { + Verifier certificatesVerifier = null; + Exception ex = null; + + // 构建平台证书验证器 + // (沿用旧逻辑)优先构建平台证书验证器,因为公钥验证器需要平台证书验证器 (见以下 .setOtherVerifier ) + // 新商户号默认无平台证书,已确认无法构建平台证书验证器,会抛出异常;老商户号,有平台证书主动开通公钥的情况,待具备条件的朋友验证 + // 建议公钥模式稳定后,优先构建公钥验证器,以免每次都尝试构建平台证书验证器,且失败 {@link com.github.binarywang.wxpay.v3.auth.PublicCertificateVerifier.verify} + if (merchantPrivateKey != null && StringUtils.isNoneBlank(certSerialNo, apiV3Key)) { + try { + certificatesVerifier = getCertificatesVerifier( + certSerialNo, mchId, apiV3Key, merchantPrivateKey, wxPayHttpProxy, certAutoUpdateTime, payBaseUrl + ); + } catch (Exception e) { + ex = e; + } + } + + // 构建公钥验证器 + if (publicKey != null && StringUtils.isNotBlank(publicKeyId)) { + try { + certificatesVerifier = getPublicCertVerifier(publicKeyId, publicKey, certificatesVerifier); + } catch (Exception e) { + ex = e; + } + } + if (certificatesVerifier != null) { + return certificatesVerifier; + } + + // 有异常时抛出 + if (ex != null) { + throw new WxPayException(ex.getMessage(), ex); + } + + // 没有证书验证器时。不确定是否抛出异常,沿用之前逻辑,返回 null + return null; + } + + /** + * 针对完全使用公钥的场景 + * @param publicKeyId 公钥id + * @param publicKey 公钥 + * @return + */ + static Verifier buildPublicCertVerifier(String publicKeyId, PublicKey publicKey) { + return getPublicCertVerifier(publicKeyId, publicKey, null); + } + + /** + * 获取证书验证器. + * + * @param certSerialNo certSerialNo + * @param mchId mchId + * @param apiV3Key apiV3Key + * @param merchantPrivateKey merchantPrivateKey + * @param wxPayHttpProxy wxPayHttpProxy + * @param certAutoUpdateTime certAutoUpdateTime + * @param payBaseUrl payBaseUrl + * @return verifier + */ + private static AutoUpdateCertificatesVerifier getCertificatesVerifier( + String certSerialNo, String mchId, String apiV3Key, PrivateKey merchantPrivateKey, + WxPayHttpProxy wxPayHttpProxy, int certAutoUpdateTime, String payBaseUrl + ) { + return new AutoUpdateCertificatesVerifier( + new WxPayCredentials(mchId, new PrivateKeySigner(certSerialNo, merchantPrivateKey)), + apiV3Key.getBytes(StandardCharsets.UTF_8), certAutoUpdateTime, + payBaseUrl, wxPayHttpProxy); + } + + /** + * 获取公钥验证器. + * + * @param publicKeyId id + * @param publicKey key + * @param certificatesVerifier verifier + * @return verifier + */ + private static Verifier getPublicCertVerifier(String publicKeyId, PublicKey publicKey, Verifier certificatesVerifier) { + Verifier publicCertificatesVerifier = new PublicCertificateVerifier(publicKey, publicKeyId); + publicCertificatesVerifier.setOtherVerifier(certificatesVerifier); + certificatesVerifier = publicCertificatesVerifier; + return certificatesVerifier; + } +} 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 35558d5636..ee44780590 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 @@ -4,7 +4,8 @@ import com.github.binarywang.wxpay.util.HttpProxyUtils; import com.github.binarywang.wxpay.util.ResourcesUtils; import com.github.binarywang.wxpay.v3.WxPayV3HttpClientBuilder; -import com.github.binarywang.wxpay.v3.auth.*; +import com.github.binarywang.wxpay.v3.auth.Verifier; +import com.github.binarywang.wxpay.v3.auth.WxPayValidator; import com.github.binarywang.wxpay.v3.util.PemUtils; import lombok.Data; import lombok.EqualsAndHashCode; @@ -13,13 +14,21 @@ import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.RegExUtils; import org.apache.commons.lang3.StringUtils; +import org.apache.http.HttpHost; +import org.apache.http.auth.AuthScope; +import org.apache.http.auth.UsernamePasswordCredentials; +import org.apache.http.client.CredentialsProvider; +import org.apache.http.impl.client.BasicCredentialsProvider; import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClients; +import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; +import org.apache.http.conn.ssl.DefaultHostnameVerifier; +import org.apache.http.conn.ssl.SSLConnectionSocketFactory; import org.apache.http.ssl.SSLContexts; import javax.net.ssl.SSLContext; import java.io.*; import java.net.URL; -import java.nio.charset.StandardCharsets; import java.security.KeyStore; import java.security.PrivateKey; import java.security.PublicKey; @@ -45,7 +54,7 @@ public class WxPayConfig { /** * 微信支付接口请求地址域名部分. */ - private String payBaseUrl = DEFAULT_PAY_BASE_URL; + private String apiHostUrl = DEFAULT_PAY_BASE_URL; /** * http请求连接超时时间. @@ -185,11 +194,32 @@ public class WxPayConfig { private CloseableHttpClient apiV3HttpClient; + + /** + * 用于普通支付接口的可复用HttpClient,使用连接池 + */ + private CloseableHttpClient httpClient; + + /** + * 用于需要SSL证书的支付接口的可复用HttpClient,使用连接池 + */ + private CloseableHttpClient sslHttpClient; + /** * 支持扩展httpClientBuilder */ private HttpClientBuilderCustomizer httpClientBuilderCustomizer; private HttpClientBuilderCustomizer apiV3HttpClientBuilderCustomizer; + + /** + * HTTP连接池最大连接数,默认20 + */ + private int maxConnTotal = 20; + + /** + * HTTP连接池每个路由的最大连接数,默认10 + */ + private int maxConnPerRoute = 10; /** * 私钥信息 */ @@ -227,17 +257,27 @@ public class WxPayConfig { */ private Verifier verifier; + /** + * 是否将全部v3接口的请求都添加Wechatpay-Serial请求头,默认不添加 + */ + private boolean strictlyNeedWechatPaySerial = false; + + /** + * 是否完全使用公钥模式(用以微信从平台证书到公钥的灰度切换),默认不使用 + */ + private boolean fullPublicKeyModel = false; + /** * 返回所设置的微信支付接口请求地址域名. * * @return 微信支付接口请求地址域名 */ - public String getPayBaseUrl() { - if (StringUtils.isEmpty(this.payBaseUrl)) { + public String getApiHostUrl() { + if (StringUtils.isEmpty(this.apiHostUrl)) { return DEFAULT_PAY_BASE_URL; } - return this.payBaseUrl; + return this.apiHostUrl; } @SneakyThrows @@ -284,32 +324,31 @@ public CloseableHttpClient initApiV3HttpClient() throws WxPayException { if (StringUtils.isBlank(this.getApiV3Key())) { throw new WxPayException("请确保apiV3Key值已设置"); } - - // 尝试从p12证书中加载私钥和证书 - PrivateKey merchantPrivateKey = null; - X509Certificate certificate = null; - Object[] objects = this.p12ToPem(); - if (objects != null) { - merchantPrivateKey = (PrivateKey) objects[0]; - certificate = (X509Certificate) objects[1]; - this.certSerialNo = certificate.getSerialNumber().toString(16).toUpperCase(); - } try { - if (merchantPrivateKey == null) { - try (InputStream keyInputStream = this.loadConfigInputStream(this.getPrivateKeyString(), this.getPrivateKeyPath(), - this.privateKeyContent, "privateKeyPath")) { - merchantPrivateKey = PemUtils.loadPrivateKey(keyInputStream); - } + PrivateKey merchantPrivateKey = null; + PublicKey publicKey = null; + + // 不使用完全公钥模式时,同时兼容平台证书和公钥 + X509Certificate certificate = null; + // 尝试从p12证书中加载私钥和证书 + Object[] objects = this.p12ToPem(); + if (objects != null) { + merchantPrivateKey = (PrivateKey) objects[0]; + certificate = (X509Certificate) objects[1]; + this.certSerialNo = certificate.getSerialNumber().toString(16).toUpperCase(); } - if (certificate == null && StringUtils.isBlank(this.getCertSerialNo())) { + if (certificate == null && StringUtils.isBlank(this.getCertSerialNo()) && StringUtils.isNotBlank(this.getPrivateCertPath())) { try (InputStream certInputStream = this.loadConfigInputStream(this.getPrivateCertString(), this.getPrivateCertPath(), this.privateCertContent, "privateCertPath")) { certificate = PemUtils.loadCertificate(certInputStream); } this.certSerialNo = certificate.getSerialNumber().toString(16).toUpperCase(); } - PublicKey publicKey = null; + if (this.getPublicKeyString() != null || this.getPublicKeyPath() != null || this.publicKeyContent != null) { + if (StringUtils.isBlank(this.getPublicKeyId())) { + throw new WxPayException("请确保和publicKeyId配套使用"); + } try (InputStream pubInputStream = this.loadConfigInputStream(this.getPublicKeyString(), this.getPublicKeyPath(), this.publicKeyContent, "publicKeyPath")) { @@ -317,18 +356,26 @@ public CloseableHttpClient initApiV3HttpClient() throws WxPayException { } } + // 加载api私钥 + if (merchantPrivateKey == null && (StringUtils.isNotBlank(this.getPrivateKeyPath()) || StringUtils.isNotBlank(this.getPrivateKeyString()) || null != this.privateKeyContent)) { + try (InputStream keyInputStream = this.loadConfigInputStream(this.getPrivateKeyString(), this.getPrivateKeyPath(), + this.privateKeyContent, "privateKeyPath")) { + merchantPrivateKey = PemUtils.loadPrivateKey(keyInputStream); + } + } + //构造Http Proxy正向代理 WxPayHttpProxy wxPayHttpProxy = getWxPayHttpProxy(); + // 构造证书验签器 Verifier certificatesVerifier; - if (publicKey == null) { - certificatesVerifier = - new AutoUpdateCertificatesVerifier( - new WxPayCredentials(mchId, new PrivateKeySigner(certSerialNo, merchantPrivateKey)), - this.getApiV3Key().getBytes(StandardCharsets.UTF_8), this.getCertAutoUpdateTime(), - this.getPayBaseUrl(), wxPayHttpProxy); + if (this.fullPublicKeyModel) { + // 使用完全公钥模式时,只加载公钥相关配置,避免下载平台证书使灰度切换无法达到100%覆盖 + certificatesVerifier = VerifierBuilder.buildPublicCertVerifier(this.publicKeyId, publicKey); } else { - certificatesVerifier = new PublicCertificateVerifier(publicKey, publicKeyId); + certificatesVerifier = VerifierBuilder.build( + this.getCertSerialNo(), this.getMchId(), this.getApiV3Key(), merchantPrivateKey, wxPayHttpProxy, + this.getCertAutoUpdateTime(), this.getApiHostUrl(), this.getPublicKeyId(), publicKey); } WxPayV3HttpClientBuilder wxPayV3HttpClientBuilder = WxPayV3HttpClientBuilder.create() @@ -382,7 +429,7 @@ private InputStream loadConfigInputStream(String configString, String configPath if (configContent != null) { return new ByteArrayInputStream(configContent); } - + if (StringUtils.isNotEmpty(configString)) { configContent = Base64.getDecoder().decode(configString); return new ByteArrayInputStream(configContent); @@ -481,4 +528,111 @@ private Object[] p12ToPem() { return null; } + + /** + * 初始化使用连接池的HttpClient + * + * @return CloseableHttpClient + * @throws WxPayException 初始化异常 + */ + public CloseableHttpClient initHttpClient() throws WxPayException { + if (this.httpClient != null) { + return this.httpClient; + } + + // 创建连接池管理器 + PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(); + connectionManager.setMaxTotal(this.maxConnTotal); + connectionManager.setDefaultMaxPerRoute(this.maxConnPerRoute); + + // 创建HttpClient构建器 + org.apache.http.impl.client.HttpClientBuilder httpClientBuilder = HttpClients.custom() + .setConnectionManager(connectionManager); + + // 配置代理 + configureProxy(httpClientBuilder); + + // 提供自定义httpClientBuilder的能力 + Optional.ofNullable(httpClientBuilderCustomizer).ifPresent(e -> { + e.customize(httpClientBuilder); + }); + + this.httpClient = httpClientBuilder.build(); + return this.httpClient; + } + + /** + * 初始化使用连接池且支持SSL的HttpClient + * + * @return CloseableHttpClient + * @throws WxPayException 初始化异常 + */ + public CloseableHttpClient initSslHttpClient() throws WxPayException { + if (this.sslHttpClient != null) { + return this.sslHttpClient; + } + + // 初始化SSL上下文 + SSLContext sslContext = this.getSslContext(); + if (null == sslContext) { + sslContext = this.initSSLContext(); + } + + // 创建支持SSL的连接池管理器 + PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(); + connectionManager.setMaxTotal(this.maxConnTotal); + connectionManager.setDefaultMaxPerRoute(this.maxConnPerRoute); + + // 创建HttpClient构建器,配置SSL + org.apache.http.impl.client.HttpClientBuilder httpClientBuilder = HttpClients.custom() + .setConnectionManager(connectionManager) + .setSSLSocketFactory(new SSLConnectionSocketFactory(sslContext, new DefaultHostnameVerifier())); + + // 配置代理 + configureProxy(httpClientBuilder); + + // 提供自定义httpClientBuilder的能力 + Optional.ofNullable(httpClientBuilderCustomizer).ifPresent(e -> { + e.customize(httpClientBuilder); + }); + + this.sslHttpClient = httpClientBuilder.build(); + return this.sslHttpClient; + } + + /** + * 配置HTTP代理 + */ + private void configureProxy(org.apache.http.impl.client.HttpClientBuilder httpClientBuilder) { + if (StringUtils.isNotBlank(this.getHttpProxyHost()) && this.getHttpProxyPort() > 0) { + if (StringUtils.isEmpty(this.getHttpProxyUsername())) { + this.setHttpProxyUsername("whatever"); + } + + // 使用代理服务器 需要用户认证的代理服务器 + CredentialsProvider provider = new BasicCredentialsProvider(); + provider.setCredentials(new AuthScope(this.getHttpProxyHost(), this.getHttpProxyPort()), + new UsernamePasswordCredentials(this.getHttpProxyUsername(), this.getHttpProxyPassword())); + httpClientBuilder.setDefaultCredentialsProvider(provider) + .setProxy(new HttpHost(this.getHttpProxyHost(), this.getHttpProxyPort())); + } + } + + /** + * 获取用于普通支付接口的HttpClient + * + * @return CloseableHttpClient + */ + public CloseableHttpClient getHttpClient() { + return httpClient; + } + + /** + * 获取用于SSL支付接口的HttpClient + * + * @return CloseableHttpClient + */ + public CloseableHttpClient getSslHttpClient() { + return sslHttpClient; + } } diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/converter/WxPayOrderNotifyResultConverter.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/converter/WxPayOrderNotifyResultConverter.java index 0f01358633..e3e28e9183 100644 --- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/converter/WxPayOrderNotifyResultConverter.java +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/converter/WxPayOrderNotifyResultConverter.java @@ -39,7 +39,6 @@ public WxPayOrderNotifyResultConverter(Mapper mapper, ReflectionProvider reflect } @Override - @SuppressWarnings("rawtypes") public boolean canConvert(Class type) { return type.equals(WxPayOrderNotifyResult.class); } diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/example/NewTransferApiExample.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/example/NewTransferApiExample.java new file mode 100644 index 0000000000..8d74e5a4ef --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/example/NewTransferApiExample.java @@ -0,0 +1,249 @@ +package com.github.binarywang.wxpay.example; + +import com.github.binarywang.wxpay.bean.notify.SignatureHeader; +import com.github.binarywang.wxpay.bean.transfer.*; +import com.github.binarywang.wxpay.config.WxPayConfig; +import com.github.binarywang.wxpay.exception.WxPayException; +import com.github.binarywang.wxpay.service.TransferService; +import com.github.binarywang.wxpay.service.WxPayService; +import com.github.binarywang.wxpay.service.impl.WxPayServiceImpl; + +/** + * 新版商户转账API使用示例 + * + * 从2025年1月15日开始,微信支付推出了新版的商户转账API + * 新开通的商户号只能使用最新版本的商户转账接口 + * + * @author WxJava Team + * @since 2025-01-15 + */ +public class NewTransferApiExample { + + private final TransferService transferService; + + public NewTransferApiExample(WxPayConfig config) { + // 初始化微信支付服务 + WxPayService wxPayService = new WxPayServiceImpl(); + wxPayService.setConfig(config); + + // 获取新版转账服务 + this.transferService = wxPayService.getTransferService(); + } + + /** + * 发起单笔转账示例 + * 新版API使用 /v3/fund-app/mch-transfer/transfer-bills 接口 + */ + public void transferExample() { + try { + // 构建转账请求 + TransferBillsRequest request = TransferBillsRequest.newBuilder() + .appid("wx1234567890123456") // 应用ID + .outBillNo("TRANSFER_" + System.currentTimeMillis()) // 商户转账单号,确保唯一 + .transferSceneId("1005") // 转账场景ID(1005=佣金报酬) + .openid("oUpF8uMuAJO_M2pxb1Q9zNjWeS6o") // 收款用户的openid + .userName("张三") // 收款用户真实姓名(可选,会自动加密) + .transferAmount(100) // 转账金额,单位:分(此处为1元) + .transferRemark("佣金报酬") // 转账备注,用户可见 + .notifyUrl("https://your-domain.com/transfer/notify") // 异步通知地址(可选) + .userRecvPerception("Y") // 用户收款感知:Y=会收到通知,N=不会收到通知 + .build(); + + // 发起转账 + TransferBillsResult result = transferService.transferBills(request); + + // 输出结果 + System.out.println("=== 转账发起成功 ==="); + System.out.println("商户单号: " + result.getOutBillNo()); + System.out.println("微信转账单号: " + result.getTransferBillNo()); + System.out.println("创建时间: " + result.getCreateTime()); + System.out.println("状态: " + result.getState()); + System.out.println("跳转领取页面信息: " + result.getPackageInfo()); + + } catch (WxPayException e) { + System.err.println("转账失败: " + e.getMessage()); + System.err.println("错误代码: " + e.getErrCode()); + System.err.println("错误描述: " + e.getErrCodeDes()); + } + } + + /** + * 通过商户单号查询转账结果 + */ + public void queryByOutBillNoExample() { + try { + String outBillNo = "TRANSFER_1642567890123"; + TransferBillsGetResult result = transferService.getBillsByOutBillNo(outBillNo); + + System.out.println("=== 查询转账结果(商户单号)==="); + System.out.println("商户单号: " + result.getOutBillNo()); + System.out.println("微信转账单号: " + result.getTransferBillNo()); + System.out.println("状态: " + result.getState()); + System.out.println("转账金额: " + result.getTransferAmount() + "分"); + System.out.println("用户openid: " + result.getOpenid()); + System.out.println("转账备注: " + result.getTransferRemark()); + + } catch (WxPayException e) { + System.err.println("查询失败: " + e.getMessage()); + } + } + + /** + * 通过微信转账单号查询转账结果 + */ + public void queryByTransferBillNoExample() { + try { + String transferBillNo = "1000000000000000000000000001"; + TransferBillsGetResult result = transferService.getBillsByTransferBillNo(transferBillNo); + + System.out.println("=== 查询转账结果(微信单号)==="); + System.out.println("微信转账单号: " + result.getTransferBillNo()); + System.out.println("状态: " + result.getState()); + System.out.println("失败原因: " + result.getFailReason()); + + } catch (WxPayException e) { + System.err.println("查询失败: " + e.getMessage()); + } + } + + /** + * 撤销转账示例 + * 注意:只有在特定状态下才能撤销 + */ + public void cancelTransferExample() { + try { + String outBillNo = "TRANSFER_1642567890123"; + TransferBillsCancelResult result = transferService.transformBillsCancel(outBillNo); + + System.out.println("=== 撤销转账结果 ==="); + System.out.println("商户单号: " + result.getOutBillNo()); + System.out.println("状态: " + result.getState()); + System.out.println("更新时间: " + result.getUpdateTime()); + + } catch (WxPayException e) { + System.err.println("撤销失败: " + e.getMessage()); + } + } + + /** + * 处理转账回调通知示例 + * 这个方法通常在您的Web服务器的回调接口中调用 + */ + public void handleNotifyExample(String notifyData, String timestamp, String nonce, String signature, String serial) { + try { + // 构建签名头信息 + SignatureHeader header = new SignatureHeader(); + header.setTimeStamp(timestamp); + header.setNonce(nonce); + header.setSignature(signature); + header.setSerial(serial); + + // 解析并验签回调数据 + TransferBillsNotifyResult notifyResult = transferService.parseTransferBillsNotifyResult(notifyData, header); + + System.out.println("=== 处理转账回调通知 ==="); + System.out.println("商户单号: " + notifyResult.getResult().getOutBillNo()); + System.out.println("微信转账单号: " + notifyResult.getResult().getTransferBillNo()); + System.out.println("状态: " + notifyResult.getResult().getState()); + System.out.println("转账金额: " + notifyResult.getResult().getTransferAmount() + "分"); + System.out.println("更新时间: " + notifyResult.getResult().getUpdateTime()); + + // 根据状态处理业务逻辑 + switch (notifyResult.getResult().getState()) { + case "SUCCESS": + System.out.println("转账成功,进行业务处理..."); + // 更新订单状态、发送通知等 + break; + case "FAIL": + System.out.println("转账失败,失败原因: " + notifyResult.getResult().getFailReason()); + // 处理失败逻辑 + break; + default: + System.out.println("其他状态: " + notifyResult.getResult().getState()); + } + + } catch (WxPayException e) { + System.err.println("回调处理失败: " + e.getMessage()); + } + } + + /** + * 批量转账示例(使用传统API) + * 注意:新商户可能无法使用此API,建议使用新版单笔转账API + */ + public void batchTransferExample() { + try { + // 构建转账明细列表 + TransferBatchesRequest.TransferDetail detail1 = TransferBatchesRequest.TransferDetail.newBuilder() + .outDetailNo("DETAIL_" + System.currentTimeMillis() + "_1") + .transferAmount(100) // 1元 + .transferRemark("佣金1") + .openid("oUpF8uMuAJO_M2pxb1Q9zNjWeS6o") + .userName("张三") + .build(); + + TransferBatchesRequest.TransferDetail detail2 = TransferBatchesRequest.TransferDetail.newBuilder() + .outDetailNo("DETAIL_" + System.currentTimeMillis() + "_2") + .transferAmount(200) // 2元 + .transferRemark("佣金2") + .openid("oUpF8uMuAJO_M2pxb1Q9zNjWeS6p") + .userName("李四") + .build(); + + // 构建批量转账请求 + TransferBatchesRequest batchRequest = TransferBatchesRequest.newBuilder() + .appid("wx1234567890123456") + .outBatchNo("BATCH_" + System.currentTimeMillis()) + .batchName("佣金批量发放") + .batchRemark("2024年1月佣金") + .totalAmount(300) // 总金额:3元 + .totalNum(2) // 总笔数:2笔 + .transferDetailList(java.util.Arrays.asList(detail1, detail2)) + .transferSceneId("1005") // 转账场景ID + .build(); + + // 发起批量转账 + TransferBatchesResult batchResult = transferService.transferBatches(batchRequest); + + System.out.println("=== 批量转账发起成功 ==="); + System.out.println("商户批次单号: " + batchResult.getOutBatchNo()); + System.out.println("微信批次单号: " + batchResult.getBatchId()); + System.out.println("批次状态: " + batchResult.getBatchStatus()); + + } catch (WxPayException e) { + System.err.println("批量转账失败: " + e.getMessage()); + } + } + + /** + * 使用配置示例 + */ + public static void main(String[] args) { + // 配置微信支付参数 + WxPayConfig config = new WxPayConfig(); + config.setAppId("wx1234567890123456"); // 应用ID + config.setMchId("1234567890"); // 商户ID + config.setApiV3Key("your_api_v3_key_32_chars"); // APIv3密钥 + config.setPrivateKeyPath("path/to/private.pem"); // 商户私钥文件路径 + config.setCertSerialNo("your_certificate_serial"); // 商户证书序列号 + + // 创建示例实例 + NewTransferApiExample example = new NewTransferApiExample(config); + + // 运行示例 + System.out.println("新版商户转账API使用示例"); + System.out.println("==============================="); + + // 1. 发起单笔转账 + example.transferExample(); + + // 2. 查询转账结果 + // example.queryByOutBillNoExample(); + + // 3. 撤销转账 + // example.cancelTransferExample(); + + // 4. 批量转账(传统API) + // example.batchTransferExample(); + } +} \ No newline at end of file diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/WxEntrustPapService.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/WxEntrustPapService.java index 168e43696a..1a1ddb120a 100644 --- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/WxEntrustPapService.java +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/WxEntrustPapService.java @@ -166,4 +166,22 @@ public interface WxEntrustPapService { * @throws WxPayException the wx pay exception */ WxWithholdOrderQueryResult papOrderQuery(WxWithholdOrderQueryRequest wxWithholdOrderQueryRequest) throws WxPayException; + + /** + *
+   *   签约、解约结果通知解析
+   *   详见:签约、解约结果通知
+   *   注意:
+   *    1、同样的通知可能会多次发送给商户系统。商户系统必须能够正确处理重复的通知。 推荐的做法是:当商户系统收到通知进行处理时,先检查对应业务数据的状态,并判断该通知是否已经处理。如果未处理,则再进行处理;如果已处理,则直接返回结果成功。在对业务数据进行状态检查和处理之前,要采用数据锁进行并发控制,以避免函数重入造成的数据混乱。
+   *    2、如果在所有通知频率(0/10/10/10/30/30/30/300/300/300/300/300/300/300/300/300/300/300/300/300/300/300/300/300/300/300/300/300/300/300(单位:秒))后没有收到微信侧回调,商户应调用查询订单接口确认订单状态。
+   *   特别提醒:
+   *    1、商户系统对于签约、解约结果通知的内容一定要做签名验证,并校验返回的商户协议号和用户openid信息是否一致,防止数据泄露导致出现“假通知”,造成损失。
+   *    2、当收到通知进行处理时,首先检查对应业务数据的状态,判断该通知是否已经处理过,如果没有处理过再进行处理,如果处理过直接返回结果成功。在对业务数据进行状态检查和处理之前,要采用数据锁进行并发控制,以避免函数重入造成的数据混乱。
+   * 
+ * + * @param xmlData the wx withhold order query request + * @return wx sign result + * @throws WxPayException the wx pay exception + */ + WxSignQueryResult parseSignNotifyResult(String xmlData) throws WxPayException; } diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/WxPayService.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/WxPayService.java index 8ceac2b6ba..c73fb843e8 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 @@ -6,6 +6,7 @@ import com.github.binarywang.wxpay.bean.request.*; import com.github.binarywang.wxpay.bean.result.*; import com.github.binarywang.wxpay.bean.result.enums.TradeTypeEnum; +import com.github.binarywang.wxpay.bean.result.enums.GlobalTradeTypeEnum; import com.github.binarywang.wxpay.bean.transfer.TransferBillsNotifyResult; import com.github.binarywang.wxpay.config.WxPayConfig; import com.github.binarywang.wxpay.constant.WxPayConstants; @@ -640,6 +641,17 @@ public interface WxPayService { */ T createPartnerOrderV3(TradeTypeEnum tradeType, WxPayPartnerUnifiedOrderV3Request request) throws WxPayException; + /** + * 境外微信支付调用统一下单接口,并组装生成支付所需参数对象. + * + * @param 请使用{@link WxPayUnifiedOrderV3Result}里的内部类或字段 + * @param tradeType the global trade type + * @param request 境外统一下单请求参数 + * @return 返回 {@link WxPayUnifiedOrderV3Result}里的内部类或字段 + * @throws WxPayException the wx pay exception + */ + T createOrderV3Global(GlobalTradeTypeEnum tradeType, WxPayUnifiedOrderV3GlobalRequest request) throws WxPayException; + /** * 在发起微信支付前,需要调用统一下单接口,获取"预支付交易会话标识" * @@ -660,6 +672,16 @@ public interface WxPayService { */ WxPayUnifiedOrderV3Result unifiedOrderV3(TradeTypeEnum tradeType, WxPayUnifiedOrderV3Request request) throws WxPayException; + /** + * 境外微信支付在发起支付前,需要调用统一下单接口,获取"预支付交易会话标识" + * + * @param tradeType the global trade type + * @param request 境外请求对象,注意一些参数如appid、mchid等不用设置,方法内会自动从配置对象中获取到(前提是对应配置中已经设置) + * @return the wx pay unified order result + * @throws WxPayException the wx pay exception + */ + WxPayUnifiedOrderV3Result unifiedOrderV3Global(GlobalTradeTypeEnum tradeType, WxPayUnifiedOrderV3GlobalRequest request) throws WxPayException; + /** *
    * 合单支付API(APP支付、JSAPI支付、H5支付、NATIVE支付).
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 077562f03c..f32083a632 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
@@ -11,6 +11,7 @@
 import com.github.binarywang.wxpay.bean.request.*;
 import com.github.binarywang.wxpay.bean.result.*;
 import com.github.binarywang.wxpay.bean.result.enums.TradeTypeEnum;
+import com.github.binarywang.wxpay.bean.result.enums.GlobalTradeTypeEnum;
 import com.github.binarywang.wxpay.bean.transfer.TransferBillsNotifyResult;
 import com.github.binarywang.wxpay.config.WxPayConfig;
 import com.github.binarywang.wxpay.config.WxPayConfigHolder;
@@ -30,11 +31,10 @@
 import com.google.gson.GsonBuilder;
 import lombok.Getter;
 import lombok.Setter;
+import lombok.extern.slf4j.Slf4j;
 import me.chanjar.weixin.common.error.WxRuntimeException;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.commons.lang3.reflect.ConstructorUtils;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 import java.io.File;
 import java.io.IOException;
@@ -59,14 +59,13 @@
  *
  * @author Binary Wang
  */
+@Slf4j
 public abstract class BaseWxPayServiceImpl implements WxPayService {
   private static final String TOTAL_FUND_COUNT = "资金流水总笔数";
 
   private static final Gson GSON = new GsonBuilder().create();
 
-  final Logger log = LoggerFactory.getLogger(this.getClass());
-
-  static ThreadLocal wxApiData = new ThreadLocal<>();
+  static final ThreadLocal wxApiData = new ThreadLocal<>();
 
 
   @Setter
@@ -209,7 +208,7 @@ public WxPayService switchoverTo(String mchId, String appId) {
     throw new WxRuntimeException(String.format("无法找到对应mchId=【%s】,appId=【%s】的商户号配置信息,请核实!", mchId, appId));
   }
 
-  private String getConfigKey(String mchId, String appId) {
+  public String getConfigKey(String mchId, String appId) {
     return mchId + "_" + appId;
   }
 
@@ -219,9 +218,9 @@ public String getPayBaseUrl() {
       if (StringUtils.isNotBlank(this.getConfig().getApiV3Key())) {
         throw new WxRuntimeException("微信支付V3 目前不支持沙箱模式!");
       }
-      return this.getConfig().getPayBaseUrl() + "/xdc/apiv2sandbox";
+      return this.getConfig().getApiHostUrl() + "/xdc/apiv2sandbox";
     }
-    return this.getConfig().getPayBaseUrl();
+    return this.getConfig().getApiHostUrl();
   }
 
   @Override
@@ -251,7 +250,7 @@ public WxPayRefundResult refundV2(WxPayRefundRequest request) throws WxPayExcept
   @Override
   public WxPayRefundV3Result refundV3(WxPayRefundV3Request request) throws WxPayException {
     String url = String.format("%s/v3/refund/domestic/refunds", this.getPayBaseUrl());
-    String response = this.postV3(url, GSON.toJson(request));
+    String response = this.postV3WithWechatpaySerial(url, GSON.toJson(request));
     return GSON.fromJson(response, WxPayRefundV3Result.class);
   }
 
@@ -294,21 +293,21 @@ public WxPayRefundQueryResult refundQueryV2(WxPayRefundQueryRequest request) thr
   @Override
   public WxPayRefundQueryV3Result refundQueryV3(String outRefundNo) throws WxPayException {
     String url = String.format("%s/v3/refund/domestic/refunds/%s", this.getPayBaseUrl(), outRefundNo);
-    String response = this.getV3(url);
+    String response = this.getV3WithWechatPaySerial(url);
     return GSON.fromJson(response, WxPayRefundQueryV3Result.class);
   }
 
   @Override
   public WxPayRefundQueryV3Result refundQueryV3(WxPayRefundQueryV3Request request) throws WxPayException {
     String url = String.format("%s/v3/refund/domestic/refunds/%s", this.getPayBaseUrl(), request.getOutRefundNo());
-    String response = this.getV3(url);
+    String response = this.getV3WithWechatPaySerial(url);
     return GSON.fromJson(response, WxPayRefundQueryV3Result.class);
   }
 
   @Override
   public WxPayRefundQueryV3Result refundPartnerQueryV3(WxPayRefundQueryV3Request request) throws WxPayException {
     String url = String.format("%s/v3/refund/domestic/refunds/%s?sub_mchid=%s", this.getPayBaseUrl(), request.getOutRefundNo(), request.getSubMchid());
-    String response = this.getV3(url);
+    String response = this.getV3WithWechatPaySerial(url);
     return GSON.fromJson(response, WxPayRefundQueryV3Result.class);
   }
 
@@ -323,14 +322,13 @@ public WxPayOrderNotifyResult parseOrderNotifyResult(String xmlData, String sign
       log.debug("微信支付异步通知请求参数:{}", xmlData);
       WxPayOrderNotifyResult result = WxPayOrderNotifyResult.fromXML(xmlData);
       if (signType == null) {
-        String configKey = this.getConfigKey(result.getMchId(), result.getAppid());
+        this.switchover(result.getMchId(), result.getAppid());
         if (result.getSignType() != null) {
           // 如果解析的通知对象中signType有值,则使用它进行验签
           signType = result.getSignType();
-        } else if (configMap.get(configKey).getSignType() != null) {
+        } else if (this.getConfig().getSignType() != null) {
           // 如果配置中signType有值,则使用它进行验签
-          signType = configMap.get(configKey).getSignType();
-          this.switchover(result.getMchId(), result.getAppid());
+          signType = this.getConfig().getSignType();
         }
       }
 
@@ -524,7 +522,7 @@ public WxPayOrderQueryV3Result queryOrderV3(WxPayOrderQueryV3Request request) th
       url = String.format("%s/v3/pay/transactions/id/%s", this.getPayBaseUrl(), request.getTransactionId());
     }
     String query = String.format("?mchid=%s", request.getMchid());
-    String response = this.getV3(url + query);
+    String response = this.getV3WithWechatPaySerial(url + query);
     return GSON.fromJson(response, WxPayOrderQueryV3Result.class);
   }
 
@@ -549,14 +547,14 @@ public WxPayPartnerOrderQueryV3Result queryPartnerOrderV3(WxPayPartnerOrderQuery
       url = String.format("%s/v3/pay/partner/transactions/id/%s", this.getPayBaseUrl(), request.getTransactionId());
     }
     String query = String.format("?sp_mchid=%s&sub_mchid=%s", request.getSpMchId(), request.getSubMchId());
-    String response = this.getV3(url + query);
+    String response = this.getV3WithWechatPaySerial(url + query);
     return GSON.fromJson(response, WxPayPartnerOrderQueryV3Result.class);
   }
 
   @Override
   public CombineQueryResult queryCombine(String combineOutTradeNo) throws WxPayException {
     String url = String.format("%s/v3/combine-transactions/out-trade-no/%s", this.getPayBaseUrl(), combineOutTradeNo);
-    String response = this.getV3(url);
+    String response = this.getV3WithWechatPaySerial(url);
     return GSON.fromJson(response, CombineQueryResult.class);
   }
 
@@ -610,7 +608,7 @@ public void closeOrderV3(WxPayOrderCloseV3Request request) throws WxPayException
       request.setMchid(this.getConfig().getMchId());
     }
     String url = String.format("%s/v3/pay/transactions/out-trade-no/%s/close", this.getPayBaseUrl(), request.getOutTradeNo());
-    this.postV3(url, GSON.toJson(request));
+    this.postV3WithWechatpaySerial(url, GSON.toJson(request));
   }
 
   @Override
@@ -622,13 +620,13 @@ public void closePartnerOrderV3(WxPayPartnerOrderCloseV3Request request) throws
       request.setSubMchId(this.getConfig().getSubMchId());
     }
     String url = String.format("%s/v3/pay/partner/transactions/out-trade-no/%s/close", this.getPayBaseUrl(), request.getOutTradeNo());
-    this.postV3(url, GSON.toJson(request));
+    this.postV3WithWechatpaySerial(url, GSON.toJson(request));
   }
 
   @Override
   public void closeCombine(CombineCloseRequest request) throws WxPayException {
     String url = String.format("%s/v3/combine-transactions/out-trade-no/%s/close", this.getPayBaseUrl(), request.getCombineOutTradeNo());
-    this.postV3(url, GSON.toJson(request));
+    this.postV3WithWechatpaySerial(url, GSON.toJson(request));
   }
 
   @Override
@@ -749,6 +747,14 @@ public  T createPartnerOrderV3(TradeTypeEnum tradeType, WxPayPartnerUnifiedOr
     return result.getPayInfo(tradeType, appId, request.getSubMchId(), this.getConfig().getPrivateKey());
   }
 
+  @Override
+  public  T createOrderV3Global(GlobalTradeTypeEnum tradeType, WxPayUnifiedOrderV3GlobalRequest request) throws WxPayException {
+    WxPayUnifiedOrderV3Result result = this.unifiedOrderV3Global(tradeType, request);
+    // Convert GlobalTradeTypeEnum to TradeTypeEnum for getPayInfo method
+    TradeTypeEnum domesticTradeType = TradeTypeEnum.valueOf(tradeType.name());
+    return result.getPayInfo(domesticTradeType, request.getAppid(), request.getMchid(), this.getConfig().getPrivateKey());
+  }
+
   @Override
   public WxPayUnifiedOrderV3Result unifiedPartnerOrderV3(TradeTypeEnum tradeType, WxPayPartnerUnifiedOrderV3Request request) throws WxPayException {
     if (StringUtils.isBlank(request.getSpAppid())) {
@@ -772,7 +778,7 @@ public WxPayUnifiedOrderV3Result unifiedPartnerOrderV3(TradeTypeEnum tradeType,
     }
 
     String url = this.getPayBaseUrl() + tradeType.getBasePartnerUrl();
-    String response = this.postV3(url, GSON.toJson(request));
+    String response = this.postV3WithWechatpaySerial(url, GSON.toJson(request));
     return GSON.fromJson(response, WxPayUnifiedOrderV3Result.class);
   }
 
@@ -789,7 +795,29 @@ public WxPayUnifiedOrderV3Result unifiedOrderV3(TradeTypeEnum tradeType, WxPayUn
     }
 
     String url = this.getPayBaseUrl() + tradeType.getPartnerUrl();
-    String response = this.postV3(url, GSON.toJson(request));
+    String response = this.postV3WithWechatpaySerial(url, GSON.toJson(request));
+    return GSON.fromJson(response, WxPayUnifiedOrderV3Result.class);
+  }
+
+  @Override
+  public WxPayUnifiedOrderV3Result unifiedOrderV3Global(GlobalTradeTypeEnum tradeType, WxPayUnifiedOrderV3GlobalRequest request) throws WxPayException {
+    if (StringUtils.isBlank(request.getAppid())) {
+      request.setAppid(this.getConfig().getAppId());
+    }
+    if (StringUtils.isBlank(request.getMchid())) {
+      request.setMchid(this.getConfig().getMchId());
+    }
+    if (StringUtils.isBlank(request.getNotifyUrl())) {
+      request.setNotifyUrl(this.getConfig().getNotifyUrl());
+    }
+    if (StringUtils.isBlank(request.getTradeType())) {
+      request.setTradeType(tradeType.name());
+    }
+
+    // Use global WeChat Pay base URL for overseas payments
+    String globalBaseUrl = "https://apihk.mch.weixin.qq.com";
+    String url = globalBaseUrl + tradeType.getUrl();
+    String response = this.postV3WithWechatpaySerial(url, GSON.toJson(request));
     return GSON.fromJson(response, WxPayUnifiedOrderV3Result.class);
   }
 
@@ -802,7 +830,7 @@ public CombineTransactionsResult combine(TradeTypeEnum tradeType, CombineTransac
       request.setCombineMchid(this.getConfig().getMchId());
     }
     String url = this.getPayBaseUrl() + tradeType.getCombineUrl();
-    String response = this.postV3(url, GSON.toJson(request));
+    String response = this.postV3WithWechatpaySerial(url, GSON.toJson(request));
     return GSON.fromJson(response, CombineTransactionsResult.class);
   }
 
@@ -1115,7 +1143,7 @@ public WxPayApplyBillV3Result applyTradeBill(WxPayApplyTradeBillV3Request reques
     } else {
       url = String.format("%s/v3/bill/tradebill?bill_date=%s&bill_type=%s&tar_type=%s", this.getPayBaseUrl(), request.getBillDate(), request.getBillType(), request.getTarType());
     }
-    String response = this.getV3(url);
+    String response = this.getV3WithWechatPaySerial(url);
     return GSON.fromJson(response, WxPayApplyBillV3Result.class);
   }
 
@@ -1127,7 +1155,7 @@ public WxPayApplyBillV3Result applyFundFlowBill(WxPayApplyFundFlowBillV3Request
     } else {
       url = String.format("%s/v3/bill/fundflowbill?bill_date=%s&account_type=%s&tar_type=%s", this.getPayBaseUrl(), request.getBillDate(), request.getAccountType(), request.getTarType());
     }
-    String response = this.getV3(url);
+    String response = this.getV3WithWechatPaySerial(url);
     return GSON.fromJson(response, WxPayApplyBillV3Result.class);
   }
 
@@ -1156,7 +1184,7 @@ public WxPayCodepayResult codepay(WxPayCodepayRequest request) throws WxPayExcep
       request.setMchid(this.getConfig().getMchId());
     }
     String url = String.format("%s/v3/pay/transactions/codepay", this.getPayBaseUrl());
-    String body = this.postV3(url, GSON.toJson(request));
+    String body = this.postV3WithWechatpaySerial(url, GSON.toJson(request));
     return GSON.fromJson(body, WxPayCodepayResult.class);
   }
 
@@ -1182,7 +1210,7 @@ public WxPayOrderReverseV3Result reverseOrderV3(WxPayOrderReverseV3Request reque
     }
     // 拼接参数请求路径并发送
     String url = String.format("%s/v3/pay/transactions/out-trade-no/%s/reverse", this.getPayBaseUrl(), request.getOutTradeNo());
-    String response = this.postV3(url, GSON.toJson(request));
+    String response = this.postV3WithWechatpaySerial(url, GSON.toJson(request));
     return GSON.fromJson(response, WxPayOrderReverseV3Result.class);
   }
 
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/BrandMerchantTransferServiceImpl.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/BrandMerchantTransferServiceImpl.java
index a9ee5d236d..dff607922b 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/BrandMerchantTransferServiceImpl.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/BrandMerchantTransferServiceImpl.java
@@ -44,7 +44,7 @@ public BrandBatchesQueryResult queryBrandWxBatches(BrandWxBatchesQueryRequest re
     if (request.getNeedQueryDetail() != null) {
       url = String.format("%s?need_query_detail=%b", url, request.getNeedQueryDetail());
     }
-    if (request.getDetailState() != null && request.getDetailState().length() != 0) {
+    if (request.getDetailState() != null && !request.getDetailState().isEmpty()) {
       url = String.format("%s&detail_state=%s", url, request.getDetailState());
     }
 
@@ -68,7 +68,7 @@ public BrandBatchesQueryResult queryBrandMerchantBatches(BrandMerchantBatchesQue
     if (request.getNeedQueryDetail() != null) {
       url = String.format("%s?need_query_detail=%b", url, request.getNeedQueryDetail());
     }
-    if (request.getDetailState() != null && request.getDetailState().length() != 0) {
+    if (request.getDetailState() != null && !request.getDetailState().isEmpty()) {
       url = String.format("%s&detail_state=%s", url, request.getDetailState());
     }
 
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/CustomDeclarationServiceImpl.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/CustomDeclarationServiceImpl.java
index d25ed7c0a2..f596d4cf8c 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/CustomDeclarationServiceImpl.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/CustomDeclarationServiceImpl.java
@@ -4,7 +4,6 @@
 import com.github.binarywang.wxpay.exception.WxPayException;
 import com.github.binarywang.wxpay.service.CustomDeclarationService;
 import com.github.binarywang.wxpay.service.WxPayService;
-import com.github.binarywang.wxpay.v3.util.RsaCryptoUtil;
 import com.google.gson.Gson;
 import com.google.gson.GsonBuilder;
 import lombok.RequiredArgsConstructor;
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/EcommerceServiceImpl.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/EcommerceServiceImpl.java
index 36dc08d6f6..479520d7f7 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/EcommerceServiceImpl.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/EcommerceServiceImpl.java
@@ -294,7 +294,7 @@ public RefundQueryResult queryRefundByRefundId(String subMchid, String refundId)
   @Override
   public ReturnAdvanceResult refundsReturnAdvance(String subMchid, String refundId) throws WxPayException {
     String url = String.format("%s/v3/ecommerce/refunds/%s/return-advance", this.payService.getPayBaseUrl(), refundId);
-    Map request = new HashMap();
+    Map request = new HashMap<>();
     request.put("sub_mchid",subMchid);
     String response = this.payService.postV3(url, GSON.toJson(request));
     return GSON.fromJson(response, ReturnAdvanceResult.class);
@@ -489,7 +489,7 @@ private String parseURLPair(Object o) {
   public static Map getObjectToMap(Object obj) {
     try {
       Map result = new LinkedHashMap<>();
-      final Class beanClass = obj.getClass();
+      final Class beanClass = obj.getClass();
       final BeanInfo beanInfo = Introspector.getBeanInfo(beanClass);
       final PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
       if (propertyDescriptors != null) {
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/MarketingFavorServiceImpl.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/MarketingFavorServiceImpl.java
index a10bbbb085..0f84d5f126 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/MarketingFavorServiceImpl.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/MarketingFavorServiceImpl.java
@@ -34,7 +34,9 @@ public FavorStocksCreateResult createFavorStocksV3(FavorStocksCreateRequest requ
     String url = String.format("%s/v3/marketing/favor/coupon-stocks", this.payService.getPayBaseUrl());
     RsaCryptoUtil.encryptFields(request, this.payService.getConfig().getVerifier().getValidCertificate());
     String result = this.payService.postV3WithWechatpaySerial(url, GSON.toJson(request));
-    return GSON.fromJson(result, FavorStocksCreateResult.class);
+    FavorStocksCreateResult favorStocksCreateResult = GSON.fromJson(result, FavorStocksCreateResult.class);
+    favorStocksCreateResult.setRawJsonString(result);
+    return favorStocksCreateResult;
   }
 
   @Override
@@ -75,7 +77,9 @@ public FavorStocksGetResult getFavorStocksV3(String stockId, String stockCreator
     String url = String.format("%s/v3/marketing/favor/stocks/%s", this.payService.getPayBaseUrl(), stockId);
     String query = String.format("?stock_creator_mchid=%s", stockCreatorMchid);
     String result = this.payService.getV3(url + query);
-    return GSON.fromJson(result, FavorStocksGetResult.class);
+    FavorStocksGetResult favorStocksGetResult = GSON.fromJson(result, FavorStocksGetResult.class);
+    favorStocksGetResult.setRawJsonString(result);
+    return favorStocksGetResult;
   }
 
   @Override
@@ -83,7 +87,9 @@ public FavorCouponsGetResult getFavorCouponsV3(String couponId, String appid, St
     String url = String.format("%s/v3/marketing/favor/users/%s/coupons/%s", this.payService.getPayBaseUrl(), openid, couponId);
     String query = String.format("?appid=%s", appid);
     String result = this.payService.getV3(url + query);
-    return GSON.fromJson(result, FavorCouponsGetResult.class);
+    FavorCouponsGetResult favorCouponsGetResult = GSON.fromJson(result, FavorCouponsGetResult.class);
+    favorCouponsGetResult.setRawJsonString(result);
+    return favorCouponsGetResult;
   }
 
   @Override
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/MerchantTransferServiceImpl.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/MerchantTransferServiceImpl.java
index 8c4568a0f8..8974ca7e2b 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/MerchantTransferServiceImpl.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/MerchantTransferServiceImpl.java
@@ -47,7 +47,7 @@ public BatchesQueryResult queryWxBatches(WxBatchesQueryRequest request) throws W
     if (request.getLimit() != null) {
       url = String.format("%s&limit=%d", url, request.getLimit());
     }
-    if (request.getDetailStatus() != null && request.getDetailStatus().length() != 0) {
+    if (request.getDetailStatus() != null && !request.getDetailStatus().isEmpty()) {
       url = String.format("%s&detail_status=%s", url, request.getDetailStatus());
     }
 
@@ -74,7 +74,7 @@ public BatchesQueryResult queryMerchantBatches(MerchantBatchesQueryRequest reque
     if (request.getLimit() != null) {
       url = String.format("%s&limit=%d", url, request.getLimit());
     }
-    if (request.getDetailStatus() != null && request.getDetailStatus().length() != 0) {
+    if (request.getDetailStatus() != null && !request.getDetailStatus().isEmpty()) {
       url = String.format("%s&detail_status=%s", url, request.getDetailStatus());
     }
 
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/ProfitSharingServiceImpl.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/ProfitSharingServiceImpl.java
index 6be5ffc8c1..afaa45440a 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/ProfitSharingServiceImpl.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/ProfitSharingServiceImpl.java
@@ -7,6 +7,7 @@
 import com.github.binarywang.wxpay.bean.profitsharing.result.*;
 import com.github.binarywang.wxpay.bean.result.BaseWxPayResult;
 import com.github.binarywang.wxpay.exception.WxPayException;
+import com.github.binarywang.wxpay.exception.WxSignTestException;
 import com.github.binarywang.wxpay.service.ProfitSharingService;
 import com.github.binarywang.wxpay.service.WxPayService;
 import com.github.binarywang.wxpay.v3.auth.Verifier;
@@ -293,7 +294,11 @@ private ProfitSharingNotifyV3Response parseNotifyData(String data, SignatureHead
    * @return true:校验通过 false:校验不通过
    */
   private boolean verifyNotifySign(SignatureHeader header, String data) throws WxPayException {
-    String beforeSign = String.format("%s%n%s%n%s%n", header.getTimeStamp(), header.getNonce(), data);
+    String wxPaySign = header.getSignature();
+    if (wxPaySign.startsWith("WECHATPAY/SIGNTEST/")) {
+      throw new WxSignTestException("微信支付签名探测流量");
+    }
+    String beforeSign = String.format("%s\n%s\n%s\n", header.getTimeStamp(), header.getNonce(), data);
     Verifier verifier = this.payService.getConfig().getVerifier();
     if (verifier == null) {
       throw new WxPayException("证书检验对象为空");
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/TransferServiceImpl.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/TransferServiceImpl.java
index f43c887c16..038af32b87 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/TransferServiceImpl.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/TransferServiceImpl.java
@@ -87,7 +87,7 @@ public TransferBatchDetailResult transferBatchesOutBatchNoDetail(String outBatch
   @Override
   public TransferBillsResult transferBills(TransferBillsRequest request) throws WxPayException {
     String url = String.format("%s/v3/fund-app/mch-transfer/transfer-bills", this.payService.getPayBaseUrl());
-    if (request.getUserName() != null && request.getUserName().length() > 0) {
+    if (request.getUserName() != null && !request.getUserName().isEmpty()) {
       X509Certificate validCertificate = this.payService.getConfig().getVerifier().getValidCertificate();
       RsaCryptoUtil.encryptFields(request, validCertificate);
     }
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/WxEntrustPapServiceImpl.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/WxEntrustPapServiceImpl.java
index 951c1d5a8c..4960ea94c5 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/WxEntrustPapServiceImpl.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/WxEntrustPapServiceImpl.java
@@ -8,10 +8,13 @@
 import com.github.binarywang.wxpay.service.WxPayService;
 import com.github.binarywang.wxpay.util.SignUtils;
 import lombok.RequiredArgsConstructor;
+import lombok.SneakyThrows;
 import lombok.extern.slf4j.Slf4j;
 import me.chanjar.weixin.common.util.json.WxGsonBuilder;
+import org.apache.commons.lang3.StringUtils;
 
 import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
 
 /**
  * @author chenliang
@@ -25,26 +28,40 @@ public class WxEntrustPapServiceImpl implements WxEntrustPapService {
 
 
   @Override
+  @SneakyThrows
   public String mpSign(WxMpEntrustRequest wxMpEntrustRequest) throws WxPayException {
+    wxMpEntrustRequest.checkAndSign(payService.getConfig());
     StringBuilder signStrTemp = new StringBuilder(payService.getPayBaseUrl() + "/papay/entrustweb");
     signStrTemp.append("?appid=").append(wxMpEntrustRequest.getAppid());
     signStrTemp.append("&contract_code=").append(wxMpEntrustRequest.getContractCode());
-    signStrTemp.append("&contract_display_account=").append(URLEncoder.encode(wxMpEntrustRequest.getContractDisplayAccount()));
-    signStrTemp.append("&mch_id=").append(wxMpEntrustRequest.getMchId()).append("¬ify_url=").append(URLEncoder.encode(wxMpEntrustRequest.getNotifyUrl()));
-    signStrTemp.append("&plan_id=").append(wxMpEntrustRequest.getPlanId()).append("&outerid=").append(URLEncoder.encode(wxMpEntrustRequest.getOuterId()));
-    signStrTemp.append("&request_serial=").append(wxMpEntrustRequest.getRequestSerial()).append("×tamp=").append(wxMpEntrustRequest.getTimestamp());
-    signStrTemp.append("&version=").append(wxMpEntrustRequest.getVersion()).append("&return_web=").append(wxMpEntrustRequest.getReturnWeb()).append("&sign=").append(wxMpEntrustRequest.getSign());
-
+    signStrTemp.append("&contract_display_account=")
+      .append(URLEncoder.encode(wxMpEntrustRequest.getContractDisplayAccount(), StandardCharsets.UTF_8.name()));
+    signStrTemp.append("&mch_id=").append(wxMpEntrustRequest.getMchId()).append("¬ify_url=")
+      .append(URLEncoder.encode(wxMpEntrustRequest.getNotifyUrl(), StandardCharsets.UTF_8.name()));
+    signStrTemp.append("&plan_id=").append(wxMpEntrustRequest.getPlanId());
+    signStrTemp.append("&request_serial=").append(wxMpEntrustRequest.getRequestSerial()).append("×tamp=")
+      .append(wxMpEntrustRequest.getTimestamp());
+    // 根据微信支付文档,returnWeb字段只在值为1时需要添加到URL参数中,表示返回签约页面的referrer url
+    if (wxMpEntrustRequest.getReturnWeb() != null && wxMpEntrustRequest.getReturnWeb() == 1) {
+      signStrTemp.append("&return_web=").append(wxMpEntrustRequest.getReturnWeb());
+    }
+    if (StringUtils.isNotEmpty(wxMpEntrustRequest.getOuterId())) {
+      signStrTemp.append("&outerid=").append(URLEncoder.encode(wxMpEntrustRequest.getOuterId(), StandardCharsets.UTF_8.name()));
+    }
+    signStrTemp.append("&version=").append(wxMpEntrustRequest.getVersion()).append("&sign=")
+      .append(wxMpEntrustRequest.getSign());
     return signStrTemp.toString();
   }
 
   @Override
+  @SneakyThrows
   public String maSign(WxMaEntrustRequest wxMaEntrustRequest) throws WxPayException {
     wxMaEntrustRequest.checkAndSign(payService.getConfig());
-    wxMaEntrustRequest.setNotifyUrl(URLEncoder.encode(wxMaEntrustRequest.getNotifyUrl()));
+    wxMaEntrustRequest.setNotifyUrl(URLEncoder.encode(wxMaEntrustRequest.getNotifyUrl(), StandardCharsets.UTF_8.name()));
     return wxMaEntrustRequest.toString();
   }
 
+  @SneakyThrows
   @Override
   public WxH5EntrustResult h5Sign(WxH5EntrustRequest wxH5EntrustRequest) throws WxPayException {
     wxH5EntrustRequest.checkAndSign(payService.getConfig());
@@ -63,10 +80,15 @@ public WxH5EntrustResult h5Sign(WxH5EntrustRequest wxH5EntrustRequest) throws Wx
     StringBuilder strBuilder = new StringBuilder(url);
     strBuilder.append("?appid=").append(wxH5EntrustRequest.getAppid());
     strBuilder.append("&contract_code=").append(wxH5EntrustRequest.getContractCode());
-    strBuilder.append("&contract_display_account=").append(URLEncoder.encode(wxH5EntrustRequest.getContractDisplayAccount()));
-    strBuilder.append("&mch_id=").append(wxH5EntrustRequest.getMchId()).append("¬ify_url=").append(URLEncoder.encode(wxH5EntrustRequest.getNotifyUrl()));
-    strBuilder.append("&plan_id=").append(wxH5EntrustRequest.getPlanId()).append("&outerid=").append(URLEncoder.encode(wxH5EntrustRequest.getOuterId()));
-    strBuilder.append("&return_appid=").append(wxH5EntrustRequest.getReturnAppid());
+    strBuilder.append("&contract_display_account=").append(URLEncoder.encode(wxH5EntrustRequest.getContractDisplayAccount(), StandardCharsets.UTF_8.name()));
+    strBuilder.append("&mch_id=").append(wxH5EntrustRequest.getMchId()).append("¬ify_url=").append(URLEncoder.encode(wxH5EntrustRequest.getNotifyUrl(), StandardCharsets.UTF_8.name()));
+    strBuilder.append("&plan_id=").append(wxH5EntrustRequest.getPlanId());
+    if (StringUtils.isNotEmpty(wxH5EntrustRequest.getOuterId())) {
+      strBuilder.append("&outerid=").append(URLEncoder.encode(wxH5EntrustRequest.getOuterId(), StandardCharsets.UTF_8.name()));
+    }
+    if (StringUtils.isNotEmpty(wxH5EntrustRequest.getReturnAppid())) {
+      strBuilder.append("&return_appid=").append(wxH5EntrustRequest.getReturnAppid());
+    }
     strBuilder.append("&clientip=").append(wxH5EntrustRequest.getClientIp());
     strBuilder.append("&request_serial=").append(wxH5EntrustRequest.getRequestSerial()).append("×tamp=").append(wxH5EntrustRequest.getTimestamp());
     strBuilder.append("&version=").append(wxH5EntrustRequest.getVersion()).append("&sign=").append(sign);
@@ -148,4 +170,11 @@ public WxWithholdOrderQueryResult papOrderQuery(WxWithholdOrderQueryRequest wxWi
     wxWithholdOrderQueryResult.checkResult(payService, wxWithholdOrderQueryRequest.getSignType(), true);
     return wxWithholdOrderQueryResult;
   }
+
+  @Override
+  public WxSignQueryResult parseSignNotifyResult(String xmlData) throws WxPayException {
+    WxSignQueryResult result = BaseWxPayResult.fromXML(xmlData, WxSignQueryResult.class);
+    result.checkResult(payService, WxPayConstants.SignType.MD5, true);
+    return result;
+  }
 }
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/WxPayServiceApacheHttpImpl.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/WxPayServiceApacheHttpImpl.java
index 7fd7939797..977a2856fe 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/WxPayServiceApacheHttpImpl.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/WxPayServiceApacheHttpImpl.java
@@ -6,6 +6,8 @@
 import com.github.binarywang.wxpay.v3.WxPayV3DownloadHttpGet;
 import com.google.gson.JsonElement;
 import com.google.gson.JsonObject;
+import lombok.extern.slf4j.Slf4j;
+import me.chanjar.weixin.common.util.http.apache.ByteArrayResponseHandler;
 import me.chanjar.weixin.common.util.json.GsonParser;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.http.*;
@@ -39,30 +41,28 @@
  *
  * @author Binary Wang
  */
+@Slf4j
 public class WxPayServiceApacheHttpImpl extends BaseWxPayServiceImpl {
 
   private static final String ACCEPT = "Accept";
   private static final String CONTENT_TYPE = "Content-Type";
   private static final String APPLICATION_JSON = "application/json";
+  private static final String WECHAT_PAY_SERIAL = "Wechatpay-Serial";
 
   @Override
   public byte[] postForBytes(String url, String requestStr, boolean useKey) throws WxPayException {
     try {
-      HttpClientBuilder httpClientBuilder = createHttpClientBuilder(useKey);
       HttpPost httpPost = this.createHttpPost(url, requestStr);
-      try (CloseableHttpClient httpClient = httpClientBuilder.build()) {
-        try (CloseableHttpResponse response = httpClient.execute(httpPost)) {
-          final byte[] bytes = EntityUtils.toByteArray(response.getEntity());
-          final String responseData = Base64.getEncoder().encodeToString(bytes);
-          this.log.info("\n【请求地址】:{}\n【请求数据】:{}\n【响应数据(Base64编码后)】:{}", url, requestStr, responseData);
-          wxApiData.set(new WxPayApiData(url, requestStr, responseData, null));
-          return bytes;
-        }
-      } finally {
-        httpPost.releaseConnection();
-      }
+      CloseableHttpClient httpClient = this.createHttpClient(useKey);
+      
+      // 使用连接池的客户端,不需要手动关闭
+      final byte[] bytes = httpClient.execute(httpPost, ByteArrayResponseHandler.INSTANCE);
+      final String responseData = Base64.getEncoder().encodeToString(bytes);
+      this.logRequestAndResponse(url, requestStr, responseData);
+      wxApiData.set(new WxPayApiData(url, requestStr, responseData, null));
+      return bytes;
     } catch (Exception e) {
-      this.log.error("\n【请求地址】:{}\n【请求数据】:{}\n【异常信息】:{}", url, requestStr, e.getMessage());
+      this.logError(url, requestStr, e);
       wxApiData.set(new WxPayApiData(url, requestStr, null, e.getMessage()));
       throw new WxPayException(e.getMessage(), e);
     }
@@ -71,22 +71,22 @@ public byte[] postForBytes(String url, String requestStr, boolean useKey) throws
   @Override
   public String post(String url, String requestStr, boolean useKey) throws WxPayException {
     try {
-      HttpClientBuilder httpClientBuilder = this.createHttpClientBuilder(useKey);
       HttpPost httpPost = this.createHttpPost(url, requestStr);
-      try (CloseableHttpClient httpClient = httpClientBuilder.build()) {
-        try (CloseableHttpResponse response = httpClient.execute(httpPost)) {
-          String responseString = EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8);
-          this.log.info("\n【请求地址】:{}\n【请求数据】:{}\n【响应数据】:{}", url, requestStr, responseString);
-          if (this.getConfig().isIfSaveApiData()) {
-            wxApiData.set(new WxPayApiData(url, requestStr, responseString, null));
-          }
-          return responseString;
+      CloseableHttpClient httpClient = this.createHttpClient(useKey);
+      
+      // 使用连接池的客户端,不需要手动关闭
+      try (CloseableHttpResponse response = httpClient.execute(httpPost)) {
+        String responseString = EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8);
+        this.logRequestAndResponse(url, requestStr, responseString);
+        if (this.getConfig().isIfSaveApiData()) {
+          wxApiData.set(new WxPayApiData(url, requestStr, responseString, null));
         }
+        return responseString;
       } finally {
         httpPost.releaseConnection();
       }
     } catch (Exception e) {
-      this.log.error("\n【请求地址】:{}\n【请求数据】:{}\n【异常信息】:{}", url, requestStr, e.getMessage());
+      this.logError(url, requestStr, e);
       if (this.getConfig().isIfSaveApiData()) {
         wxApiData.set(new WxPayApiData(url, requestStr, null, e.getMessage()));
       }
@@ -96,11 +96,14 @@ public String post(String url, String requestStr, boolean useKey) throws WxPayEx
 
   @Override
   public String postV3(String url, String requestStr) throws WxPayException {
-    CloseableHttpClient httpClient = this.createApiV3HttpClient();
     HttpPost httpPost = this.createHttpPost(url, requestStr);
-    httpPost.addHeader(ACCEPT, APPLICATION_JSON);
-    httpPost.addHeader(CONTENT_TYPE, APPLICATION_JSON);
-    try (CloseableHttpResponse response = httpClient.execute(httpPost)) {
+    this.configureRequest(httpPost);
+    return this.requestV3(url, requestStr, httpPost);
+  }
+
+  private String requestV3(String url, String requestStr, HttpRequestBase httpRequestBase) throws WxPayException {
+    CloseableHttpClient httpClient = this.createApiV3HttpClient();
+    try (CloseableHttpResponse response = httpClient.execute(httpRequestBase)) {
       //v3已经改为通过状态码判断200 204 成功
       int statusCode = response.getStatusLine().getStatusCode();
       //post方法有可能会没有返回值的情况
@@ -110,7 +113,7 @@ public String postV3(String url, String requestStr) throws WxPayException {
       }
 
       if (HttpStatus.SC_OK == statusCode || HttpStatus.SC_NO_CONTENT == statusCode) {
-        this.log.info("\n【请求地址】:{}\n【请求数据】:{}\n【响应数据】:{}", url, requestStr, responseString);
+        this.logRequestAndResponse(url, requestStr, responseString);
         return responseString;
       }
 
@@ -118,62 +121,25 @@ public String postV3(String url, String requestStr) throws WxPayException {
       JsonObject jsonObject = GsonParser.parse(responseString);
       throw convertException(jsonObject);
     } catch (Exception e) {
-      this.log.error("\n【请求地址】:{}\n【请求数据】:{}\n【异常信息】:{}", url, requestStr, e.getMessage());
+      this.logError(url, requestStr, e);
       throw (e instanceof WxPayException) ? (WxPayException) e : new WxPayException(e.getMessage(), e);
     } finally {
-      httpPost.releaseConnection();
+      httpRequestBase.releaseConnection();
     }
-
-
   }
 
   @Override
   public String patchV3(String url, String requestStr) throws WxPayException {
-    CloseableHttpClient httpClient = this.createApiV3HttpClient();
     HttpPatch httpPatch = new HttpPatch(url);
-    httpPatch.setEntity(this.createEntry(requestStr));
-
-    httpPatch.setConfig(RequestConfig.custom()
-      .setConnectionRequestTimeout(this.getConfig().getHttpConnectionTimeout())
-      .setConnectTimeout(this.getConfig().getHttpConnectionTimeout())
-      .setSocketTimeout(this.getConfig().getHttpTimeout())
-      .build());
-
-    httpPatch.addHeader(ACCEPT, APPLICATION_JSON);
-    httpPatch.addHeader(CONTENT_TYPE, APPLICATION_JSON);
-    try (CloseableHttpResponse response = httpClient.execute(httpPatch)) {
-      //v3已经改为通过状态码判断200 204 成功
-      int statusCode = response.getStatusLine().getStatusCode();
-      //post方法有可能会没有返回值的情况
-      String responseString = null;
-      if (response.getEntity() != null) {
-        responseString = EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8);
-      }
-
-      if (HttpStatus.SC_OK == statusCode || HttpStatus.SC_NO_CONTENT == statusCode) {
-        this.log.info("\n【请求地址】:{}\n【请求数据】:{}\n【响应数据】:{}", url, requestStr, responseString);
-        return responseString;
-      }
-
-      //有错误提示信息返回
-      JsonObject jsonObject = GsonParser.parse(responseString);
-      throw convertException(jsonObject);
-    } catch (Exception e) {
-      this.log.error("\n【请求地址】:{}\n【请求数据】:{}\n【异常信息】:{}", url, requestStr, e.getMessage());
-      throw (e instanceof WxPayException) ? (WxPayException) e : new WxPayException(e.getMessage(), e);
-    } finally {
-      httpPatch.releaseConnection();
-    }
+    httpPatch.setEntity(createEntry(requestStr));
+    return this.requestV3(url, requestStr, httpPatch);
   }
 
   @Override
   public String postV3WithWechatpaySerial(String url, String requestStr) throws WxPayException {
-    CloseableHttpClient httpClient = this.createApiV3HttpClient();
     HttpPost httpPost = this.createHttpPost(url, requestStr);
-    httpPost.addHeader(ACCEPT, APPLICATION_JSON);
-    httpPost.addHeader(CONTENT_TYPE, APPLICATION_JSON);
-    String serialNumber = getWechatpaySerial(getConfig());
-    httpPost.addHeader("Wechatpay-Serial", serialNumber);
+    this.configureRequest(httpPost);
+    CloseableHttpClient httpClient = this.createApiV3HttpClient();
     try (CloseableHttpResponse response = httpClient.execute(httpPost)) {
       //v3已经改为通过状态码判断200 204 成功
       int statusCode = response.getStatusLine().getStatusCode();
@@ -184,7 +150,7 @@ public String postV3WithWechatpaySerial(String url, String requestStr) throws Wx
       }
 
       if (HttpStatus.SC_OK == statusCode || HttpStatus.SC_NO_CONTENT == statusCode) {
-        this.log.info("\n【请求地址】:{}\n【请求数据】:{}\n【响应数据】:{}", url, requestStr, responseString);
+        this.logRequestAndResponse(url, requestStr, responseString);
         return responseString;
       }
 
@@ -192,8 +158,7 @@ public String postV3WithWechatpaySerial(String url, String requestStr) throws Wx
       JsonObject jsonObject = GsonParser.parse(responseString);
       throw convertException(jsonObject);
     } catch (Exception e) {
-      this.log.error("\n【请求地址】:{}\n【请求数据】:{}\n【异常信息】:{}", url, requestStr, e.getMessage());
-      e.printStackTrace();
+      this.logError(url, requestStr, e);
       throw (e instanceof WxPayException) ? (WxPayException) e : new WxPayException(e.getMessage(), e);
     } finally {
       httpPost.releaseConnection();
@@ -207,12 +172,7 @@ public String postV3(String url, HttpPost httpPost) throws WxPayException {
 
   @Override
   public String requestV3(String url, HttpRequestBase httpRequest) throws WxPayException {
-    httpRequest.setConfig(RequestConfig.custom()
-      .setConnectionRequestTimeout(this.getConfig().getHttpConnectionTimeout())
-      .setConnectTimeout(this.getConfig().getHttpConnectionTimeout())
-      .setSocketTimeout(this.getConfig().getHttpTimeout())
-      .build());
-
+    this.configureRequest(httpRequest);
     CloseableHttpClient httpClient = this.createApiV3HttpClient();
     try (CloseableHttpResponse response = httpClient.execute(httpRequest)) {
       //v3已经改为通过状态码判断200 204 成功
@@ -224,7 +184,7 @@ public String requestV3(String url, HttpRequestBase httpRequest) throws WxPayExc
       }
 
       if (HttpStatus.SC_OK == statusCode || HttpStatus.SC_NO_CONTENT == statusCode) {
-        this.log.info("\n【请求地址】:{}\n【响应数据】:{}", url, responseString);
+        log.info("\n【请求地址】:{}\n【响应数据】:{}", url, responseString);
         return responseString;
       }
 
@@ -232,7 +192,7 @@ public String requestV3(String url, HttpRequestBase httpRequest) throws WxPayExc
       JsonObject jsonObject = GsonParser.parse(responseString);
       throw convertException(jsonObject);
     } catch (Exception e) {
-      this.log.error("\n【请求地址】:{}\n【异常信息】:{}", url, e.getMessage());
+      log.error("\n【请求地址】:{}\n【异常信息】:{}", url, e.getMessage());
       throw (e instanceof WxPayException) ? (WxPayException) e : new WxPayException(e.getMessage(), e);
     } finally {
       httpRequest.releaseConnection();
@@ -241,27 +201,24 @@ public String requestV3(String url, HttpRequestBase httpRequest) throws WxPayExc
 
   @Override
   public String getV3(String url) throws WxPayException {
+    if (this.getConfig().isStrictlyNeedWechatPaySerial()) {
+      return getV3WithWechatPaySerial(url);
+    }
     HttpGet httpGet = new HttpGet(url);
-    httpGet.addHeader(ACCEPT, APPLICATION_JSON);
-    httpGet.addHeader(CONTENT_TYPE, APPLICATION_JSON);
     return this.requestV3(url, httpGet);
   }
 
   @Override
   public String getV3WithWechatPaySerial(String url) throws WxPayException {
     HttpGet httpGet = new HttpGet(url);
-    httpGet.addHeader(ACCEPT, APPLICATION_JSON);
-    httpGet.addHeader(CONTENT_TYPE, APPLICATION_JSON);
-    String serialNumber = getWechatpaySerial(getConfig());
-    httpGet.addHeader("Wechatpay-Serial", serialNumber);
     return this.requestV3(url, httpGet);
   }
 
   @Override
   public InputStream downloadV3(String url) throws WxPayException {
-    CloseableHttpClient httpClient = this.createApiV3HttpClient();
     HttpGet httpGet = new WxPayV3DownloadHttpGet(url);
-    httpGet.addHeader(ACCEPT, ContentType.WILDCARD.getMimeType());
+    this.configureRequest(httpGet);
+    CloseableHttpClient httpClient = this.createApiV3HttpClient();
     try (CloseableHttpResponse response = httpClient.execute(httpGet)) {
       //v3已经改为通过状态码判断200 204 成功
       int statusCode = response.getStatusLine().getStatusCode();
@@ -269,7 +226,7 @@ public InputStream downloadV3(String url) throws WxPayException {
       boolean isJsonContentType = Objects.nonNull(contentType) && ContentType.APPLICATION_JSON.getMimeType()
         .equals(ContentType.parse(String.valueOf(contentType.getValue())).getMimeType());
       if ((HttpStatus.SC_OK == statusCode || HttpStatus.SC_NO_CONTENT == statusCode) && !isJsonContentType) {
-        this.log.info("\n【请求地址】:{}\n", url);
+        log.info("\n【请求地址】:{}\n", url);
         return response.getEntity().getContent();
       }
 
@@ -279,7 +236,7 @@ public InputStream downloadV3(String url) throws WxPayException {
       JsonObject jsonObject = GsonParser.parse(responseString);
       throw convertException(jsonObject);
     } catch (Exception e) {
-      this.log.error("\n【请求地址】:{}\n【异常信息】:{}", url, e.getMessage());
+      log.error("\n【请求地址】:{}\n【异常信息】:{}", url, e.getMessage());
       throw (e instanceof WxPayException) ? (WxPayException) e : new WxPayException(e.getMessage(), e);
     } finally {
       httpGet.releaseConnection();
@@ -289,21 +246,33 @@ public InputStream downloadV3(String url) throws WxPayException {
   @Override
   public String putV3(String url, String requestStr) throws WxPayException {
     HttpPut httpPut = new HttpPut(url);
-    StringEntity entity = this.createEntry(requestStr);
+    StringEntity entity = createEntry(requestStr);
     httpPut.setEntity(entity);
-    httpPut.addHeader(ACCEPT, APPLICATION_JSON);
-    httpPut.addHeader(CONTENT_TYPE, APPLICATION_JSON);
     return requestV3(url, httpPut);
   }
 
   @Override
   public String deleteV3(String url) throws WxPayException {
     HttpDelete httpDelete = new HttpDelete(url);
-    httpDelete.addHeader(ACCEPT, APPLICATION_JSON);
-    httpDelete.addHeader(CONTENT_TYPE, APPLICATION_JSON);
     return requestV3(url, httpDelete);
   }
 
+  private void configureRequest(HttpRequestBase request) {
+    String serialNumber = getWechatPaySerial(getConfig());
+    String method = request.getMethod();
+    request.addHeader(ACCEPT, APPLICATION_JSON);
+    if (!method.equals("POST")) {
+      request.addHeader(CONTENT_TYPE, APPLICATION_JSON);
+    }
+    request.addHeader(WECHAT_PAY_SERIAL, serialNumber);
+
+    request.setConfig(RequestConfig.custom()
+      .setConnectionRequestTimeout(this.getConfig().getHttpConnectionTimeout())
+      .setConnectTimeout(this.getConfig().getHttpConnectionTimeout())
+      .setSocketTimeout(this.getConfig().getHttpTimeout())
+      .build());
+  }
+
   private CloseableHttpClient createApiV3HttpClient() throws WxPayException {
     CloseableHttpClient apiV3HttpClient = this.getConfig().getApiV3HttpClient();
     if (null == apiV3HttpClient) {
@@ -312,8 +281,28 @@ private CloseableHttpClient createApiV3HttpClient() throws WxPayException {
     return apiV3HttpClient;
   }
 
-  private StringEntity createEntry(String requestStr) {
-    return new StringEntity(requestStr, ContentType.create(APPLICATION_JSON, "utf-8"));
+  CloseableHttpClient createHttpClient(boolean useKey) throws WxPayException {
+    if (useKey) {
+      // 使用SSL连接池客户端
+      CloseableHttpClient sslHttpClient = this.getConfig().getSslHttpClient();
+      if (null == sslHttpClient) {
+        this.getConfig().initSslHttpClient();
+        sslHttpClient = this.getConfig().getSslHttpClient();
+      }
+      return sslHttpClient;
+    } else {
+      // 使用普通连接池客户端
+      CloseableHttpClient httpClient = this.getConfig().getHttpClient();
+      if (null == httpClient) {
+        this.getConfig().initHttpClient();
+        httpClient = this.getConfig().getHttpClient();
+      }
+      return httpClient;
+    }
+  }
+
+  private static StringEntity createEntry(String requestStr) {
+    return new StringEntity(requestStr, ContentType.create(APPLICATION_JSON, StandardCharsets.UTF_8));
     //return new StringEntity(new String(requestStr.getBytes(StandardCharsets.UTF_8), StandardCharsets.ISO_8859_1));
   }
 
@@ -348,7 +337,7 @@ private HttpClientBuilder createHttpClientBuilder(boolean useKey) throws WxPayEx
 
   private HttpPost createHttpPost(String url, String requestStr) {
     HttpPost httpPost = new HttpPost(url);
-    httpPost.setEntity(this.createEntry(requestStr));
+    httpPost.setEntity(createEntry(requestStr));
 
     httpPost.setConfig(RequestConfig.custom()
       .setConnectionRequestTimeout(this.getConfig().getHttpConnectionTimeout())
@@ -369,9 +358,8 @@ private void initSSLContext(HttpClientBuilder httpClientBuilder) throws WxPayExc
       new DefaultHostnameVerifier()));
   }
 
-
   private WxPayException convertException(JsonObject jsonObject) {
-    //todo 这里考虑使用新的适用于V3的异常
+    //TODO 这里考虑使用新的适用于V3的异常
     JsonElement codeElement = jsonObject.get("code");
     String code = codeElement == null ? null : codeElement.getAsString();
     String message = jsonObject.get("message").getAsString();
@@ -383,14 +371,20 @@ private WxPayException convertException(JsonObject jsonObject) {
 
   /**
    * 兼容微信支付公钥模式
-   * @param wxPayConfig
-   * @return
    */
-  private String getWechatpaySerial(WxPayConfig wxPayConfig) {
-    String serialNumber = wxPayConfig.getVerifier().getValidCertificate().getSerialNumber().toString(16).toUpperCase();
+  private String getWechatPaySerial(WxPayConfig wxPayConfig) {
     if (StringUtils.isNotBlank(wxPayConfig.getPublicKeyId())) {
-      serialNumber = wxPayConfig.getPublicKeyId();
+      return wxPayConfig.getPublicKeyId();
     }
-    return serialNumber;
+
+    return wxPayConfig.getVerifier().getValidCertificate().getSerialNumber().toString(16).toUpperCase();
+  }
+
+  private void logRequestAndResponse(String url, String requestStr, String responseStr) {
+    log.info("\n【请求地址】:{}\n【请求数据】:{}\n【响应数据】:{}", url, requestStr, responseStr);
+  }
+
+  private void logError(String url, String requestStr, Exception e) {
+    log.error("\n【请求地址】:{}\n【请求数据】:{}\n【异常信息】:{}", url, requestStr, e.getMessage());
   }
 }
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/WxPayServiceHttpComponentsImpl.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/WxPayServiceHttpComponentsImpl.java
new file mode 100644
index 0000000000..1c558f711b
--- /dev/null
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/WxPayServiceHttpComponentsImpl.java
@@ -0,0 +1,367 @@
+package com.github.binarywang.wxpay.service.impl;
+
+import com.github.binarywang.wxpay.bean.WxPayApiData;
+import com.github.binarywang.wxpay.config.WxPayConfig;
+import com.github.binarywang.wxpay.exception.WxPayException;
+import com.github.binarywang.wxpay.v3.WxPayV3DownloadHttpGet;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import lombok.extern.slf4j.Slf4j;
+import me.chanjar.weixin.common.util.http.apache.ByteArrayResponseHandler;
+import me.chanjar.weixin.common.util.json.GsonParser;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.http.*;
+import org.apache.http.auth.AuthScope;
+import org.apache.http.auth.UsernamePasswordCredentials;
+import org.apache.http.client.CredentialsProvider;
+import org.apache.http.client.config.RequestConfig;
+import org.apache.http.client.methods.*;
+import org.apache.http.conn.ssl.DefaultHostnameVerifier;
+import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
+import org.apache.http.entity.ContentType;
+import org.apache.http.entity.StringEntity;
+import org.apache.http.impl.client.BasicCredentialsProvider;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClientBuilder;
+import org.apache.http.impl.client.HttpClients;
+import org.apache.http.util.EntityUtils;
+
+import javax.net.ssl.SSLContext;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.Base64;
+import java.util.Objects;
+import java.util.Optional;
+
+/**
+ * 微信支付请求实现类,apache httpconponents 实现.
+ *
+ * @author altusea
+ */
+@Slf4j
+public class WxPayServiceHttpComponentsImpl extends BaseWxPayServiceImpl {
+
+  private static final String ACCEPT = "Accept";
+  private static final String CONTENT_TYPE = "Content-Type";
+  private static final String APPLICATION_JSON = "application/json";
+  private static final String WECHAT_PAY_SERIAL = "Wechatpay-Serial";
+
+  @Override
+  public byte[] postForBytes(String url, String requestStr, boolean useKey) throws WxPayException {
+    try {
+      HttpClientBuilder httpClientBuilder = createHttpClientBuilder(useKey);
+      HttpPost httpPost = this.createHttpPost(url, requestStr);
+      try (CloseableHttpClient httpClient = httpClientBuilder.build()) {
+        final byte[] bytes = httpClient.execute(httpPost, ByteArrayResponseHandler.INSTANCE);
+        final String responseData = Base64.getEncoder().encodeToString(bytes);
+        this.logRequestAndResponse(url, requestStr, responseData);
+        wxApiData.set(new WxPayApiData(url, requestStr, responseData, null));
+        return bytes;
+      }
+    } catch (Exception e) {
+      this.logError(url, requestStr, e);
+      wxApiData.set(new WxPayApiData(url, requestStr, null, e.getMessage()));
+      throw new WxPayException(e.getMessage(), e);
+    }
+  }
+
+  @Override
+  public String post(String url, String requestStr, boolean useKey) throws WxPayException {
+    try {
+      HttpClientBuilder httpClientBuilder = this.createHttpClientBuilder(useKey);
+      HttpPost httpPost = this.createHttpPost(url, requestStr);
+      try (CloseableHttpClient httpClient = httpClientBuilder.build()) {
+        try (CloseableHttpResponse response = httpClient.execute(httpPost)) {
+          String responseString = EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8);
+          this.logRequestAndResponse(url, requestStr, responseString);
+          if (this.getConfig().isIfSaveApiData()) {
+            wxApiData.set(new WxPayApiData(url, requestStr, responseString, null));
+          }
+          return responseString;
+        }
+      } finally {
+        httpPost.releaseConnection();
+      }
+    } catch (Exception e) {
+      this.logError(url, requestStr, e);
+      if (this.getConfig().isIfSaveApiData()) {
+        wxApiData.set(new WxPayApiData(url, requestStr, null, e.getMessage()));
+      }
+      throw new WxPayException(e.getMessage(), e);
+    }
+  }
+
+  @Override
+  public String postV3(String url, String requestStr) throws WxPayException {
+    HttpPost httpPost = this.createHttpPost(url, requestStr);
+    this.configureRequest(httpPost);
+    return this.requestV3(url, requestStr, httpPost);
+  }
+
+  private String requestV3(String url, String requestStr, HttpRequestBase httpRequestBase) throws WxPayException {
+    CloseableHttpClient httpClient = this.createApiV3HttpClient();
+    try (CloseableHttpResponse response = httpClient.execute(httpRequestBase)) {
+      //v3已经改为通过状态码判断200 204 成功
+      int statusCode = response.getStatusLine().getStatusCode();
+      //post方法有可能会没有返回值的情况
+      String responseString = null;
+      if (response.getEntity() != null) {
+        responseString = EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8);
+      }
+
+      if (HttpStatus.SC_OK == statusCode || HttpStatus.SC_NO_CONTENT == statusCode) {
+        this.logRequestAndResponse(url, requestStr, responseString);
+        return responseString;
+      }
+
+      //有错误提示信息返回
+      JsonObject jsonObject = GsonParser.parse(responseString);
+      throw convertException(jsonObject);
+    } catch (Exception e) {
+      this.logError(url, requestStr, e);
+      throw (e instanceof WxPayException) ? (WxPayException) e : new WxPayException(e.getMessage(), e);
+    } finally {
+      httpRequestBase.releaseConnection();
+    }
+  }
+
+  @Override
+  public String patchV3(String url, String requestStr) throws WxPayException {
+    HttpPatch httpPatch = new HttpPatch(url);
+    httpPatch.setEntity(createEntry(requestStr));
+    return this.requestV3(url, requestStr, httpPatch);
+  }
+
+  @Override
+  public String postV3WithWechatpaySerial(String url, String requestStr) throws WxPayException {
+    HttpPost httpPost = this.createHttpPost(url, requestStr);
+    this.configureRequest(httpPost);
+    CloseableHttpClient httpClient = this.createApiV3HttpClient();
+    try (CloseableHttpResponse response = httpClient.execute(httpPost)) {
+      //v3已经改为通过状态码判断200 204 成功
+      int statusCode = response.getStatusLine().getStatusCode();
+      String responseString = "{}";
+      HttpEntity entity = response.getEntity();
+      if (entity != null) {
+        responseString = EntityUtils.toString(entity, StandardCharsets.UTF_8);
+      }
+
+      if (HttpStatus.SC_OK == statusCode || HttpStatus.SC_NO_CONTENT == statusCode) {
+        this.logRequestAndResponse(url, requestStr, responseString);
+        return responseString;
+      }
+
+      //有错误提示信息返回
+      JsonObject jsonObject = GsonParser.parse(responseString);
+      throw convertException(jsonObject);
+    } catch (Exception e) {
+      this.logError(url, requestStr, e);
+      throw (e instanceof WxPayException) ? (WxPayException) e : new WxPayException(e.getMessage(), e);
+    } finally {
+      httpPost.releaseConnection();
+    }
+  }
+
+  @Override
+  public String postV3(String url, HttpPost httpPost) throws WxPayException {
+    return this.requestV3(url, httpPost);
+  }
+
+  @Override
+  public String requestV3(String url, HttpRequestBase httpRequest) throws WxPayException {
+    this.configureRequest(httpRequest);
+    CloseableHttpClient httpClient = this.createApiV3HttpClient();
+    try (CloseableHttpResponse response = httpClient.execute(httpRequest)) {
+      //v3已经改为通过状态码判断200 204 成功
+      int statusCode = response.getStatusLine().getStatusCode();
+      //post方法有可能会没有返回值的情况
+      String responseString = null;
+      if (response.getEntity() != null) {
+        responseString = EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8);
+      }
+
+      if (HttpStatus.SC_OK == statusCode || HttpStatus.SC_NO_CONTENT == statusCode) {
+        log.info("\n【请求地址】:{}\n【响应数据】:{}", url, responseString);
+        return responseString;
+      }
+
+      //有错误提示信息返回
+      JsonObject jsonObject = GsonParser.parse(responseString);
+      throw convertException(jsonObject);
+    } catch (Exception e) {
+      log.error("\n【请求地址】:{}\n【异常信息】:{}", url, e.getMessage());
+      throw (e instanceof WxPayException) ? (WxPayException) e : new WxPayException(e.getMessage(), e);
+    } finally {
+      httpRequest.releaseConnection();
+    }
+  }
+
+  @Override
+  public String getV3(String url) throws WxPayException {
+    if (this.getConfig().isStrictlyNeedWechatPaySerial()) {
+      return getV3WithWechatPaySerial(url);
+    }
+    HttpGet httpGet = new HttpGet(url);
+    return this.requestV3(url, httpGet);
+  }
+
+  @Override
+  public String getV3WithWechatPaySerial(String url) throws WxPayException {
+    HttpGet httpGet = new HttpGet(url);
+    return this.requestV3(url, httpGet);
+  }
+
+  @Override
+  public InputStream downloadV3(String url) throws WxPayException {
+    HttpGet httpGet = new WxPayV3DownloadHttpGet(url);
+    this.configureRequest(httpGet);
+    CloseableHttpClient httpClient = this.createApiV3HttpClient();
+    try (CloseableHttpResponse response = httpClient.execute(httpGet)) {
+      //v3已经改为通过状态码判断200 204 成功
+      int statusCode = response.getStatusLine().getStatusCode();
+      Header contentType = response.getFirstHeader(HttpHeaders.CONTENT_TYPE);
+      boolean isJsonContentType = Objects.nonNull(contentType) && ContentType.APPLICATION_JSON.getMimeType()
+        .equals(ContentType.parse(String.valueOf(contentType.getValue())).getMimeType());
+      if ((HttpStatus.SC_OK == statusCode || HttpStatus.SC_NO_CONTENT == statusCode) && !isJsonContentType) {
+        log.info("\n【请求地址】:{}\n", url);
+        return response.getEntity().getContent();
+      }
+
+      //response里的header有content-type=json说明返回了错误信息
+      //有错误提示信息返回
+      String responseString = EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8);
+      JsonObject jsonObject = GsonParser.parse(responseString);
+      throw convertException(jsonObject);
+    } catch (Exception e) {
+      log.error("\n【请求地址】:{}\n【异常信息】:{}", url, e.getMessage());
+      throw (e instanceof WxPayException) ? (WxPayException) e : new WxPayException(e.getMessage(), e);
+    } finally {
+      httpGet.releaseConnection();
+    }
+  }
+
+  @Override
+  public String putV3(String url, String requestStr) throws WxPayException {
+    HttpPut httpPut = new HttpPut(url);
+    StringEntity entity = createEntry(requestStr);
+    httpPut.setEntity(entity);
+    return requestV3(url, httpPut);
+  }
+
+  @Override
+  public String deleteV3(String url) throws WxPayException {
+    HttpDelete httpDelete = new HttpDelete(url);
+    return requestV3(url, httpDelete);
+  }
+
+  private void configureRequest(HttpRequestBase request) {
+    String serialNumber = getWechatPaySerial(getConfig());
+    String method = request.getMethod();
+    request.addHeader(ACCEPT, APPLICATION_JSON);
+    if (!method.equals("POST")) {
+      request.addHeader(CONTENT_TYPE, APPLICATION_JSON);
+    }
+    request.addHeader(WECHAT_PAY_SERIAL, serialNumber);
+
+    request.setConfig(RequestConfig.custom()
+      .setConnectionRequestTimeout(this.getConfig().getHttpConnectionTimeout())
+      .setConnectTimeout(this.getConfig().getHttpConnectionTimeout())
+      .setSocketTimeout(this.getConfig().getHttpTimeout())
+      .build());
+  }
+
+  private CloseableHttpClient createApiV3HttpClient() throws WxPayException {
+    CloseableHttpClient apiV3HttpClient = this.getConfig().getApiV3HttpClient();
+    if (null == apiV3HttpClient) {
+      return this.getConfig().initApiV3HttpClient();
+    }
+    return apiV3HttpClient;
+  }
+
+  private static StringEntity createEntry(String requestStr) {
+    return new StringEntity(requestStr, ContentType.create(APPLICATION_JSON, StandardCharsets.UTF_8));
+    //return new StringEntity(new String(requestStr.getBytes(StandardCharsets.UTF_8), StandardCharsets.ISO_8859_1));
+  }
+
+  private HttpClientBuilder createHttpClientBuilder(boolean useKey) throws WxPayException {
+    HttpClientBuilder httpClientBuilder = HttpClients.custom();
+    if (useKey) {
+      this.initSSLContext(httpClientBuilder);
+    }
+
+    if (StringUtils.isNotBlank(this.getConfig().getHttpProxyHost()) && this.getConfig().getHttpProxyPort() > 0) {
+      if (StringUtils.isEmpty(this.getConfig().getHttpProxyUsername())) {
+        this.getConfig().setHttpProxyUsername("whatever");
+      }
+
+      // 使用代理服务器 需要用户认证的代理服务器
+      CredentialsProvider provider = new BasicCredentialsProvider();
+      provider.setCredentials(new AuthScope(this.getConfig().getHttpProxyHost(),
+          this.getConfig().getHttpProxyPort()),
+        new UsernamePasswordCredentials(this.getConfig().getHttpProxyUsername(),
+          this.getConfig().getHttpProxyPassword()));
+      httpClientBuilder.setDefaultCredentialsProvider(provider)
+        .setProxy(new HttpHost(this.getConfig().getHttpProxyHost(), this.getConfig().getHttpProxyPort()));
+    }
+
+    // 提供自定义httpClientBuilder的能力
+    Optional.ofNullable(getConfig().getHttpClientBuilderCustomizer()).ifPresent(e -> {
+      e.customize(httpClientBuilder);
+    });
+
+    return httpClientBuilder;
+  }
+
+  private HttpPost createHttpPost(String url, String requestStr) {
+    HttpPost httpPost = new HttpPost(url);
+    httpPost.setEntity(createEntry(requestStr));
+
+    httpPost.setConfig(RequestConfig.custom()
+      .setConnectionRequestTimeout(this.getConfig().getHttpConnectionTimeout())
+      .setConnectTimeout(this.getConfig().getHttpConnectionTimeout())
+      .setSocketTimeout(this.getConfig().getHttpTimeout())
+      .build());
+
+    return httpPost;
+  }
+
+  private void initSSLContext(HttpClientBuilder httpClientBuilder) throws WxPayException {
+    SSLContext sslContext = this.getConfig().getSslContext();
+    if (null == sslContext) {
+      sslContext = this.getConfig().initSSLContext();
+    }
+
+    httpClientBuilder.setSSLSocketFactory(new SSLConnectionSocketFactory(sslContext,
+      new DefaultHostnameVerifier()));
+  }
+
+  private WxPayException convertException(JsonObject jsonObject) {
+    //TODO 这里考虑使用新的适用于V3的异常
+    JsonElement codeElement = jsonObject.get("code");
+    String code = codeElement == null ? null : codeElement.getAsString();
+    String message = jsonObject.get("message").getAsString();
+    WxPayException wxPayException = new WxPayException(message);
+    wxPayException.setErrCode(code);
+    wxPayException.setErrCodeDes(message);
+    return wxPayException;
+  }
+
+  /**
+   * 兼容微信支付公钥模式
+   */
+  private String getWechatPaySerial(WxPayConfig wxPayConfig) {
+    if (StringUtils.isNotBlank(wxPayConfig.getPublicKeyId())) {
+      return wxPayConfig.getPublicKeyId();
+    }
+
+    return wxPayConfig.getVerifier().getValidCertificate().getSerialNumber().toString(16).toUpperCase();
+  }
+
+  private void logRequestAndResponse(String url, String requestStr, String responseStr) {
+    log.info("\n【请求地址】:{}\n【请求数据】:{}\n【响应数据】:{}", url, requestStr, responseStr);
+  }
+
+  private void logError(String url, String requestStr, Exception e) {
+    log.error("\n【请求地址】:{}\n【请求数据】:{}\n【异常信息】:{}", url, requestStr, e.getMessage());
+  }
+}
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/WxPayServiceJoddHttpImpl.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/WxPayServiceJoddHttpImpl.java
index 5e6d23eac9..7c2f1e82c0 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/WxPayServiceJoddHttpImpl.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/WxPayServiceJoddHttpImpl.java
@@ -9,6 +9,7 @@
 import jodd.http.ProxyInfo.ProxyType;
 import jodd.http.net.SSLSocketHttpConnectionProvider;
 import jodd.http.net.SocketHttpConnectionProvider;
+import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.http.client.methods.HttpPost;
 import org.apache.http.client.methods.HttpRequestBase;
@@ -24,6 +25,7 @@
  *
  * @author Binary Wang
  */
+@Slf4j
 public class WxPayServiceJoddHttpImpl extends BaseWxPayServiceImpl {
   @Override
   public byte[] postForBytes(String url, String requestStr, boolean useKey) throws WxPayException {
@@ -31,13 +33,13 @@ public byte[] postForBytes(String url, String requestStr, boolean useKey) throws
       HttpRequest request = this.buildHttpRequest(url, requestStr, useKey);
       byte[] responseBytes = request.send().bodyBytes();
       final String responseString = Base64.getEncoder().encodeToString(responseBytes);
-      this.log.info("\n【请求地址】:{}\n【请求数据】:{}\n【响应数据(Base64编码后)】:{}", url, requestStr, responseString);
+      log.info("\n【请求地址】:{}\n【请求数据】:{}\n【响应数据(Base64编码后)】:{}", url, requestStr, responseString);
       if (this.getConfig().isIfSaveApiData()) {
         wxApiData.set(new WxPayApiData(url, requestStr, responseString, null));
       }
       return responseBytes;
     } catch (Exception e) {
-      this.log.error("\n【请求地址】:{}\n【请求数据】:{}\n【异常信息】:{}", url, requestStr, e.getMessage());
+      log.error("\n【请求地址】:{}\n【请求数据】:{}\n【异常信息】:{}", url, requestStr, e.getMessage());
       wxApiData.set(new WxPayApiData(url, requestStr, null, e.getMessage()));
       throw new WxPayException(e.getMessage(), e);
     }
@@ -49,13 +51,13 @@ public String post(String url, String requestStr, boolean useKey) throws WxPayEx
       HttpRequest request = this.buildHttpRequest(url, requestStr, useKey);
       String responseString = this.getResponseString(request.send());
 
-      this.log.info("\n【请求地址】:{}\n【请求数据】:{}\n【响应数据】:{}", url, requestStr, responseString);
+      log.info("\n【请求地址】:{}\n【请求数据】:{}\n【响应数据】:{}", url, requestStr, responseString);
       if (this.getConfig().isIfSaveApiData()) {
         wxApiData.set(new WxPayApiData(url, requestStr, responseString, null));
       }
       return responseString;
     } catch (Exception e) {
-      this.log.error("\n【请求地址】:{}\n【请求数据】:{}\n【异常信息】:{}", url, requestStr, e.getMessage());
+      log.error("\n【请求地址】:{}\n【请求数据】:{}\n【异常信息】:{}", url, requestStr, e.getMessage());
       wxApiData.set(new WxPayApiData(url, requestStr, null, e.getMessage()));
       throw new WxPayException(e.getMessage(), e);
     }
@@ -146,9 +148,9 @@ private HttpRequest buildHttpRequest(String url, String requestStr, boolean useK
 
   private String getResponseString(HttpResponse response) throws WxPayException {
     try {
-      this.log.debug("【微信服务器响应头信息】:\n{}", response.toString(false));
+      log.debug("【微信服务器响应头信息】:\n{}", response.toString(false));
     } catch (NullPointerException e) {
-      this.log.warn("HttpResponse.toString() 居然抛出空指针异常了", e);
+      log.warn("HttpResponse.toString() 居然抛出空指针异常了", e);
     }
 
     String responseString = response.bodyText();
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 9e005a813e..6c0009fd18 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
@@ -92,8 +92,7 @@ public static String createSign(Map params, String signType, Str
     for (String key : new TreeMap<>(params).keySet()) {
       String value = params.get(key);
       boolean shouldSign = false;
-      if (StringUtils.isNotEmpty(value) && !ArrayUtils.contains(ignoredParams, key)
-        && !NO_SIGN_PARAMS.contains(key)) {
+      if (StringUtils.isNotEmpty(value) && !ArrayUtils.contains(ignoredParams, key) && !NO_SIGN_PARAMS.contains(key)) {
         shouldSign = true;
       }
 
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/util/ZipUtils.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/util/ZipUtils.java
index f9c434196a..1bab0432e6 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/util/ZipUtils.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/util/ZipUtils.java
@@ -24,7 +24,7 @@ public static File unGzip(final File file) throws IOException {
     resultFile.createNewFile();
 
     try (FileOutputStream fos = new FileOutputStream(resultFile);
-         GZIPInputStream gzis = new GZIPInputStream(new FileInputStream(file));) {
+         GZIPInputStream gzis = new GZIPInputStream(new FileInputStream(file))) {
       IOUtils.copy(gzis, fos);
     }
 
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/v3/WxPayV3HttpClientBuilder.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/v3/WxPayV3HttpClientBuilder.java
index f479367239..c88c884f57 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/v3/WxPayV3HttpClientBuilder.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/v3/WxPayV3HttpClientBuilder.java
@@ -2,13 +2,9 @@
 
 
 import java.security.PrivateKey;
-import java.security.cert.X509Certificate;
-import java.util.List;
 
-import com.github.binarywang.wxpay.v3.auth.CertificatesVerifier;
 import com.github.binarywang.wxpay.v3.auth.PrivateKeySigner;
 import com.github.binarywang.wxpay.v3.auth.WxPayCredentials;
-import com.github.binarywang.wxpay.v3.auth.WxPayValidator;
 import org.apache.http.impl.client.CloseableHttpClient;
 import org.apache.http.impl.client.HttpClientBuilder;
 import org.apache.http.impl.execchain.ClientExecChain;
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/v3/auth/AutoUpdateCertificatesVerifier.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/v3/auth/AutoUpdateCertificatesVerifier.java
index abcae7dff7..21624d455f 100755
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/v3/auth/AutoUpdateCertificatesVerifier.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/v3/auth/AutoUpdateCertificatesVerifier.java
@@ -135,7 +135,7 @@ private void checkAndAutoUpdateCert() {
           //更新时间
           instant = Instant.now();
         } catch (GeneralSecurityException | IOException e) {
-          log.warn("Auto update cert failed, exception = " + e);
+          log.warn("Auto update cert failed, exception = {}", e);
         } finally {
           lock.unlock();
         }
@@ -169,7 +169,7 @@ private void autoUpdateCert() throws IOException, GeneralSecurityException {
       }
       this.verifier = new CertificatesVerifier(newCertList);
     } else {
-      log.warn("Auto update cert failed, statusCode = " + statusCode + ",body = " + body);
+      log.warn("Auto update cert failed, statusCode = {},body = {}", statusCode, body);
       throw new WxRuntimeException(this.getErrorMsg(body));
     }
   }
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/v3/auth/PublicCertificateVerifier.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/v3/auth/PublicCertificateVerifier.java
index 9344fc6f83..8c9c4f3569 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/v3/auth/PublicCertificateVerifier.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/v3/auth/PublicCertificateVerifier.java
@@ -9,6 +9,8 @@ public class PublicCertificateVerifier implements Verifier{
 
     private final PublicKey publicKey;
 
+    private Verifier certificateVerifier;
+
     private final X509PublicCertificate publicCertificate;
 
     public PublicCertificateVerifier(PublicKey publicKey, String publicId) {
@@ -16,8 +18,15 @@ public PublicCertificateVerifier(PublicKey publicKey, String publicId) {
         this.publicCertificate = new X509PublicCertificate(publicKey, publicId);
     }
 
+   public void setOtherVerifier(Verifier verifier) {
+      this.certificateVerifier = verifier;
+   }
+
     @Override
     public boolean verify(String serialNumber, byte[] message, String signature) {
+        if (!serialNumber.contains("PUB_KEY_ID") && this.certificateVerifier != null) {
+            return this.certificateVerifier.verify(serialNumber, message, signature);
+        }
         try {
             Signature sign = Signature.getInstance("SHA256withRSA");
             sign.initVerify(publicKey);
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/v3/auth/Verifier.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/v3/auth/Verifier.java
index 49f92e2f5b..22676601c0 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/v3/auth/Verifier.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/v3/auth/Verifier.java
@@ -7,4 +7,6 @@ public interface Verifier {
 
 
   X509Certificate getValidCertificate();
+
+  default void setOtherVerifier(Verifier verifier) {};
 }
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/v3/auth/WxPayValidator.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/v3/auth/WxPayValidator.java
index acb75bb6cd..cc88caf758 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/v3/auth/WxPayValidator.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/v3/auth/WxPayValidator.java
@@ -10,6 +10,7 @@
 import org.apache.http.util.EntityUtils;
 
 import java.io.IOException;
+import java.nio.charset.StandardCharsets;
 
 /**
  * @author spvycf & F00lish
@@ -39,7 +40,7 @@ public final boolean validate(CloseableHttpResponse response) throws IOException
     }
 
     String message = buildMessage(response);
-    return verifier.verify(serialNo.getValue(), message.getBytes("utf-8"), sign.getValue());
+    return verifier.verify(serialNo.getValue(), message.getBytes(StandardCharsets.UTF_8), sign.getValue());
   }
 
   protected final String buildMessage(CloseableHttpResponse response) throws IOException {
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/v3/util/AesUtils.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/v3/util/AesUtils.java
index b4a97ba88f..831dfe2bb1 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/v3/util/AesUtils.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/v3/util/AesUtils.java
@@ -80,11 +80,11 @@ public static String decryptToString(String associatedData, String nonce, String
     try {
       Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
 
-      SecretKeySpec key = new SecretKeySpec(apiV3Key.getBytes(), "AES");
-      GCMParameterSpec spec = new GCMParameterSpec(TAG_LENGTH_BIT, nonce.getBytes());
+      SecretKeySpec key = new SecretKeySpec(apiV3Key.getBytes(StandardCharsets.UTF_8), "AES");
+      GCMParameterSpec spec = new GCMParameterSpec(TAG_LENGTH_BIT, nonce.getBytes(StandardCharsets.UTF_8));
 
       cipher.init(Cipher.DECRYPT_MODE, key, spec);
-      cipher.updateAAD(associatedData.getBytes());
+      cipher.updateAAD(associatedData.getBytes(StandardCharsets.UTF_8));
 
       return new String(cipher.doFinal(Base64.getDecoder().decode(ciphertext)), StandardCharsets.UTF_8);
     } catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
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 d8fe3b35ba..8c3e2ace53 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
@@ -47,7 +47,7 @@ private static void encryptField(Object encryptObject, X509Certificate certifica
           Object oldValue = field.get(encryptObject);
           if (oldValue != null) {
             String oldStr = (String) oldValue;
-            if (!"".equals(oldStr.trim())) {
+            if (!oldStr.trim().isEmpty()) {
               field.set(encryptObject, encryptOAEP(oldStr, certificate));
             }
           }
@@ -57,8 +57,8 @@ private static void encryptField(Object encryptObject, X509Certificate certifica
           if (obj == null) {
             continue;
           }
-          if (obj instanceof Collection) {
-            Collection collection = (Collection) obj;
+          if (obj instanceof Collection) {
+            Collection collection = (Collection) obj;
             for (Object o : collection) {
               if (o != null) {
                 encryptField(o, certificate);
diff --git a/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/bean/marketing/FavorStocksGetResultTest.java b/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/bean/marketing/FavorStocksGetResultTest.java
new file mode 100644
index 0000000000..ed39413833
--- /dev/null
+++ b/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/bean/marketing/FavorStocksGetResultTest.java
@@ -0,0 +1,164 @@
+package com.github.binarywang.wxpay.bean.marketing;
+
+import com.github.binarywang.wxpay.bean.result.BaseWxPayV3Result;
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonParser;
+import org.testng.annotations.Test;
+
+import static org.testng.Assert.*;
+
+/**
+ * 测试FavorStocksGetResult的原始JSON保存功能
+ *
+ * @author Binary Wang
+ */
+public class FavorStocksGetResultTest {
+
+  private static final Gson GSON = new GsonBuilder().create();
+
+  @Test
+  public void testRawJsonPreservation() {
+    // 模拟微信API返回的JSON(包含未在Result类中定义的字段)
+    String mockJson = "{\n" +
+      "  \"stock_id\": \"9836588\",\n" +
+      "  \"stock_creator_mchid\": \"1230000109\",\n" +
+      "  \"stock_name\": \"微信支付代金券\",\n" +
+      "  \"status\": \"running\",\n" +
+      "  \"create_time\": \"2021-01-01T00:00:00.000+08:00\",\n" +
+      "  \"description\": \"微信支付营销\",\n" +
+      "  \"card_id\": \"pFS7Fjg9kqcMOBtl3bFn\",\n" +
+      "  \"extra_field\": \"这是一个新增字段\"\n" +
+      "}";
+
+    // 模拟服务调用:反序列化JSON
+    FavorStocksGetResult result = GSON.fromJson(mockJson, FavorStocksGetResult.class);
+    
+    // 模拟服务调用:设置原始JSON
+    result.setRawJsonString(mockJson);
+
+    // 验证基本字段正常解析
+    assertEquals(result.getStockId(), "9836588");
+    assertEquals(result.getStockCreatorMchId(), "1230000109");
+    assertEquals(result.getStockName(), "微信支付代金券");
+    assertEquals(result.getStatus(), "running");
+
+    // 验证原始JSON被保存
+    assertNotNull(result.getRawJsonString());
+    assertEquals(result.getRawJsonString(), mockJson);
+
+    // 验证可以从原始JSON中获取未定义的字段
+    assertTrue(result.getRawJsonString().contains("\"card_id\": \"pFS7Fjg9kqcMOBtl3bFn\""));
+    assertTrue(result.getRawJsonString().contains("\"extra_field\": \"这是一个新增字段\""));
+  }
+
+  @Test
+  public void testBaseV3ResultInheritance() {
+    FavorStocksGetResult result = new FavorStocksGetResult();
+    
+    // 验证继承关系
+    assertTrue(result instanceof BaseWxPayV3Result);
+    
+    // 验证基类方法可用
+    result.setRawJsonString("test json");
+    assertEquals(result.getRawJsonString(), "test json");
+  }
+
+  @Test
+  public void testNullRawJson() {
+    FavorStocksGetResult result = new FavorStocksGetResult();
+    
+    // 验证初始状态下rawJsonString为null
+    assertNull(result.getRawJsonString());
+    
+    // 验证设置null不会出错
+    result.setRawJsonString(null);
+    assertNull(result.getRawJsonString());
+  }
+
+  @Test
+  public void testRealWorldUsagePattern() {
+    // 实际使用场景的示例
+    String realApiResponse = "{\n" +
+      "  \"stock_id\": \"9836588\",\n" +
+      "  \"stock_creator_mchid\": \"1230000109\",\n" +
+      "  \"stock_name\": \"微信支付代金券\",\n" +
+      "  \"status\": \"running\",\n" +
+      "  \"create_time\": \"2021-01-01T00:00:00.000+08:00\",\n" +
+      "  \"description\": \"微信支付营销\",\n" +
+      "  \"card_id\": \"pFS7Fjg9kqcMOBtl3bFn\",\n" +
+      "  \"future_field_1\": \"未来可能新增的字段1\",\n" +
+      "  \"future_field_2\": \"未来可能新增的字段2\"\n" +
+      "}";
+
+    FavorStocksGetResult result = GSON.fromJson(realApiResponse, FavorStocksGetResult.class);
+    result.setRawJsonString(realApiResponse);
+
+    // 1. 正常使用已定义的字段
+    assertEquals(result.getStockId(), "9836588");
+    assertEquals(result.getStockName(), "微信支付代金券");
+
+    // 2. 安全地获取可能存在的新字段
+    String cardId = getStringFieldSafely(result, "card_id");
+    assertEquals(cardId, "pFS7Fjg9kqcMOBtl3bFn");
+
+    // 3. 获取未来可能新增的字段
+    String futureField1 = getStringFieldSafely(result, "future_field_1");
+    assertEquals(futureField1, "未来可能新增的字段1");
+
+    String nonExistentField = getStringFieldSafely(result, "non_existent");
+    assertNull(nonExistentField);
+  }
+
+  @Test
+  public void testCardIdExtractionExample() {
+    // 测试具体的card_id字段提取(这是issue中提到的用例)
+    String apiResponseWithCardId = "{\n" +
+      "  \"stock_id\": \"9836588\",\n" +
+      "  \"stock_creator_mchid\": \"1230000109\",\n" +
+      "  \"stock_name\": \"微信支付代金券\",\n" +
+      "  \"status\": \"running\",\n" +
+      "  \"card_id\": \"pFS7Fjg9kqcMOBtl3bFn\"\n" +
+      "}";
+
+    FavorStocksGetResult result = GSON.fromJson(apiResponseWithCardId, FavorStocksGetResult.class);
+    result.setRawJsonString(apiResponseWithCardId);
+
+    // 验证可以获取card_id字段
+    JsonElement jsonElement = JsonParser.parseString(result.getRawJsonString());
+    assertTrue(jsonElement.getAsJsonObject().has("card_id"));
+    String cardId = jsonElement.getAsJsonObject().get("card_id").getAsString();
+    assertEquals(cardId, "pFS7Fjg9kqcMOBtl3bFn");
+
+    // 展示实际用法
+    String extractedCardId = extractCardId(result);
+    assertEquals(extractedCardId, "pFS7Fjg9kqcMOBtl3bFn");
+  }
+
+  /**
+   * 提取card_id的示例方法
+   */
+  private String extractCardId(FavorStocksGetResult result) {
+    return getStringFieldSafely(result, "card_id");
+  }
+
+  /**
+   * 安全地从结果中获取字符串字段的工具方法
+   */
+  private String getStringFieldSafely(BaseWxPayV3Result result, String fieldName) {
+    String rawJson = result.getRawJsonString();
+    if (rawJson == null) return null;
+    
+    try {
+      JsonElement jsonElement = JsonParser.parseString(rawJson);
+      if (jsonElement.getAsJsonObject().has(fieldName)) {
+        JsonElement fieldElement = jsonElement.getAsJsonObject().get(fieldName);
+        return fieldElement.isJsonNull() ? null : fieldElement.getAsString();
+      }
+    } catch (Exception e) {
+      // 解析失败时返回null
+    }
+    return null;
+  }
+}
\ No newline at end of file
diff --git a/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/bean/notify/WxPayRefundNotifyResultTest.java b/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/bean/notify/WxPayRefundNotifyResultTest.java
index 963afb2618..e7a22ee6cd 100644
--- a/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/bean/notify/WxPayRefundNotifyResultTest.java
+++ b/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/bean/notify/WxPayRefundNotifyResultTest.java
@@ -119,7 +119,7 @@ public void testFromXMLFastMode() throws WxPayException {
       refundNotifyResult.loadReqInfo(xmlDecryptedReqInfo);
       assertEquals(refundNotifyResult.getReqInfo().getRefundFee().intValue(), 15);
       assertEquals(refundNotifyResult.getReqInfo().getRefundStatus(), "SUCCESS");
-      assertEquals(refundNotifyResult.getReqInfo().getRefundRecvAccout(), "用户零钱");
+      assertEquals(refundNotifyResult.getReqInfo().getRefundRecvAccount(), "用户零钱");
       System.out.println(refundNotifyResult);
     } finally {
       XmlConfig.fastMode = false;
diff --git a/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/bean/payscore/WxPartnerPayScoreRequestTest.java b/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/bean/payscore/WxPartnerPayScoreRequestTest.java
new file mode 100644
index 0000000000..7b475d84a0
--- /dev/null
+++ b/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/bean/payscore/WxPartnerPayScoreRequestTest.java
@@ -0,0 +1,45 @@
+package com.github.binarywang.wxpay.bean.payscore;
+
+import org.testng.annotations.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * @author Binary Wang
+ * created on  2020-07-11
+ */
+public class WxPartnerPayScoreRequestTest {
+
+  @Test
+  public void testToJson() {
+    WxPartnerPayScoreRequest request = WxPartnerPayScoreRequest.builder()
+      .outOrderNo("QLS202005201058000201")
+      .appid("123")
+      .serviceId("345")
+      .serviceIntroduction("租借服务")
+      .timeRange(new TimeRange("20230901011023", "20230930235959","开始时间","结束时间"))
+      .device(new Device("deviceId","deviceId","212323232"))
+      .build();
+    System.out.println(request.toJson());
+    String expectedJson =
+      "{\"out_order_no\":\"QLS202005201058000201\",\"appid\":\"123\",\"service_id\":\"345\",\"service_introduction\":\"租借服务\",\"time_range\":{\"start_time\":\"20230901011023\",\"end_time\":\"20230930235959\",\"start_time_remark\":\"开始时间\",\"end_time_remark\":\"结束时间\"},\"device\":{\"start_device_id\":\"deviceId\",\"end_device_id\":\"deviceId\",\"materiel_no\":\"212323232\"}}";
+    assertThat(request.toJson()).isEqualTo(expectedJson);
+//    {
+//      "out_order_no": "QLS202005201058000201",
+//      "appid": "123",
+//      "service_id": "345",
+//      "service_introduction": "租借服务",
+//      "time_range": {
+//      "start_time": "20230901011023",
+//        "end_time": "20230930235959",
+//        "start_time_remark": "开始时间",
+//        "end_time_remark": "结束时间"
+//    },
+//      "device": {
+//      "start_device_id": "deviceId",
+//        "end_device_id": "deviceId",
+//        "materiel_no": "212323232"
+//    }
+//    }
+  }
+}
diff --git a/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/bean/payscore/WxPayScoreRequestTest.java b/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/bean/payscore/WxPayScoreRequestTest.java
index 5d29f15a76..15f3f3abae 100644
--- a/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/bean/payscore/WxPayScoreRequestTest.java
+++ b/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/bean/payscore/WxPayScoreRequestTest.java
@@ -2,6 +2,8 @@
 
 import org.testng.annotations.Test;
 
+import static org.assertj.core.api.Assertions.assertThat;
+
 /**
  * @author Binary Wang
  * created on  2020-07-11
@@ -15,40 +17,29 @@ public void testToJson() {
       .serviceId("345")
       .serviceIntroduction("租借服务")
       .timeRange(new TimeRange("20230901011023", "20230930235959","开始时间","结束时间"))
+      .device(new Device("deviceId","deviceId","212323232"))
       .build();
-    System.out.println(request.toJson());
-    /*    {
-      "out_order_no":"QLS202005201058000201",
-      "appid":"123",
-      "service_id":"345",
-      "service_introduction":"租借服务",
-      "time_range":{
-      "start_time":"OnAccept",
-        "end_time":"20200520225840"
-    },
-      "location":{
-      "start_location":"山",
-        "end_location":"山"
-    },
-      "risk_fund":{
-      "name":"DEPOSIT",
-        "amount":200,
-        "description":"丢失偿还费用2元/台"
-    },
-      "attach":"",
-      "notify_url":"/pay/notify/payScore",
-      "openid":"",
-      "need_user_confirm":true,
-      "profit_sharing":false,
-      "post_payments":[
-      {
-        "name":"租借服务",
-        "amount":100,
-        "description":"服务费:1元/台",
-        "count":1
-      }
-    ],
-      "total_amount":0
-    }*/
+    String json = request.toJson();
+    System.out.println(json);
+
+    String expectedJson = "{\"out_order_no\":\"QLS202005201058000201\",\"appid\":\"123\",\"service_id\":\"345\",\"service_introduction\":\"租借服务\",\"time_range\":{\"start_time\":\"20230901011023\",\"end_time\":\"20230930235959\",\"start_time_remark\":\"开始时间\",\"end_time_remark\":\"结束时间\"},\"device\":{\"start_device_id\":\"deviceId\",\"end_device_id\":\"deviceId\",\"materiel_no\":\"212323232\"}}";
+    assertThat(request.toJson()).isEqualTo(expectedJson);
+//    {
+//      "out_order_no": "QLS202005201058000201",
+//      "appid": "123",
+//      "service_id": "345",
+//      "service_introduction": "租借服务",
+//      "time_range": {
+//      "start_time": "20230901011023",
+//        "end_time": "20230930235959",
+//        "start_time_remark": "开始时间",
+//        "end_time_remark": "结束时间"
+//    },
+//      "device": {
+//      "start_device_id": "deviceId",
+//        "end_device_id": "deviceId",
+//        "materiel_no": "212323232"
+//    }
+//    }
   }
 }
diff --git a/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/bean/request/CombineCloseRequestTest.java b/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/bean/request/CombineCloseRequestTest.java
new file mode 100644
index 0000000000..aaaa693324
--- /dev/null
+++ b/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/bean/request/CombineCloseRequestTest.java
@@ -0,0 +1,47 @@
+package com.github.binarywang.wxpay.bean.request;
+
+import com.google.gson.Gson;
+import org.testng.annotations.Test;
+
+import java.util.Arrays;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * @author Binary Wang
+ * created on  2024-12-19
+ */
+public class CombineCloseRequestTest {
+
+  @Test
+  public void testSerialization() {
+    CombineCloseRequest request = new CombineCloseRequest();
+    request.setCombineAppid("wxd678efh567hg6787");
+    request.setCombineOutTradeNo("P20150806125346");
+
+    CombineCloseRequest.SubOrders subOrder = new CombineCloseRequest.SubOrders();
+    subOrder.setMchid("1900000109");
+    subOrder.setOutTradeNo("20150806125346");
+    subOrder.setSubMchid("1230000109");
+    subOrder.setSubAppid("wxd678efh567hg6999");
+
+    request.setSubOrders(Arrays.asList(subOrder));
+
+    Gson gson = new Gson();
+    String json = gson.toJson(request);
+
+    // Verify that the JSON contains the new fields
+    assertThat(json).contains("\"sub_mchid\":\"1230000109\"");
+    assertThat(json).contains("\"sub_appid\":\"wxd678efh567hg6999\"");
+    assertThat(json).contains("\"combine_appid\":\"wxd678efh567hg6787\"");
+    assertThat(json).contains("\"mchid\":\"1900000109\"");
+    assertThat(json).contains("\"out_trade_no\":\"20150806125346\"");
+
+    // Verify deserialization works
+    CombineCloseRequest deserializedRequest = gson.fromJson(json, CombineCloseRequest.class);
+    assertThat(deserializedRequest.getCombineAppid()).isEqualTo("wxd678efh567hg6787");
+    assertThat(deserializedRequest.getSubOrders()).hasSize(1);
+    assertThat(deserializedRequest.getSubOrders().get(0).getSubMchid()).isEqualTo("1230000109");
+    assertThat(deserializedRequest.getSubOrders().get(0).getSubAppid()).isEqualTo("wxd678efh567hg6999");
+  }
+}
\ No newline at end of file
diff --git a/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/service/impl/BaseWxPayServiceGlobalImplTest.java b/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/service/impl/BaseWxPayServiceGlobalImplTest.java
new file mode 100644
index 0000000000..c648c8a171
--- /dev/null
+++ b/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/service/impl/BaseWxPayServiceGlobalImplTest.java
@@ -0,0 +1,89 @@
+package com.github.binarywang.wxpay.service.impl;
+
+import com.github.binarywang.wxpay.bean.request.WxPayUnifiedOrderV3GlobalRequest;
+import com.github.binarywang.wxpay.bean.result.enums.GlobalTradeTypeEnum;
+import com.github.binarywang.wxpay.constant.WxPayConstants;
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import me.chanjar.weixin.common.util.RandomUtils;
+import org.testng.annotations.Test;
+
+import static org.testng.Assert.*;
+
+/**
+ * 境外微信支付测试类
+ *
+ * @author Binary Wang
+ */
+public class BaseWxPayServiceGlobalImplTest {
+
+  private static final Gson GSON = new GsonBuilder().create();
+
+  @Test
+  public void testWxPayUnifiedOrderV3GlobalRequest() {
+    // Test that the new request class has the required fields
+    WxPayUnifiedOrderV3GlobalRequest request = new WxPayUnifiedOrderV3GlobalRequest();
+    
+    // Set basic order information
+    String outTradeNo = RandomUtils.getRandomStr();
+    request.setOutTradeNo(outTradeNo);
+    request.setDescription("Test overseas payment");
+    request.setNotifyUrl("https://api.example.com/notify");
+    
+    // Set amount
+    WxPayUnifiedOrderV3GlobalRequest.Amount amount = new WxPayUnifiedOrderV3GlobalRequest.Amount();
+    amount.setCurrency(WxPayConstants.CurrencyType.CNY);
+    amount.setTotal(100); // 1 yuan in cents
+    request.setAmount(amount);
+    
+    // Set payer
+    WxPayUnifiedOrderV3GlobalRequest.Payer payer = new WxPayUnifiedOrderV3GlobalRequest.Payer();
+    payer.setOpenid("test_openid");
+    request.setPayer(payer);
+    
+    // Set the new required fields for global payments
+    request.setTradeType("JSAPI");
+    request.setMerchantCategoryCode("5812"); // Example category code
+    
+    // Assert that all fields are properly set
+    assertNotNull(request.getTradeType());
+    assertNotNull(request.getMerchantCategoryCode());
+    assertEquals("JSAPI", request.getTradeType());
+    assertEquals("5812", request.getMerchantCategoryCode());
+    assertEquals(outTradeNo, request.getOutTradeNo());
+    assertEquals("Test overseas payment", request.getDescription());
+    assertEquals(100, request.getAmount().getTotal());
+    assertEquals("test_openid", request.getPayer().getOpenid());
+    
+    // Test JSON serialization contains the new fields
+    String json = GSON.toJson(request);
+    assertTrue(json.contains("trade_type"));
+    assertTrue(json.contains("merchant_category_code"));
+    assertTrue(json.contains("JSAPI"));
+    assertTrue(json.contains("5812"));
+  }
+
+  @Test
+  public void testGlobalTradeTypeEnum() {
+    // Test that all trade types have the correct global endpoints
+    assertEquals("/global/v3/transactions/app", GlobalTradeTypeEnum.APP.getUrl());
+    assertEquals("/global/v3/transactions/jsapi", GlobalTradeTypeEnum.JSAPI.getUrl());
+    assertEquals("/global/v3/transactions/native", GlobalTradeTypeEnum.NATIVE.getUrl());
+    assertEquals("/global/v3/transactions/h5", GlobalTradeTypeEnum.H5.getUrl());
+  }
+
+  @Test
+  public void testGlobalTradeTypeEnumValues() {
+    // Test that we have all the main trade types
+    GlobalTradeTypeEnum[] tradeTypes = GlobalTradeTypeEnum.values();
+    assertEquals(4, tradeTypes.length);
+    
+    // Test that we can convert between enum name and TradeTypeEnum
+    for (GlobalTradeTypeEnum globalType : tradeTypes) {
+      // This tests that the enum names match between Global and regular TradeTypeEnum
+      String name = globalType.name();
+      assertNotNull(name);
+      assertTrue(name.equals("APP") || name.equals("JSAPI") || name.equals("NATIVE") || name.equals("H5"));
+    }
+  }
+}
\ No newline at end of file
diff --git a/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/service/impl/BaseWxPayServiceImplTest.java b/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/service/impl/BaseWxPayServiceImplTest.java
index e777d68977..955071e10f 100644
--- a/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/service/impl/BaseWxPayServiceImplTest.java
+++ b/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/service/impl/BaseWxPayServiceImplTest.java
@@ -936,7 +936,7 @@ public void testQueryOrderV3WithProxy() {
       WxPayOrderQueryV3Request request = new WxPayOrderQueryV3Request();
       request.setOutTradeNo("n1ZvYqjAg3D3LUBa");
       WxPayConfig config = this.payService.getConfig();
-      config.setPayBaseUrl("http://api.mch.weixin.qq.com");
+      config.setApiHostUrl("http://api.mch.weixin.qq.com");
       config.setHttpProxyHost("12.11.1.113");
       config.setHttpProxyPort(8015);
       WxPayOrderQueryV3Result result = this.payService.queryOrderV3(request);
diff --git a/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/service/impl/ConnectionPoolUsageExampleTest.java b/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/service/impl/ConnectionPoolUsageExampleTest.java
new file mode 100644
index 0000000000..143743ffcf
--- /dev/null
+++ b/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/service/impl/ConnectionPoolUsageExampleTest.java
@@ -0,0 +1,61 @@
+package com.github.binarywang.wxpay.service.impl;
+
+import com.github.binarywang.wxpay.config.WxPayConfig;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+/**
+ * 演示连接池功能的示例测试
+ */
+public class ConnectionPoolUsageExampleTest {
+
+  @Test
+  public void demonstrateConnectionPoolUsage() throws Exception {
+    // 1. 创建配置并设置连接池参数
+    WxPayConfig config = new WxPayConfig();
+    config.setAppId("wx123456789");
+    config.setMchId("1234567890");
+    config.setMchKey("32位商户密钥32位商户密钥32位商户密钥");
+    
+    // 设置连接池参数(可选,有默认值)
+    config.setMaxConnTotal(50);  // 最大连接数,默认20
+    config.setMaxConnPerRoute(20); // 每个路由最大连接数,默认10
+    
+    // 2. 初始化连接池
+    CloseableHttpClient pooledClient = config.initHttpClient();
+    Assert.assertNotNull(pooledClient);
+    
+    // 3. 创建支付服务实例
+    WxPayServiceApacheHttpImpl payService = new WxPayServiceApacheHttpImpl();
+    payService.setConfig(config);
+    
+    // 4. 现在所有的HTTP请求都会使用连接池
+    // 对于非SSL请求,会复用同一个HttpClient实例
+    CloseableHttpClient client1 = payService.createHttpClient(false);
+    CloseableHttpClient client2 = payService.createHttpClient(false);
+    Assert.assertSame(client1, client2, "非SSL请求应该复用同一个客户端实例");
+    
+    // 对于SSL请求,也会复用同一个SSL HttpClient实例(需要配置证书后)
+    System.out.println("连接池配置成功!");
+    System.out.println("最大连接数:" + config.getMaxConnTotal());
+    System.out.println("每路由最大连接数:" + config.getMaxConnPerRoute());
+  }
+  
+  @Test
+  public void demonstrateDefaultConfiguration() throws Exception {
+    // 使用默认配置的示例
+    WxPayConfig config = new WxPayConfig();
+    config.setAppId("wx123456789");
+    config.setMchId("1234567890");
+    config.setMchKey("32位商户密钥32位商户密钥32位商户密钥");
+    
+    // 不设置连接池参数,使用默认值
+    CloseableHttpClient client = config.initHttpClient();
+    Assert.assertNotNull(client);
+    
+    // 验证默认配置
+    Assert.assertEquals(config.getMaxConnTotal(), 20, "默认最大连接数应该是20");
+    Assert.assertEquals(config.getMaxConnPerRoute(), 10, "默认每路由最大连接数应该是10");
+  }
+}
\ No newline at end of file
diff --git a/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/service/impl/OverseasWxPayExample.java b/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/service/impl/OverseasWxPayExample.java
new file mode 100644
index 0000000000..ccccf9c803
--- /dev/null
+++ b/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/service/impl/OverseasWxPayExample.java
@@ -0,0 +1,153 @@
+package com.github.binarywang.wxpay.service.impl;
+
+import com.github.binarywang.wxpay.bean.request.WxPayUnifiedOrderV3GlobalRequest;
+import com.github.binarywang.wxpay.bean.result.WxPayUnifiedOrderV3Result;
+import com.github.binarywang.wxpay.bean.result.enums.GlobalTradeTypeEnum;
+import com.github.binarywang.wxpay.config.WxPayConfig;
+import com.github.binarywang.wxpay.constant.WxPayConstants;
+import com.github.binarywang.wxpay.exception.WxPayException;
+import com.github.binarywang.wxpay.service.WxPayService;
+import me.chanjar.weixin.common.util.RandomUtils;
+
+/**
+ * 境外微信支付使用示例
+ * Example usage for overseas WeChat Pay
+ *
+ * @author Binary Wang
+ */
+public class OverseasWxPayExample {
+
+  /**
+   * 境外微信支付JSAPI下单示例
+   * Example for overseas WeChat Pay JSAPI order creation
+   */
+  public void createOverseasJsapiOrder(WxPayService payService) throws WxPayException {
+    // 创建境外支付请求对象
+    WxPayUnifiedOrderV3GlobalRequest request = new WxPayUnifiedOrderV3GlobalRequest();
+    
+    // 设置基础订单信息
+    request.setOutTradeNo(RandomUtils.getRandomStr()); // 商户订单号
+    request.setDescription("境外商品购买"); // 商品描述
+    request.setNotifyUrl("https://your-domain.com/notify"); // 支付通知地址
+    
+    // 设置金额信息
+    WxPayUnifiedOrderV3GlobalRequest.Amount amount = new WxPayUnifiedOrderV3GlobalRequest.Amount();
+    amount.setCurrency(WxPayConstants.CurrencyType.CNY); // 币种
+    amount.setTotal(100); // 金额,单位为分
+    request.setAmount(amount);
+    
+    // 设置支付者信息
+    WxPayUnifiedOrderV3GlobalRequest.Payer payer = new WxPayUnifiedOrderV3GlobalRequest.Payer();
+    payer.setOpenid("用户的openid"); // 用户openid
+    request.setPayer(payer);
+    
+    // 设置境外支付必需的参数
+    request.setTradeType("JSAPI"); // 交易类型
+    request.setMerchantCategoryCode("5812"); // 商户类目代码,境外商户必填
+    
+    // 可选:设置场景信息
+    WxPayUnifiedOrderV3GlobalRequest.SceneInfo sceneInfo = new WxPayUnifiedOrderV3GlobalRequest.SceneInfo();
+    sceneInfo.setPayerClientIp("用户IP地址");
+    request.setSceneInfo(sceneInfo);
+    
+    // 调用境外支付接口
+    WxPayUnifiedOrderV3Result.JsapiResult result = payService.createOrderV3Global(
+        GlobalTradeTypeEnum.JSAPI, 
+        request
+    );
+    
+    // 返回的result包含前端需要的支付参数
+    System.out.println("支付参数:" + result);
+  }
+
+  /**
+   * 境外微信支付APP下单示例
+   * Example for overseas WeChat Pay APP order creation
+   */
+  public void createOverseasAppOrder(WxPayService payService) throws WxPayException {
+    WxPayUnifiedOrderV3GlobalRequest request = new WxPayUnifiedOrderV3GlobalRequest();
+    
+    // 设置基础信息
+    request.setOutTradeNo(RandomUtils.getRandomStr());
+    request.setDescription("境外APP商品购买");
+    request.setNotifyUrl("https://your-domain.com/notify");
+    
+    // 设置金额
+    WxPayUnifiedOrderV3GlobalRequest.Amount amount = new WxPayUnifiedOrderV3GlobalRequest.Amount();
+    amount.setCurrency(WxPayConstants.CurrencyType.CNY);
+    amount.setTotal(200); // 2元
+    request.setAmount(amount);
+    
+    // APP支付不需要设置payer.openid,但需要设置空的payer对象
+    request.setPayer(new WxPayUnifiedOrderV3GlobalRequest.Payer());
+    
+    // 境外支付必需参数
+    request.setTradeType("APP");
+    request.setMerchantCategoryCode("5812");
+    
+    // 调用境外APP支付接口
+    WxPayUnifiedOrderV3Result.AppResult result = payService.createOrderV3Global(
+        GlobalTradeTypeEnum.APP, 
+        request
+    );
+    
+    System.out.println("APP支付参数:" + result);
+  }
+
+  /**
+   * 境外微信支付NATIVE下单示例
+   * Example for overseas WeChat Pay NATIVE order creation  
+   */
+  public void createOverseasNativeOrder(WxPayService payService) throws WxPayException {
+    WxPayUnifiedOrderV3GlobalRequest request = new WxPayUnifiedOrderV3GlobalRequest();
+    
+    request.setOutTradeNo(RandomUtils.getRandomStr());
+    request.setDescription("境外扫码支付");
+    request.setNotifyUrl("https://your-domain.com/notify");
+    
+    // 设置金额
+    WxPayUnifiedOrderV3GlobalRequest.Amount amount = new WxPayUnifiedOrderV3GlobalRequest.Amount();
+    amount.setCurrency(WxPayConstants.CurrencyType.CNY);
+    amount.setTotal(300); // 3元
+    request.setAmount(amount);
+    
+    // NATIVE支付不需要设置payer.openid
+    request.setPayer(new WxPayUnifiedOrderV3GlobalRequest.Payer());
+    
+    // 境外支付必需参数
+    request.setTradeType("NATIVE");
+    request.setMerchantCategoryCode("5812");
+    
+    // 调用境外NATIVE支付接口
+    String result = payService.createOrderV3Global(
+        GlobalTradeTypeEnum.NATIVE, 
+        request
+    );
+    
+    System.out.println("NATIVE支付二维码链接:" + result);
+  }
+
+  /**
+   * 配置示例
+   * Configuration example
+   */
+  public WxPayConfig createOverseasConfig() {
+    WxPayConfig config = new WxPayConfig();
+    
+    // 基础配置
+    config.setAppId("你的AppId");
+    config.setMchId("你的境外商户号");
+    config.setMchKey("你的商户密钥");
+    config.setNotifyUrl("https://your-domain.com/notify");
+    
+    // 境外支付使用的是全球API,在代码中会自动使用 https://apihk.mch.weixin.qq.com 作为基础URL
+    // 无需额外设置payBaseUrl,方法内部会自动处理
+    
+    // V3相关配置(境外支付也使用V3接口)
+    config.setPrivateKeyPath("你的私钥文件路径");
+    config.setCertSerialNo("你的商户证书序列号");
+    config.setApiV3Key("你的APIv3密钥");
+    
+    return config;
+  }
+}
\ No newline at end of file
diff --git a/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/service/impl/WxPayServiceApacheHttpImplConnectionPoolTest.java b/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/service/impl/WxPayServiceApacheHttpImplConnectionPoolTest.java
new file mode 100644
index 0000000000..393d601a69
--- /dev/null
+++ b/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/service/impl/WxPayServiceApacheHttpImplConnectionPoolTest.java
@@ -0,0 +1,86 @@
+package com.github.binarywang.wxpay.service.impl;
+
+import com.github.binarywang.wxpay.config.WxPayConfig;
+import com.github.binarywang.wxpay.exception.WxPayException;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+/**
+ * 测试WxPayServiceApacheHttpImpl的连接池功能
+ */
+public class WxPayServiceApacheHttpImplConnectionPoolTest {
+
+  @Test
+  public void testHttpClientConnectionPool() throws Exception {
+    WxPayConfig config = new WxPayConfig();
+    config.setAppId("test-app-id");
+    config.setMchId("test-mch-id");
+    config.setMchKey("test-mch-key");
+    
+    // 测试初始化连接池
+    CloseableHttpClient httpClient1 = config.initHttpClient();
+    Assert.assertNotNull(httpClient1, "HttpClient should not be null");
+    
+    // 再次获取,应该返回同一个实例
+    CloseableHttpClient httpClient2 = config.getHttpClient();
+    Assert.assertSame(httpClient1, httpClient2, "Should return the same HttpClient instance");
+    
+    // 验证连接池配置
+    WxPayServiceApacheHttpImpl service = new WxPayServiceApacheHttpImpl();
+    service.setConfig(config);
+    
+    // 测试不使用SSL的情况下应该使用连接池
+    CloseableHttpClient clientForNonSSL = service.createHttpClient(false);
+    Assert.assertSame(httpClient1, clientForNonSSL, "Should use pooled client for non-SSL requests");
+  }
+  
+  @Test
+  public void testSslHttpClientConnectionPool() throws Exception {
+    WxPayConfig config = new WxPayConfig();
+    config.setAppId("test-app-id");
+    config.setMchId("test-mch-id");
+    config.setMchKey("test-mch-key");
+    
+    // 为了测试SSL客户端,我们需要设置一些基本的SSL配置
+    // 注意:在实际使用中需要提供真实的证书
+    try {
+      CloseableHttpClient sslClient1 = config.initSslHttpClient();
+      Assert.assertNotNull(sslClient1, "SSL HttpClient should not be null");
+      
+      CloseableHttpClient sslClient2 = config.getSslHttpClient();
+      Assert.assertSame(sslClient1, sslClient2, "Should return the same SSL HttpClient instance");
+      
+      WxPayServiceApacheHttpImpl service = new WxPayServiceApacheHttpImpl();
+      service.setConfig(config);
+      
+      // 测试使用SSL的情况下应该使用SSL连接池
+      CloseableHttpClient clientForSSL = service.createHttpClient(true);
+      Assert.assertSame(sslClient1, clientForSSL, "Should use pooled SSL client for SSL requests");
+      
+    } catch (WxPayException e) {
+      // SSL初始化失败是预期的,因为我们没有提供真实的证书
+      // 这里主要是测试代码路径是否正确
+      Assert.assertTrue(e.getMessage().contains("证书") || e.getMessage().contains("商户号"), 
+          "Should fail with certificate or merchant ID related error");
+    }
+  }
+  
+  @Test
+  public void testConnectionPoolConfiguration() throws Exception {
+    WxPayConfig config = new WxPayConfig();
+    config.setAppId("test-app-id");
+    config.setMchId("test-mch-id");
+    config.setMchKey("test-mch-key");
+    config.setMaxConnTotal(50);
+    config.setMaxConnPerRoute(20);
+    
+    CloseableHttpClient httpClient = config.initHttpClient();
+    Assert.assertNotNull(httpClient, "HttpClient should not be null");
+    
+    // 验证配置值是否正确设置
+    Assert.assertEquals(config.getMaxConnTotal(), 50, "Max total connections should be 50");
+    Assert.assertEquals(config.getMaxConnPerRoute(), 20, "Max connections per route should be 20");
+  }
+}
\ No newline at end of file
diff --git a/weixin-java-qidian/pom.xml b/weixin-java-qidian/pom.xml
index 737af413d2..62734353df 100644
--- a/weixin-java-qidian/pom.xml
+++ b/weixin-java-qidian/pom.xml
@@ -7,7 +7,7 @@
   
     com.github.binarywang
     wx-java
-    4.7.2.B
+    4.7.8.B
   
 
   weixin-java-qidian
@@ -31,6 +31,11 @@
       okhttp
       provided
     
+    
+      org.apache.httpcomponents.client5
+      httpclient5
+      provided
+    
 
     
       org.testng
diff --git a/weixin-java-qidian/src/main/java/me/chanjar/weixin/qidian/api/WxQidianService.java b/weixin-java-qidian/src/main/java/me/chanjar/weixin/qidian/api/WxQidianService.java
index aeea34e829..b7c5a64c7a 100644
--- a/weixin-java-qidian/src/main/java/me/chanjar/weixin/qidian/api/WxQidianService.java
+++ b/weixin-java-qidian/src/main/java/me/chanjar/weixin/qidian/api/WxQidianService.java
@@ -340,7 +340,7 @@ public interface WxQidianService extends WxService {
    *
    * @return RequestHttp对象 request http
    */
-  RequestHttp getRequestHttp();
+  RequestHttp getRequestHttp();
 
   WxQidianDialService getDialService();
 
diff --git a/weixin-java-qidian/src/main/java/me/chanjar/weixin/qidian/api/impl/BaseWxQidianServiceImpl.java b/weixin-java-qidian/src/main/java/me/chanjar/weixin/qidian/api/impl/BaseWxQidianServiceImpl.java
index 2b7c7057a4..3f5023afe7 100644
--- a/weixin-java-qidian/src/main/java/me/chanjar/weixin/qidian/api/impl/BaseWxQidianServiceImpl.java
+++ b/weixin-java-qidian/src/main/java/me/chanjar/weixin/qidian/api/impl/BaseWxQidianServiceImpl.java
@@ -56,7 +56,7 @@ public boolean checkSignature(String timestamp, String nonce, String signature)
     try {
       return SHA1.gen(this.getWxMpConfigStorage().getToken(), timestamp, nonce).equals(signature);
     } catch (Exception e) {
-      log.error("Checking signature failed, and the reason is :" + e.getMessage());
+      log.error("Checking signature failed, and the reason is :{}", e.getMessage());
       return false;
     }
   }
@@ -405,7 +405,7 @@ public void setMaxRetryTimes(int maxRetryTimes) {
   }
 
   @Override
-  public RequestHttp getRequestHttp() {
+  public RequestHttp getRequestHttp() {
     return this;
   }
 
diff --git a/weixin-java-qidian/src/main/java/me/chanjar/weixin/qidian/api/impl/WxQidianServiceHttpClientImpl.java b/weixin-java-qidian/src/main/java/me/chanjar/weixin/qidian/api/impl/WxQidianServiceHttpClientImpl.java
index cd39f1a68e..2fc779a949 100644
--- a/weixin-java-qidian/src/main/java/me/chanjar/weixin/qidian/api/impl/WxQidianServiceHttpClientImpl.java
+++ b/weixin-java-qidian/src/main/java/me/chanjar/weixin/qidian/api/impl/WxQidianServiceHttpClientImpl.java
@@ -2,15 +2,14 @@
 
 import me.chanjar.weixin.common.error.WxErrorException;
 import me.chanjar.weixin.common.error.WxRuntimeException;
-import me.chanjar.weixin.common.util.http.HttpType;
+import me.chanjar.weixin.common.util.http.HttpClientType;
+import me.chanjar.weixin.common.util.http.apache.ApacheBasicResponseHandler;
 import me.chanjar.weixin.common.util.http.apache.ApacheHttpClientBuilder;
 import me.chanjar.weixin.common.util.http.apache.DefaultApacheHttpClientBuilder;
 import me.chanjar.weixin.qidian.config.WxQidianConfigStorage;
 import org.apache.http.HttpHost;
 import org.apache.http.client.config.RequestConfig;
-import org.apache.http.client.methods.CloseableHttpResponse;
 import org.apache.http.client.methods.HttpGet;
-import org.apache.http.impl.client.BasicResponseHandler;
 import org.apache.http.impl.client.CloseableHttpClient;
 
 import java.io.IOException;
@@ -39,8 +38,8 @@ public HttpHost getRequestHttpProxy() {
   }
 
   @Override
-  public HttpType getRequestType() {
-    return HttpType.APACHE_HTTP;
+  public HttpClientType getRequestType() {
+    return HttpClientType.APACHE_HTTP;
   }
 
   @Override
@@ -86,11 +85,8 @@ public String getAccessToken(boolean forceRefresh) throws WxErrorException {
           RequestConfig requestConfig = RequestConfig.custom().setProxy(this.getRequestHttpProxy()).build();
           httpGet.setConfig(requestConfig);
         }
-        try (CloseableHttpResponse response = getRequestHttpClient().execute(httpGet)) {
-          return this.extractAccessToken(new BasicResponseHandler().handleResponse(response));
-        } finally {
-          httpGet.releaseConnection();
-        }
+        String responseContent = getRequestHttpClient().execute(httpGet, ApacheBasicResponseHandler.INSTANCE);
+        return this.extractAccessToken(responseContent);
       } catch (IOException e) {
         throw new WxRuntimeException(e);
       }
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
new file mode 100644
index 0000000000..a5cc23f0a2
--- /dev/null
+++ b/weixin-java-qidian/src/main/java/me/chanjar/weixin/qidian/api/impl/WxQidianServiceHttpComponentsImpl.java
@@ -0,0 +1,99 @@
+package me.chanjar.weixin.qidian.api.impl;
+
+import me.chanjar.weixin.common.error.WxErrorException;
+import me.chanjar.weixin.common.error.WxRuntimeException;
+import me.chanjar.weixin.common.util.http.HttpClientType;
+import me.chanjar.weixin.common.util.http.hc.BasicResponseHandler;
+import me.chanjar.weixin.common.util.http.hc.DefaultHttpComponentsClientBuilder;
+import me.chanjar.weixin.common.util.http.hc.HttpComponentsClientBuilder;
+import me.chanjar.weixin.qidian.config.WxQidianConfigStorage;
+import org.apache.hc.client5.http.classic.methods.HttpGet;
+import org.apache.hc.client5.http.config.RequestConfig;
+import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
+import org.apache.hc.core5.http.HttpHost;
+
+import java.io.IOException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.locks.Lock;
+
+import static me.chanjar.weixin.qidian.enums.WxQidianApiUrl.Other.GET_ACCESS_TOKEN_URL;
+
+/**
+ * apache http client5 方式实现.
+ *
+ * @author altusea
+ */
+public class WxQidianServiceHttpComponentsImpl extends BaseWxQidianServiceImpl {
+  private CloseableHttpClient httpClient;
+  private HttpHost httpProxy;
+
+  @Override
+  public CloseableHttpClient getRequestHttpClient() {
+    return httpClient;
+  }
+
+  @Override
+  public HttpHost getRequestHttpProxy() {
+    return httpProxy;
+  }
+
+  @Override
+  public HttpClientType getRequestType() {
+    return HttpClientType.HTTP_COMPONENTS;
+  }
+
+  @Override
+  public void initHttp() {
+    WxQidianConfigStorage configStorage = this.getWxMpConfigStorage();
+    HttpComponentsClientBuilder apacheHttpClientBuilder = DefaultHttpComponentsClientBuilder.get();
+
+    apacheHttpClientBuilder.httpProxyHost(configStorage.getHttpProxyHost())
+        .httpProxyPort(configStorage.getHttpProxyPort()).httpProxyUsername(configStorage.getHttpProxyUsername())
+        .httpProxyPassword(configStorage.getHttpProxyPassword().toCharArray());
+
+    if (configStorage.getHttpProxyHost() != null && configStorage.getHttpProxyPort() > 0) {
+      this.httpProxy = new HttpHost(configStorage.getHttpProxyHost(), configStorage.getHttpProxyPort());
+    }
+
+    this.httpClient = apacheHttpClientBuilder.build();
+  }
+
+  @Override
+  public String getAccessToken(boolean forceRefresh) throws WxErrorException {
+    final WxQidianConfigStorage config = this.getWxMpConfigStorage();
+    if (!config.isAccessTokenExpired() && !forceRefresh) {
+      return config.getAccessToken();
+    }
+
+    Lock lock = config.getAccessTokenLock();
+    boolean locked = false;
+    try {
+      do {
+        locked = lock.tryLock(100, TimeUnit.MILLISECONDS);
+        if (!forceRefresh && !config.isAccessTokenExpired()) {
+          return config.getAccessToken();
+        }
+      } while (!locked);
+
+      String url = String.format(GET_ACCESS_TOKEN_URL.getUrl(config), config.getAppId(), config.getSecret());
+      try {
+        HttpGet httpGet = new HttpGet(url);
+        if (this.getRequestHttpProxy() != null) {
+          RequestConfig requestConfig = RequestConfig.custom().setProxy(this.getRequestHttpProxy()).build();
+          httpGet.setConfig(requestConfig);
+        }
+        String responseContent = getRequestHttpClient().execute(httpGet, BasicResponseHandler.INSTANCE);
+        return this.extractAccessToken(responseContent);
+      } catch (IOException e) {
+        throw new WxRuntimeException(e);
+      }
+    } catch (InterruptedException e) {
+      throw new WxRuntimeException(e);
+    } finally {
+      if (locked) {
+        lock.unlock();
+      }
+    }
+  }
+
+}
diff --git a/weixin-java-qidian/src/main/java/me/chanjar/weixin/qidian/api/impl/WxQidianServiceJoddHttpImpl.java b/weixin-java-qidian/src/main/java/me/chanjar/weixin/qidian/api/impl/WxQidianServiceJoddHttpImpl.java
index 41ec6d9f38..18a2262a3f 100644
--- a/weixin-java-qidian/src/main/java/me/chanjar/weixin/qidian/api/impl/WxQidianServiceJoddHttpImpl.java
+++ b/weixin-java-qidian/src/main/java/me/chanjar/weixin/qidian/api/impl/WxQidianServiceJoddHttpImpl.java
@@ -6,7 +6,7 @@
 import jodd.http.net.SocketHttpConnectionProvider;
 import me.chanjar.weixin.common.error.WxErrorException;
 import me.chanjar.weixin.common.error.WxRuntimeException;
-import me.chanjar.weixin.common.util.http.HttpType;
+import me.chanjar.weixin.common.util.http.HttpClientType;
 import me.chanjar.weixin.qidian.config.WxQidianConfigStorage;
 
 import java.util.concurrent.TimeUnit;
@@ -34,8 +34,8 @@ public ProxyInfo getRequestHttpProxy() {
   }
 
   @Override
-  public HttpType getRequestType() {
-    return HttpType.JODD_HTTP;
+  public HttpClientType getRequestType() {
+    return HttpClientType.JODD_HTTP;
   }
 
   @Override
diff --git a/weixin-java-qidian/src/main/java/me/chanjar/weixin/qidian/api/impl/WxQidianServiceOkHttpImpl.java b/weixin-java-qidian/src/main/java/me/chanjar/weixin/qidian/api/impl/WxQidianServiceOkHttpImpl.java
index 92cf51e670..5ff6734ccb 100644
--- a/weixin-java-qidian/src/main/java/me/chanjar/weixin/qidian/api/impl/WxQidianServiceOkHttpImpl.java
+++ b/weixin-java-qidian/src/main/java/me/chanjar/weixin/qidian/api/impl/WxQidianServiceOkHttpImpl.java
@@ -2,7 +2,7 @@
 
 import me.chanjar.weixin.common.error.WxErrorException;
 import me.chanjar.weixin.common.error.WxRuntimeException;
-import me.chanjar.weixin.common.util.http.HttpType;
+import me.chanjar.weixin.common.util.http.HttpClientType;
 import me.chanjar.weixin.common.util.http.okhttp.DefaultOkHttpClientBuilder;
 import me.chanjar.weixin.common.util.http.okhttp.OkHttpProxyInfo;
 import me.chanjar.weixin.qidian.config.WxQidianConfigStorage;
@@ -35,8 +35,8 @@ public OkHttpProxyInfo getRequestHttpProxy() {
   }
 
   @Override
-  public HttpType getRequestType() {
-    return HttpType.OK_HTTP;
+  public HttpClientType getRequestType() {
+    return HttpClientType.OK_HTTP;
   }
 
   @Override
@@ -60,9 +60,7 @@ public String getAccessToken(boolean forceRefresh) throws WxErrorException {
       Request request = new Request.Builder().url(url).get().build();
       Response response = getRequestHttpClient().newCall(request).execute();
       return this.extractAccessToken(Objects.requireNonNull(response.body()).string());
-    } catch (IOException e) {
-      throw new WxRuntimeException(e);
-    } catch (InterruptedException e) {
+    } catch (IOException | InterruptedException e) {
       throw new WxRuntimeException(e);
     } finally {
       if (locked) {
diff --git a/weixin-java-qidian/src/main/java/me/chanjar/weixin/qidian/util/WxQidianConfigStorageHolder.java b/weixin-java-qidian/src/main/java/me/chanjar/weixin/qidian/util/WxQidianConfigStorageHolder.java
index ec2e872942..2b47667996 100644
--- a/weixin-java-qidian/src/main/java/me/chanjar/weixin/qidian/util/WxQidianConfigStorageHolder.java
+++ b/weixin-java-qidian/src/main/java/me/chanjar/weixin/qidian/util/WxQidianConfigStorageHolder.java
@@ -5,12 +5,7 @@
  * created on  2020年12月26日
  */
 public class WxQidianConfigStorageHolder {
-  private static final ThreadLocal THREAD_LOCAL = new ThreadLocal() {
-    @Override
-    protected String initialValue() {
-      return "default";
-    }
-  };
+  private static final ThreadLocal THREAD_LOCAL = ThreadLocal.withInitial(() -> "default");
 
   public static String get() {
     return THREAD_LOCAL.get();