diff --git a/.github/agents/my-agent.agent.md b/.github/agents/my-agent.agent.md new file mode 100644 index 0000000000..dcce85fd88 --- /dev/null +++ b/.github/agents/my-agent.agent.md @@ -0,0 +1,13 @@ +--- +# Fill in the fields below to create a basic custom agent for your repository. +# The Copilot CLI can be used for local testing: https://gh.io/customagents/cli +# To make this agent available, merge this file into the default repository branch. +# For format details, see: https://gh.io/customagents/config + +name: 全部用中文 +description: 需要用中文,包括PR标题和分析总结过程 +--- + +# My Agent + +请使用中文输出思考过程和总结,包括PR标题,提交commit信息也要使用中文 diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 0000000000..cad29d96d9 --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,202 @@ +# Copilot Instruction +请始终使用中文生成 Pull Request 的标题、描述和提交信息 + + +# WxJava - 微信 Java SDK 开发说明 + +WxJava 是一个支持多种微信平台的完整 Java SDK,包含公众号、小程序、微信支付、企业微信、开放平台、视频号、企点等多种功能模块。 + +**请始终优先参考本说明,只有在遇到与此内容不一致的意外信息时,才退而使用搜索或 bash 命令。** + +## 高效开发指南 + +### 前置条件与环境准备 +- **Java 要求**:JDK 8+(项目最低目标为 Java 8) +- **Maven**:推荐 Maven 3.6+(已验证 Maven 3.9.11) +- **IDE**:推荐使用 IntelliJ IDEA(项目针对 IDEA 优化) + +### 引导、构建与校验 +克隆仓库后按顺序执行以下命令: + +```bash +# 1. 基础编译(请勿中断 - 约需 4-5 分钟) +mvn clean compile -DskipTests=true --no-transfer-progress +# 超时时间:建议设置 8 分钟以上。实际时间:约 4 分钟 + +# 2. 完整打包(请勿中断 - 约需 2-3 分钟) +mvn clean package -DskipTests=true --no-transfer-progress +# 超时时间:建议设置 5 分钟以上。实际时间:约 2 分钟 + +# 3. 代码质量校验(请勿中断 - 约需 45-60 秒) +mvn checkstyle:check --no-transfer-progress +# 超时时间:建议设置 3 分钟以上。实际时间:约 50 秒 +``` + +重要时间说明: +- 绝对不要中断任意 Maven 构建命令 +- 编译阶段耗时最长(约 4 分钟),原因是项目包含 34 个模块 +- 后续构建会更快,因为存在增量编译 +- 始终使用 `--no-transfer-progress` 以减少日志噪音 + +### 测试结构 +- **测试框架**:TestNG(非 JUnit) +- **测试文件**:共有 298 个测试文件 +- **默认行为**:pom.xml 中默认禁用测试(`true`) +- **测试配置**:测试需要通过 test-config.xml 提供真实的微信 API 凭据 +- **注意**:没有真实微信 API 凭据请不要尝试运行测试,测试将会失败 + +## 项目结构与导航 + +### 核心 SDK 模块(主要开发区) +- `weixin-java-common/` - 通用工具与基础类(最重要) +- `weixin-java-mp/` - 公众号 SDK +- `weixin-java-pay/` - 微信支付 SDK +- `weixin-java-miniapp/` - 小程序 SDK +- `weixin-java-cp/` - 企业微信 SDK +- `weixin-java-open/` - 开放平台 SDK +- `weixin-java-channel/` - 视频号 / Channel SDK +- `weixin-java-qidian/` - 企点 SDK + +### 框架集成模块 +- `spring-boot-starters/` - Spring Boot 自动配置 starter +- `solon-plugins/` - Solon 框架插件 +- `weixin-graal/` - GraalVM 本地镜像支持 + +### 配置与质量控制 +- `quality-checks/google_checks.xml` - Checkstyle 配置 +- `.editorconfig` - 代码格式规则(2 个空格等于 1 个制表) +- `pom.xml` - 根级 Maven 配置 + +## 开发工作流 + +### 修改代码的流程 +1. 修改前务必先构建以建立干净基线: + ```bash + mvn clean compile --no-transfer-progress + ``` + +2. 遵循代码风格(由 checkstyle 强制): + - 缩进使用 2 个空格(不要用制表符) + - 遵循 Google Java 风格指南 + - 在 IDE 中安装 EditorConfig 插件 + +3. 增量验证修改: + ```bash + # 每次修改后运行: + mvn compile --no-transfer-progress + mvn checkstyle:check --no-transfer-progress + ``` + +### 提交修改前的必须校验 +请务必按顺序完成以下校验步骤: + +1. 代码风格校验: + ```bash + mvn checkstyle:check --no-transfer-progress + # 必须通过 - 约需 50 秒 + ``` + +2. 完整清理构建: + ```bash + mvn clean package -DskipTests=true --no-transfer-progress + # 必须成功 - 约需 2 分钟 + ``` + +3. 文档:为公共方法和类补充或更新 javadoc +4. 贡献规范:遵循 `CONTRIBUTING.md`,Pull Request 必须以 `develop` 分支为目标 + +## 模块依赖与构建顺序 + +### 核心模块依赖(构建顺序) +1. `weixin-graal`(GraalVM 支持) +2. `weixin-java-common`(所有模块的基础) +3. 核心 SDK 模块(mp、pay、miniapp、cp、open、channel、qidian) +4. 框架集成(spring-boot-starters、solon-plugins) + +### 主要关系模式 +- 所有 SDK 模块都依赖于 `weixin-java-common` +- Spring Boot starters 依赖对应的 SDK 模块 +- Solon 插件遵循与 Spring Boot starters 相同的依赖模式 +- 每个模块都有单账号与多账号配置支持 + +## 常见任务与命令 + +### 验证指定模块 +```bash +# 构建单个模块(将 'weixin-java-mp' 替换为目标模块): +cd weixin-java-mp +mvn clean compile --no-transfer-progress +``` + +### 检查依赖 +```bash +# 分析依赖树: +mvn dependency:tree --no-transfer-progress + +# 检查依赖更新: +./others/check-dependency-updates.sh +``` + +### 发布与发布准备 +```bash +# 版本检查: +mvn versions:display-property-updates --no-transfer-progress + +# 部署(需要凭据): +mvn clean deploy -P release --no-transfer-progress +``` + +## 重要文件与位置 + +### 配置文件 +- `pom.xml` - 根级 Maven 配置与依赖管理 +- `quality-checks/google_checks.xml` - Checkstyle 规则 +- `.editorconfig` - IDE 格式化配置 +- `.github/workflows/maven-publish.yml` - CI/CD 工作流 + +### 文档 +- `README.md` - 项目概览与使用说明(中文) +- `CONTRIBUTING.md` - 贡献指南 +- `demo.md` - 示例项目与演示链接 +- 每个模块均有单独的文档与示例 + +### 测试资源 +- `*/src/test/resources/test-config.sample.xml` - 测试配置模板 +- 测试运行需要真实的微信 API 凭据 + +## SDK 使用模式 + +### Maven 依赖示例 +```xml + + com.github.binarywang + weixin-java-mp + 4.7.0 + +``` + +### 常见开发区域 +- **API 客户端实现**:位于 `*/service/impl/` 目录 +- **模型类**:位于 `*/bean/` 目录 +- **配置**:位于 `*/config/` 目录 +- **工具类**:位于 `weixin-java-common` 的 `*/util/` 目录 + +## 故障排查 + +### 构建问题 +- **OutOfMemoryError**:增加 Maven 内存:`export MAVEN_OPTS="-Xmx2g"` +- **编译失败**:通常为依赖问题 - 先执行 `mvn clean` +- **Checkstyle 失败**:检查 IDE 的 `.editorconfig` 设置 + +### 常见陷阱 +- **测试默认跳过**:这是正常现象 — 测试需要微信 API 凭据 +- **多模块变更**:总是在仓库根目录构建,而不是单独模块 +- **分支目标**:Pull Request 必须以 `develop` 分支为目标,而不是 `master` 或 `release` + +## 性能说明 +- **首次构建**:由于依赖下载,耗时 4-5 分钟 +- **增量构建**:通常更快(约 30-60 秒) +- **Checkstyle**:运行迅速(约 50 秒),应当经常运行 +- **IDE 性能**:项目使用 Lombok,请确保启用注解处理 + +注意:本项目为 SDK 库项目,而非可运行应用。修改应以 API 功能为主,不要改动应用级行为。 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/README.md b/README.md index ab645b7c4f..f1cccac4b3 100644 --- a/README.md +++ b/README.md @@ -1,72 +1,77 @@ ## 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,开源支付系统 - -
- - mp qrcode - - - - diboot低代码开发平台 - - - - aliyun ad - -
- - Featured|HelloGitHub - - binarywang%2FWxJava | 趋势转变 - -
### 重要信息 1. [`WxJava` 荣获 `GitCode` 2024年度十大开源社区奖项](https://mp.weixin.qq.com/s/wM_UlMsDm3IZ1CPPDvcvQw)。 2. 项目合作洽谈请联系微信`binary0000`(在微信里自行搜索并添加好友,请注明来意,如有关于SDK问题需讨论请参考下文入群讨论,不要加此微信)。 -3. **2023-12-28 发布 [【4.6.0正式版】](https://mp.weixin.qq.com/s/9Hhc_8w-v7ogS_TEAsqfAg)**! -4. 贡献源码可以参考视频:[【贡献源码全过程(上集)】](https://mp.weixin.qq.com/s/3xUZSATWwHR_gZZm207h7Q)、[【贡献源码全过程(下集)】](https://mp.weixin.qq.com/s/nyzJwVVoYSJ4hSbwyvTx9A) ,友情提供:[程序员小山与Bug](https://space.bilibili.com/473631007) -5. 新手重要提示:本项目仅是一个SDK开发工具包,未提供Web实现,建议使用 `maven` 或 `gradle` 引用本项目即可使用本SDK提供的各种功能,详情可参考 **[【Demo项目】](demo.md)** 或本项目中的部分单元测试代码; -6. 微信开发新手请务必阅读【开发文档】([Gitee Wiki](https://gitee.com/binary/weixin-java-tools/wikis/Home) 或者 [Github Wiki](https://github.com/binarywang/WxJava/wiki))的常见问题部分,可以少走很多弯路,节省不少时间。 -7. 技术交流群:想获得QQ群/微信群/钉钉企业群等信息的同学,请使用微信扫描上面的微信公众号二维码关注 `WxJava` 后点击相关菜单即可获取加入方式,同时也可以在微信中搜索 `weixin-java-tools` 或 `WxJava` 后选择正确的公众号进行关注,该公众号会及时通知SDK相关更新信息,并不定期分享微信Java开发相关技术知识; -8. 钉钉技术交流群:`32206329`(技术交流2群), `30294972`(技术交流1群,目前已满),`35724728`(通知群,实时通知Github项目变更记录)。 -9. 微信开发新手或者Java开发新手在群内提问或新开Issue提问前,请先阅读[【提问的智慧】](https://github.com/ryanhanwu/How-To-Ask-Questions-The-Smart-Way/blob/master/README-zh_CN.md),并确保已查阅过 [【开发文档Wiki】](https://github.com/binarywang/WxJava/wiki) ,避免浪费大家的宝贵时间; -10. 寻求帮助时需贴代码或大长串异常信息的,请利用 http://paste.ubuntu.com +3. **2024-12-30 发布 [【4.7.0正式版】](https://mp.weixin.qq.com/s/_7k-XLYBqeJJhvHWCsdT0A)**! +5. 贡献源码可以参考视频:[【贡献源码全过程(上集)】](https://mp.weixin.qq.com/s/3xUZSATWwHR_gZZm207h7Q)、[【贡献源码全过程(下集)】](https://mp.weixin.qq.com/s/nyzJwVVoYSJ4hSbwyvTx9A) ,友情提供:[程序员小山与Bug](https://space.bilibili.com/473631007) +6. 新手重要提示:本项目仅是一个SDK开发工具包,未提供Web实现,建议使用 `maven` 或 `gradle` 引用本项目即可使用本SDK提供的各种功能,详情可参考 **[【Demo项目】](demo.md)** 或本项目中的部分单元测试代码; +7. 微信开发新手请务必阅读【开发文档】([Gitee Wiki](https://gitee.com/binary/weixin-java-tools/wikis/Home) 或者 [Github Wiki](https://github.com/binarywang/WxJava/wiki))的常见问题部分,可以少走很多弯路,节省不少时间。 +8. 技术交流群:想获得QQ群/微信群/钉钉企业群等信息的同学,请使用微信扫描上面的微信公众号二维码关注 `WxJava` 后点击相关菜单即可获取加入方式,同时也可以在微信中搜索 `weixin-java-tools` 或 `WxJava` 后选择正确的公众号进行关注,该公众号会及时通知SDK相关更新信息,并不定期分享微信Java开发相关技术知识; +9. 钉钉技术交流群:`32206329`(技术交流2群), `30294972`(技术交流1群,目前已满),`35724728`(通知群,实时通知Github项目变更记录)。 +10. 微信开发新手或者Java开发新手在群内提问或新开Issue提问前,请先阅读[【提问的智慧】](https://github.com/ryanhanwu/How-To-Ask-Questions-The-Smart-Way/blob/master/README-zh_CN.md),并确保已查阅过 [【开发文档Wiki】](https://github.com/binarywang/WxJava/wiki) ,避免浪费大家的宝贵时间; +11. 寻求帮助时需贴代码或大长串异常信息的,请利用 http://paste.ubuntu.com -------------------------------- ### 其他说明 @@ -84,13 +89,13 @@ --------------------------------- ### 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 com.github.binarywang (不同模块参考下文) - 4.6.0 + 4.7.0 ``` @@ -101,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`)主要用于第三方平台,代公众号或小程序进行开发和管理 + --------------------------------- ### 版本说明 @@ -108,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/docs/MINIAPP_KEFU_SERVICE.md b/docs/MINIAPP_KEFU_SERVICE.md new file mode 100644 index 0000000000..96cf4c3831 --- /dev/null +++ b/docs/MINIAPP_KEFU_SERVICE.md @@ -0,0 +1,80 @@ +# WeChat Mini Program Customer Service Management + +This document describes the new customer service management functionality added to the WxJava Mini Program SDK. + +## Overview + +Previously, the mini program module only had: +- `WxMaCustomserviceWorkService` - For binding mini programs to enterprise WeChat customer service +- `WxMaMsgService.sendKefuMsg()` - For sending customer service messages + +The new `WxMaKefuService` adds comprehensive customer service management capabilities: + +## Features + +### Customer Service Account Management +- `kfList()` - Get list of customer service accounts +- `kfAccountAdd()` - Add new customer service account +- `kfAccountUpdate()` - Update customer service account +- `kfAccountDel()` - Delete customer service account + +### Session Management +- `kfSessionCreate()` - Create customer service session +- `kfSessionClose()` - Close customer service session +- `kfSessionGet()` - Get customer session status +- `kfSessionList()` - Get customer service session list + +## Usage Example + +```java +// Get the customer service management service +WxMaKefuService kefuService = wxMaService.getKefuService(); + +// Add a new customer service account +WxMaKfAccountRequest request = WxMaKfAccountRequest.builder() + .kfAccount("service001@example") + .kfNick("Customer Service 001") + .kfPwd("password123") + .build(); +boolean result = kefuService.kfAccountAdd(request); + +// Create a session between user and customer service +boolean sessionResult = kefuService.kfSessionCreate("user_openid", "service001@example"); + +// Get customer service list +WxMaKfList kfList = kefuService.kfList(); +``` + +## Bean Classes + +### Request Objects +- `WxMaKfAccountRequest` - For customer service account operations +- `WxMaKfSessionRequest` - For session operations + +### Response Objects +- `WxMaKfInfo` - Customer service account information +- `WxMaKfList` - List of customer service accounts +- `WxMaKfSession` - Session information +- `WxMaKfSessionList` - List of sessions + +## API Endpoints + +The service uses the following WeChat Mini Program API endpoints: +- `https://api.weixin.qq.com/cgi-bin/customservice/getkflist` - Get customer service list +- `https://api.weixin.qq.com/customservice/kfaccount/add` - Add customer service account +- `https://api.weixin.qq.com/customservice/kfaccount/update` - Update customer service account +- `https://api.weixin.qq.com/customservice/kfaccount/del` - Delete customer service account +- `https://api.weixin.qq.com/customservice/kfsession/create` - Create session +- `https://api.weixin.qq.com/customservice/kfsession/close` - Close session +- `https://api.weixin.qq.com/customservice/kfsession/getsession` - Get session +- `https://api.weixin.qq.com/customservice/kfsession/getsessionlist` - Get session list + +## Integration + +The service is automatically available through the main `WxMaService` interface: + +```java +WxMaKefuService kefuService = wxMaService.getKefuService(); +``` + +This fills the gap mentioned in the original issue and provides full customer service management capabilities for WeChat Mini Programs. \ No newline at end of file diff --git a/docs/NEW_TRANSFER_API_SUPPORT.md b/docs/NEW_TRANSFER_API_SUPPORT.md new file mode 100644 index 0000000000..c7e9eaf490 --- /dev/null +++ b/docs/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/docs/NEW_TRANSFER_API_USAGE.md b/docs/NEW_TRANSFER_API_USAGE.md new file mode 100644 index 0000000000..9d1ac8254a --- /dev/null +++ b/docs/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/docs/QUARKUS_SUPPORT.md b/docs/QUARKUS_SUPPORT.md new file mode 100644 index 0000000000..c20fb2c28b --- /dev/null +++ b/docs/QUARKUS_SUPPORT.md @@ -0,0 +1,112 @@ +# WxJava Quarkus/GraalVM Native Image Support + +## 概述 + +从 4.7.8.B 版本开始,WxJava 提供了对 Quarkus 和 GraalVM Native Image 的支持。这允许您将使用 WxJava 的应用程序编译为原生可执行文件,从而获得更快的启动速度和更低的内存占用。 + +## 问题背景 + +在之前的版本中,使用 Quarkus 构建 Native Image 时会遇到以下错误: + +``` +Error: Unsupported features in 3 methods +Detailed message: +Error: Detected an instance of Random/SplittableRandom class in the image heap. +Instances created during image generation have cached seed values and don't behave as expected. +The culprit object has been instantiated by the 'org.apache.http.impl.auth.NTLMEngineImpl' class initializer +``` + +## 解决方案 + +为了解决这个问题,WxJava 进行了以下改进: + +### 1. Random 实例的延迟初始化 + +所有 `java.util.Random` 实例都已改为延迟初始化,避免在类加载时创建: + +- `RandomUtils` - 使用双重检查锁定模式延迟初始化 +- `SignUtils` - 使用双重检查锁定模式延迟初始化 +- `WxCryptUtil` - 使用双重检查锁定模式延迟初始化 + +### 2. Native Image 配置 + +在 `weixin-java-common` 模块中添加了 GraalVM Native Image 配置文件: + +- `META-INF/native-image/com.github.binarywang/weixin-java-common/native-image.properties` + - 配置 Apache HttpClient 相关类在运行时初始化,避免在构建时创建 SecureRandom 实例 + +- `META-INF/native-image/com.github.binarywang/weixin-java-common/reflect-config.json` + - 配置反射访问的类和方法 + +## 使用方式 + +### Quarkus 项目配置 + +在您的 Quarkus 项目中使用 WxJava,只需正常引入依赖即可: + +```xml + + com.github.binarywang + weixin-java-miniapp + 4.7.8.B + +``` + +### 构建 Native Image + +使用 Quarkus 构建原生可执行文件: + +```bash +./mvnw package -Pnative +``` + +或使用容器构建: + +```bash +./mvnw package -Pnative -Dquarkus.native.container-build=true +``` + +### GraalVM Native Image + +如果直接使用 GraalVM Native Image 工具: + +```bash +native-image --no-fallback \ + -H:+ReportExceptionStackTraces \ + -jar your-application.jar +``` + +WxJava 的配置文件会自动被 Native Image 工具识别和应用。 + +## 测试验证 + +建议在构建 Native Image 后进行以下测试: + +1. 验证应用程序可以正常启动 +2. 验证微信 API 调用功能正常 +3. 验证随机字符串生成功能正常 +4. 验证加密/解密功能正常 + +## 已知限制 + +- 本配置主要针对 Quarkus 3.x 和 GraalVM 22.x+ 版本进行测试 +- 如果使用其他 Native Image 构建工具(如 Spring Native),可能需要额外配置 +- 部分反射使用可能需要根据实际使用的 WxJava 功能进行调整 + +## 问题反馈 + +如果在使用 Quarkus/GraalVM Native Image 时遇到问题,请通过以下方式反馈: + +1. 在 [GitHub Issues](https://github.com/binarywang/WxJava/issues) 提交问题 +2. 提供详细的错误信息和 Native Image 构建日志 +3. 说明使用的 Quarkus 版本和 GraalVM 版本 + +## 参考资料 + +- [Quarkus 官方文档](https://quarkus.io/) +- [GraalVM Native Image 文档](https://www.graalvm.org/latest/reference-manual/native-image/) +- [Quarkus Tips for Writing Native Applications](https://quarkus.io/guides/writing-native-applications-tips) + +## 贡献 + +欢迎提交 PR 完善 Quarkus/GraalVM 支持!如果您发现了新的兼容性问题或有改进建议,请参考 [代码贡献指南](CONTRIBUTING.md)。 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 730f6b5809..eba8e083a7 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 com.github.binarywang wx-java - 4.7.0 + 4.7.9.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 bf956526c8..971fc184ab 100644 --- a/solon-plugins/pom.xml +++ b/solon-plugins/pom.xml @@ -6,7 +6,7 @@ com.github.binarywang wx-java - 4.7.0 + 4.7.9.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 aa9911e115..df721c03a3 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.0 + 4.7.9.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 dda371c780..5e497a4c46 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.0 + 4.7.9.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 d2218490b9..c39b84dc23 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.0 + 4.7.9.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 05598d6b9c..412db6ea98 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.0 + 4.7.9.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 deca9a2ffa..a1cab06d60 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.0 + 4.7.9.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 5075140322..513de54cd7 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.0 + 4.7.9.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 67f9e2da37..b65c3e4945 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.0 + 4.7.9.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 5dcea9ac93..b504caf7d8 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.0 + 4.7.9.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 bd8c9e3e45..368d4a6258 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.0 + 4.7.9.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 47153d8f13..baace7b37b 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.0 + 4.7.9.B 4.0.0 diff --git a/solon-plugins/wx-java-pay-solon-plugin/src/main/java/com/binarywang/solon/wxjava/pay/config/WxPayAutoConfiguration.java b/solon-plugins/wx-java-pay-solon-plugin/src/main/java/com/binarywang/solon/wxjava/pay/config/WxPayAutoConfiguration.java index 1957e4157e..94112c7d9d 100644 --- a/solon-plugins/wx-java-pay-solon-plugin/src/main/java/com/binarywang/solon/wxjava/pay/config/WxPayAutoConfiguration.java +++ b/solon-plugins/wx-java-pay-solon-plugin/src/main/java/com/binarywang/solon/wxjava/pay/config/WxPayAutoConfiguration.java @@ -46,13 +46,20 @@ 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())); + payConfig.setStrictlyNeedWechatPaySerial(this.properties.isStrictlyNeedWechatPaySerial()); + payConfig.setFullPublicKeyModel(this.properties.isFullPublicKeyModel()); wxPayService.setConfig(payConfig); return wxPayService; diff --git a/solon-plugins/wx-java-pay-solon-plugin/src/main/java/com/binarywang/solon/wxjava/pay/properties/WxPayProperties.java b/solon-plugins/wx-java-pay-solon-plugin/src/main/java/com/binarywang/solon/wxjava/pay/properties/WxPayProperties.java index a882f6ac31..0b035e983e 100644 --- a/solon-plugins/wx-java-pay-solon-plugin/src/main/java/com/binarywang/solon/wxjava/pay/properties/WxPayProperties.java +++ b/solon-plugins/wx-java-pay-solon-plugin/src/main/java/com/binarywang/solon/wxjava/pay/properties/WxPayProperties.java @@ -82,4 +82,40 @@ public class WxPayProperties { */ private boolean useSandboxEnv; + /** + * 微信支付异步回调地址,通知url必须为直接可访问的url,不能携带参数 + */ + private String notifyUrl; + + /** + * 微信支付分授权回调地址 + */ + private String payScorePermissionNotifyUrl; + + /** + * 公钥ID + */ + private String publicKeyId; + + /** + * pub_key.pem证书文件的绝对路径或者以classpath:开头的类路径. + */ + private String publicKeyPath; + + /** + * 自定义API主机地址,用于替换默认的 https://api.mch.weixin.qq.com + * 例如:http://proxy.company.com:8080 + */ + private String apiHostUrl; + + /** + * 是否将全部v3接口的请求都添加Wechatpay-Serial请求头,默认不添加 + */ + private boolean strictlyNeedWechatPaySerial = false; + + /** + * 是否完全使用公钥模式(用以微信从平台证书到公钥的灰度切换),默认不使用 + */ + private boolean fullPublicKeyModel = false; + } diff --git a/solon-plugins/wx-java-qidian-solon-plugin/pom.xml b/solon-plugins/wx-java-qidian-solon-plugin/pom.xml index b5488655ec..f1c7c2f26b 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.0 + 4.7.9.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 75f2d94865..fda5172752 100644 --- a/spring-boot-starters/pom.xml +++ b/spring-boot-starters/pom.xml @@ -6,7 +6,7 @@ com.github.binarywang wx-java - 4.7.0 + 4.7.9.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 b67cc1733e..e0a53f8313 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.0 + 4.7.9.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 4a4567198c..63b74d4763 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.0 + 4.7.9.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 0128c7bf52..b1806a3476 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.0 + 4.7.9.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 59c2f63f8a..5b8419bf23 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.0 + 4.7.9.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..097fc7e07a --- /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.9.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 c90f2b741d..e265218a37 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.0 + 4.7.9.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 2eaa6f1c77..a6f0fc2a38 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.0 + 4.7.9.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 3ec7cf5163..1ded1a4e16 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.0 + 4.7.9.B 4.0.0 @@ -44,6 +44,11 @@ okhttp provided + + org.apache.httpcomponents.client5 + httpclient5 + provided +
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 4bc7037c22..2b83d966ac 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.0 + 4.7.9.B 4.0.0 @@ -39,6 +39,11 @@ okhttp provided + + org.apache.httpcomponents.client5 + httpclient5 + provided + diff --git a/spring-boot-starters/wx-java-mp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/config/WxMpServiceAutoConfiguration.java b/spring-boot-starters/wx-java-mp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/config/WxMpServiceAutoConfiguration.java index 3b8733c286..dc6dcafb82 100644 --- a/spring-boot-starters/wx-java-mp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/config/WxMpServiceAutoConfiguration.java +++ b/spring-boot-starters/wx-java-mp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/config/WxMpServiceAutoConfiguration.java @@ -4,6 +4,7 @@ import com.binarywang.spring.starter.wxjava.mp.properties.WxMpProperties; import me.chanjar.weixin.mp.api.WxMpService; import me.chanjar.weixin.mp.api.impl.WxMpServiceHttpClientImpl; +import me.chanjar.weixin.mp.api.impl.WxMpServiceHttpComponentsImpl; import me.chanjar.weixin.mp.api.impl.WxMpServiceImpl; import me.chanjar.weixin.mp.api.impl.WxMpServiceJoddHttpImpl; import me.chanjar.weixin.mp.api.impl.WxMpServiceOkHttpImpl; @@ -35,6 +36,9 @@ public WxMpService wxMpService(WxMpConfigStorage configStorage, WxMpProperties w case HttpClient: wxMpService = newWxMpServiceHttpClientImpl(); break; + case HttpComponents: + wxMpService = newWxMpServiceHttpComponentsImpl(); + break; default: wxMpService = newWxMpServiceImpl(); break; @@ -60,4 +64,8 @@ private WxMpService newWxMpServiceJoddHttpImpl() { return new WxMpServiceJoddHttpImpl(); } + private WxMpService newWxMpServiceHttpComponentsImpl() { + return new WxMpServiceHttpComponentsImpl(); + } + } diff --git a/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/enums/HttpClientType.java b/spring-boot-starters/wx-java-mp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/enums/HttpClientType.java index f67ef97c2e..0bf034417f 100644 --- a/spring-boot-starters/wx-java-mp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/enums/HttpClientType.java +++ b/spring-boot-starters/wx-java-mp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/enums/HttpClientType.java @@ -19,4 +19,8 @@ public enum HttpClientType { * JoddHttp. */ JoddHttp, + /** + * HttpComponents (Apache HttpClient 5.x). + */ + HttpComponents, } 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 cd6f25e892..8afd1b83a9 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.0 + 4.7.9.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 960556dad7..ff1d6b84b1 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.0 + 4.7.9.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 9a9672de1a..5a794de7e8 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,13 +50,20 @@ 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())); + payConfig.setStrictlyNeedWechatPaySerial(this.properties.isStrictlyNeedWechatPaySerial()); + payConfig.setFullPublicKeyModel(this.properties.isFullPublicKeyModel()); 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 232bd33c47..8212e3b013 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 */ @@ -73,11 +83,37 @@ public class WxPayProperties { * apiv3 商户apiclient_cert.pem */ private String privateCertPath; - + + /** + * 公钥ID + */ + private String publicKeyId; + + /** + * pub_key.pem证书文件的绝对路径或者以classpath:开头的类路径. + */ + private String publicKeyPath; + /** * 微信支付是否使用仿真测试环境. * 默认不使用 */ private boolean useSandboxEnv; + /** + * 自定义API主机地址,用于替换默认的 https://api.mch.weixin.qq.com + * 例如:http://proxy.company.com:8080 + */ + private String apiHostUrl; + + /** + * 是否将全部v3接口的请求都添加Wechatpay-Serial请求头,默认不添加 + */ + private boolean strictlyNeedWechatPaySerial = false; + + /** + * 是否完全使用公钥模式(用以微信从平台证书到公钥的灰度切换),默认不使用 + */ + private boolean fullPublicKeyModel = false; + } diff --git a/spring-boot-starters/wx-java-qidian-spring-boot-starter/pom.xml b/spring-boot-starters/wx-java-qidian-spring-boot-starter/pom.xml index 5fe49991e1..2257f8253e 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.0 + 4.7.9.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 44c7c952fe..8e91201f38 100644 --- a/weixin-graal/pom.xml +++ b/weixin-graal/pom.xml @@ -6,7 +6,7 @@ com.github.binarywang wx-java - 4.7.0 + 4.7.9.B weixin-graal diff --git a/weixin-java-channel/pom.xml b/weixin-java-channel/pom.xml index 0cb27642ab..b18759d728 100644 --- a/weixin-java-channel/pom.xml +++ b/weixin-java-channel/pom.xml @@ -6,7 +6,7 @@ com.github.binarywang wx-java - 4.7.0 + 4.7.9.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..85c945d428 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 @@ -2,10 +2,8 @@ import java.util.List; -import me.chanjar.weixin.channel.bean.after.AfterSaleInfoResponse; -import me.chanjar.weixin.channel.bean.after.AfterSaleListResponse; -import me.chanjar.weixin.channel.bean.after.AfterSaleReasonResponse; -import me.chanjar.weixin.channel.bean.after.AfterSaleRejectReasonResponse; + +import me.chanjar.weixin.channel.bean.after.*; import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; import me.chanjar.weixin.channel.bean.complaint.ComplaintOrderResponse; import me.chanjar.weixin.common.error.WxErrorException; @@ -26,10 +24,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; + /** * 获取售后单详情 * @@ -136,4 +146,41 @@ WxChannelBaseResponse addComplaintEvidence(String complaintId, String content, L * @throws WxErrorException 异常 */ AfterSaleRejectReasonResponse getRejectReason() throws WxErrorException; + + /** + * 换货发货 + * 文档地址:https://developers.weixin.qq.com/doc/store/shop/API/channels-shop-aftersale/api_acceptexchangereship.html + * + * @param afterSaleOrderId 售后单号 + * @param waybillId 快递单号 + * @param deliveryId 快递公司id + * @return BaseResponse + * + * @throws WxErrorException 异常 + */ + WxChannelBaseResponse acceptExchangeReship(String afterSaleOrderId, String waybillId, String deliveryId) throws WxErrorException; + + /** + * 换货拒绝发货 + * 文档地址:https://developers.weixin.qq.com/doc/store/shop/API/channels-shop-aftersale/api_rejectexchangereship.html + * + * @param afterSaleOrderId 售后单号 + * @param rejectReason 拒绝原因具体描述 ,可使用默认描述,也可以自定义描述 + * @param rejectReasonType 拒绝原因枚举值 + * @param rejectCertificates 退款凭证,可使用图片上传接口获取media_id(数据类型填0) + * @return BaseResponse + * + * @throws WxErrorException 异常 + */ + WxChannelBaseResponse rejectExchangeReship(String afterSaleOrderId, String rejectReason, Integer rejectReasonType, List rejectCertificates) throws WxErrorException; + + /** + * 商家协商 + * 文档地址:https://developers.weixin.qq.com/doc/store/shop/API/channels-shop-aftersale/api_merchantupdateaftersale.html + * @param param 参数 + * @return BaseResponse + * + * @throws WxErrorException 异常 + */ + WxChannelBaseResponse merchantUpdateAfterSale(AfterSaleMerchantUpdateParam 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..92f865444b 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 @@ -22,17 +22,25 @@ @Slf4j 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); } @@ -101,4 +109,25 @@ public AfterSaleRejectReasonResponse getRejectReason() throws WxErrorException { String resJson = shopService.post(AFTER_SALE_REJECT_REASON_GET_URL, "{}"); return ResponseUtils.decode(resJson, AfterSaleRejectReasonResponse.class); } + + @Override + public WxChannelBaseResponse acceptExchangeReship(String afterSaleOrderId, String waybillId, String deliveryId) throws WxErrorException { + AfterSaleAcceptExchangeReshipParam param = new AfterSaleAcceptExchangeReshipParam(afterSaleOrderId, waybillId, deliveryId); + String resJson = shopService.post(AFTER_SALE_ACCEPT_EXCHANGE_RESHIP_URL, param); + return ResponseUtils.decode(resJson, WxChannelBaseResponse.class); + } + + @Override + public WxChannelBaseResponse rejectExchangeReship(String afterSaleOrderId, String rejectReason, Integer rejectReasonType, List rejectCertificates) throws WxErrorException { + AfterSaleRejectExchangeReshipParam param = new AfterSaleRejectExchangeReshipParam(afterSaleOrderId, rejectReason, rejectReasonType, rejectCertificates); + String resJson = shopService.post(AFTER_SALE_REJECT_EXCHANGE_RESHIP_URL, param); + return ResponseUtils.decode(resJson, WxChannelBaseResponse.class); + } + + @Override + public WxChannelBaseResponse merchantUpdateAfterSale(AfterSaleMerchantUpdateParam param) throws WxErrorException { + String resJson = shopService.post(AFTER_SALE_MERCHANT_UPDATE_URL, param); + return ResponseUtils.decode(resJson, WxChannelBaseResponse.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..f7b0ccc65b 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; @@ -41,11 +41,11 @@ public void initHttp() { this.httpProxy = OkHttpProxyInfo.httpProxy(this.config.getHttpProxyHost(), this.config.getHttpProxyPort(), this.config.getHttpProxyUsername(), this.config.getHttpProxyPassword()); okhttp3.OkHttpClient.Builder clientBuilder = new okhttp3.OkHttpClient.Builder(); clientBuilder.proxy(this.getRequestHttpProxy().getProxy()); - clientBuilder.authenticator(new Authenticator() { + clientBuilder.proxyAuthenticator(new Authenticator() { @Override public Request authenticate(Route route, Response response) throws IOException { String credential = Credentials.basic(WxChannelServiceOkHttpImpl.this.httpProxy.getProxyUsername(), WxChannelServiceOkHttpImpl.this.httpProxy.getProxyPassword()); - return response.request().newBuilder().header("Authorization", credential).build(); + return response.request().newBuilder().header("Proxy-Authorization", credential).build(); } }); this.httpClient = clientBuilder.build(); @@ -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/AfterSaleAcceptExchangeReshipParam.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/after/AfterSaleAcceptExchangeReshipParam.java new file mode 100644 index 0000000000..66be1c715d --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/after/AfterSaleAcceptExchangeReshipParam.java @@ -0,0 +1,35 @@ +package me.chanjar.weixin.channel.bean.after; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +/** + * 售后单换货发货信息 + * + * @author Chu + */ +@Data +@JsonInclude(JsonInclude.Include.NON_NULL) +public class AfterSaleAcceptExchangeReshipParam extends AfterSaleIdParam { + private static final long serialVersionUID = -7946679037747710613L; + + /** 快递单号*/ + @JsonProperty("waybill_id") + private String waybillId; + + /** 快递公司id,通过获取快递公司列表接口获得,非主流快递公司可以填OTHER*/ + @JsonProperty("delivery_id") + private String deliveryId; + + public AfterSaleAcceptExchangeReshipParam() { + + } + + public AfterSaleAcceptExchangeReshipParam(String afterSaleOrderId, String waybillId, String deliveryId) { + super(afterSaleOrderId); + this.waybillId = waybillId; + this.deliveryId = deliveryId; + } + +} 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..a73d6ae310 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/after/AfterSaleExchangeProductInfo.java @@ -0,0 +1,42 @@ +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; + + /** 旧商品价格 */ + @JsonProperty("old_sku_price") + private Integer oldSkuPrice; + + /** 新商品价格 */ + @JsonProperty("new_sku_price") + private Integer newSkuPrice; +} 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/AfterSaleMerchantUpdateParam.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/after/AfterSaleMerchantUpdateParam.java new file mode 100644 index 0000000000..275577e1df --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/after/AfterSaleMerchantUpdateParam.java @@ -0,0 +1,57 @@ +package me.chanjar.weixin.channel.bean.after; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import java.util.List; + +/** + * 售后单商家协商信息 + * + * @author Chu + */ +@Data +@JsonInclude(JsonInclude.Include.NON_NULL) +public class AfterSaleMerchantUpdateParam extends AfterSaleIdParam { + private static final long serialVersionUID = -3672834150982780L; + + /** 协商修改把售后单修改成该售后类型。1:退款;2:退货退款*/ + @JsonProperty("type") + private Integer type; + + /** 金额(单位:分)*/ + @JsonProperty("amount") + private Integer amount; + + /** 协商描述*/ + @JsonProperty("merchant_update_desc") + private String merchantUpdateDesc; + + /** 协商原因*/ + @JsonProperty("update_reason_type") + private Integer updateReasonType; + + /** 1:已协商一致,邀请买家取消售后; 2:邀请买家核实与补充凭证; 3:修改买家售后申请*/ + @JsonProperty("merchant_update_type") + private Integer merchantUpdateType; + + /** 协商凭证id列表,可使用图片上传接口获取media_id(数据类型填0),当update_reason_type对应的need_image为1时必填*/ + @JsonProperty("media_ids") + private List mediaIds; + + public AfterSaleMerchantUpdateParam() { + } + + public AfterSaleMerchantUpdateParam(String afterSaleOrderId, Integer type, Integer updateReasonType, Integer merchantUpdateType + , Integer amount, String merchantUpdateDesc, List mediaIds) { + super(afterSaleOrderId); + this.type = type; + this.updateReasonType = updateReasonType; + this.merchantUpdateType = merchantUpdateType; + this.amount = amount; + this.merchantUpdateDesc = merchantUpdateDesc; + this.mediaIds = mediaIds; + } + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/after/AfterSaleRejectExchangeReshipParam.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/after/AfterSaleRejectExchangeReshipParam.java new file mode 100644 index 0000000000..668ffa11e9 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/after/AfterSaleRejectExchangeReshipParam.java @@ -0,0 +1,41 @@ +package me.chanjar.weixin.channel.bean.after; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import java.util.List; + +/** + * 售后单换货拒绝发货信息 + * + * @author Chu + */ +@Data +@JsonInclude(JsonInclude.Include.NON_NULL) +public class AfterSaleRejectExchangeReshipParam extends AfterSaleIdParam { + private static final long serialVersionUID = -7946679037747710613L; + + /** 拒绝原因具体描述 ,可使用默认描述,也可以自定义描述*/ + @JsonProperty("reject_reason") + private String rejectReason; + + /** 拒绝原因枚举 */ + @JsonProperty("reject_reason_type") + private Integer rejectReasonType; + + /** 退款凭证,可使用图片上传接口获取media_id(数据类型填0)*/ + @JsonProperty("reject_certificates") + private List rejectCertificates; + + public AfterSaleRejectExchangeReshipParam() { + } + + public AfterSaleRejectExchangeReshipParam(String afterSaleOrderId, String rejectReason, Integer rejectReasonType, List rejectCertificates) { + super(afterSaleOrderId); + this.rejectReason = rejectReason; + this.rejectReasonType = rejectReasonType; + this.rejectCertificates = rejectCertificates; + } + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/after/AfterSaleRejectReason.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/after/AfterSaleRejectReason.java index 51c88ae222..7987153ec0 100644 --- a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/after/AfterSaleRejectReason.java +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/after/AfterSaleRejectReason.java @@ -36,4 +36,9 @@ public class AfterSaleRejectReason implements Serializable { @JsonProperty("reject_reason") private String rejectReason; + /** + * 售后拒绝原因适用场景 + */ + @JsonProperty("reject_scene") + private Integer rejectScene; } 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/complaint/ComplaintHistory.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/complaint/ComplaintHistory.java index 4570fdc615..84a558b2b1 100644 --- a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/complaint/ComplaintHistory.java +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/complaint/ComplaintHistory.java @@ -26,7 +26,7 @@ public class ComplaintHistory implements Serializable { /** 用户联系电话 */ @JsonProperty("phone_number") - private Integer phoneNumber; + private String phoneNumber; /** 相关文本内容 */ @JsonProperty("content") 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/ChangeSkuInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/ChangeSkuInfo.java new file mode 100644 index 0000000000..b40a497755 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/ChangeSkuInfo.java @@ -0,0 +1,42 @@ +package me.chanjar.weixin.channel.bean.order; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * 更换sku信息 + */ +@Data +@NoArgsConstructor +public class ChangeSkuInfo implements Serializable { + + private static final long serialVersionUID = 8783442929429377519L; + + /** + * 发货前更换sku状态。3:等待商家处理,4:商家审核通过,5:商家拒绝,6:用户主动取消,7:超时默认拒绝 + */ + @JsonProperty("preshipment_change_sku_state") + private Integer preshipmentChangeSkuState; + + /** + * 原sku_id + */ + @JsonProperty("old_sku_id") + private String oldSkuId; + + /** + * 用户申请更换的sku_id + */ + @JsonProperty("new_sku_id") + private String newSkuId; + + /** + * 商家处理请求的最后时间,只有当前换款请求处于"等待商家处理"才有值 + */ + @JsonProperty("ddl_time_stamp") + private Integer deadlineTimeStamp; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/DropshipInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/DropshipInfo.java new file mode 100644 index 0000000000..9c5340376d --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/DropshipInfo.java @@ -0,0 +1,24 @@ +package me.chanjar.weixin.channel.bean.order; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * 代发相关信息 + */ +@Data +@NoArgsConstructor +public class DropshipInfo implements Serializable { + + private static final long serialVersionUID = -4562618835611282016L; + + /** + * 代发单号 + */ + @JsonProperty("ds_order_id") + private Long dsOrderId; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/FreeGiftInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/FreeGiftInfo.java new file mode 100644 index 0000000000..b2612cfccd --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/FreeGiftInfo.java @@ -0,0 +1,25 @@ +package me.chanjar.weixin.channel.bean.order; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; +import java.util.List; + +/** + * 赠品信息 + */ +@Data +@NoArgsConstructor +public class FreeGiftInfo implements Serializable { + + private static final long serialVersionUID = 2024061212345678901L; + + /** + * 赠品对应的主品信息 + */ + @JsonProperty("main_product_list") + private List mainProductList; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/MainProductInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/MainProductInfo.java new file mode 100644 index 0000000000..bb13c0b0b7 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/MainProductInfo.java @@ -0,0 +1,42 @@ +package me.chanjar.weixin.channel.bean.order; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * 赠品对应的主品信息 + */ +@Data +@NoArgsConstructor +public class MainProductInfo implements Serializable { + + private static final long serialVersionUID = 2024061212345678901L; + + /** + * 赠品数量 + */ + @JsonProperty("gift_cnt") + private Integer giftCnt; + + /** + * 活动id + */ + @JsonProperty("task_id") + private Integer taskId; + + /** + * 商品id + */ + @JsonProperty("product_id") + private Integer productId; + + /** + * 主品sku_id + */ + @JsonProperty("sku_id") + private Integer skuId; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/OrderCommissionInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/OrderCommissionInfo.java index 78e391e774..f3cab1f4bf 100644 --- a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/OrderCommissionInfo.java +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/OrderCommissionInfo.java @@ -42,4 +42,8 @@ public class OrderCommissionInfo implements Serializable { /** 达人openfinderid */ @JsonProperty("openfinderid") private String openFinderId; + + /** 新带货平台 id */ + @JsonProperty("talent_id") + private String talentId; } diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/OrderCouponInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/OrderCouponInfo.java index a8f020c0ef..34f2d670d0 100644 --- a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/OrderCouponInfo.java +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/OrderCouponInfo.java @@ -18,4 +18,24 @@ public class OrderCouponInfo implements Serializable { /** 用户优惠券id */ @JsonProperty("user_coupon_id") private String userCouponId; + + /** + * 优惠券类型 + * 1 商家优惠 + * 2 达人优惠 + * 3 平台优惠 + * 4 国家补贴 + * 5 地方补贴 + */ + @JsonProperty("coupon_type") + private Integer couponType; + + /** 优惠金额,单位为分,该张优惠券、抵扣该商品的金额 */ + @JsonProperty("discounted_price") + private Integer discountedPrice; + + /** 优惠券id */ + @JsonProperty("coupon_id") + private String couponId; + } 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/OrderPayInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/OrderPayInfo.java index 6c912f7c45..7a9f367d76 100644 --- a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/OrderPayInfo.java +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/OrderPayInfo.java @@ -16,12 +16,8 @@ public class OrderPayInfo implements Serializable { private static final long serialVersionUID = -5085386252699113948L; /** 预支付id */ - @JsonProperty("prepayId") - private String prepayId; - - /** 预支付时间,秒级时间戳 */ - @JsonProperty("prepay_time") - private Long prepayTime; + @JsonProperty("payment_method") + private Integer paymentMethod; /** 支付时间,秒级时间戳 */ @JsonProperty("pay_time") diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/OrderPriceInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/OrderPriceInfo.java index cad435df2b..50eac04e50 100644 --- a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/OrderPriceInfo.java +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/OrderPriceInfo.java @@ -107,4 +107,34 @@ public class OrderPriceInfo implements Serializable { @JsonProperty("finder_discounted_price") private Integer finderDiscountedPrice; + /** + * 订单维度会员权益优惠金额 + */ + @JsonProperty("vip_discounted_price") + private Integer vipDiscountedPrice; + + /** + * 订单维度一起买优惠金额,单位为分 + */ + @JsonProperty("bulkbuy_discounted_price") + private Integer bulkbuyDiscountedPrice; + + /** + * 订单维度国补优惠金额 + */ + @JsonProperty("national_subsidy_discounted_price") + private Integer nationalSubsidyDiscountedPrice; + + /** + * 订单维度平台券优惠金额,单位为分 + */ + @JsonProperty("cash_coupon_discounted_price") + private Integer cashCouponDiscountedPrice; + + /** + * 订单维度地方补贴优惠金额(商家出资),单位为分 + */ + @JsonProperty("national_subsidy_merchant_discounted_price") + private Integer nationalSubsidyMerchantDiscountedPrice; + } diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/OrderProductInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/OrderProductInfo.java index 1e49455dc4..e5c37e3cba 100644 --- a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/OrderProductInfo.java +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/OrderProductInfo.java @@ -176,7 +176,7 @@ public class OrderProductInfo implements Serializable { private Integer merchantDiscountedPrice; /** - * 商家优惠金额,单位为分 + * 达人优惠金额,单位为分 */ @JsonProperty("finder_discounted_price") private Integer finderDiscountedPrice; @@ -186,4 +186,71 @@ public class OrderProductInfo implements Serializable { */ @JsonProperty("is_free_gift") private Boolean freeGift; + + /** + * 订单内商品维度会员权益优惠金额,单位为分 + */ + @JsonProperty("vip_discounted_price") + private Integer vipDiscountedPrice; + + /** + * 商品常量编号,订单内商品唯一标识,下单后不会发生变化 + */ + @JsonProperty("product_unique_id") + private String productUniqueId; + + /** + * 更换sku信息 + */ + @JsonProperty("change_sku_info") + private ChangeSkuInfo changeSkuInfo; + + /** + * 赠品信息 + */ + @JsonProperty("free_gift_info") + private FreeGiftInfo freeGiftInfo; + + /** + * 订单内商品维度一起买优惠金额,单位为分 + */ + @JsonProperty("bulkbuy_discounted_price") + private Integer bulkbuyDiscountedPrice; + + /** + * 订单内商品维度国补优惠金额,单位为分 + */ + @JsonProperty("national_subsidy_discounted_price") + private Integer nationalSubsidyDiscountedPrice; + + /** + * 代发相关信息 + */ + @JsonProperty("dropship_info") + private DropshipInfo dropshipInfo; + + /** + * 是否闪购商品 + */ + @JsonProperty("is_flash_sale") + private Boolean flashSale; + + /** + * 订单内商品维度地方补贴优惠金额(商家出资),单位为分 + */ + @JsonProperty("national_subsidy_merchant_discounted_price") + private Integer nationalSubsidyMerchantDiscountedPrice; + + /** + * 订单内商品维度活动商家补贴,即参与平台补贴活动时商家通过活动报名价优惠的部分,单位为分 + */ + @JsonProperty("platform_activity_merchant_discounted_price") + private Integer platformActivityMerchantDiscountedPrice; + + /** + * 订单内商品维度平台券优惠金额,单位为分 + */ + @JsonProperty("cash_coupon_discounted_price") + private Integer cashCouponDiscountedPrice; + } 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/bean/product/SpuInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/SpuInfo.java index a160a31373..155148c178 100644 --- a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/SpuInfo.java +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/SpuInfo.java @@ -139,4 +139,16 @@ public class SpuInfo extends SpuSimpleInfo { /** 尺码表信息 */ @JsonProperty("size_chart") private SpuSizeChart sizeChart; + + /** 短标题 */ + @JsonProperty("short_title") + private String shortTitle; + + /** 销量 */ + @JsonProperty("total_sold_num") + private Integer totalSoldNum; + + /** 发布模式,0: 普通模式;1: 极简模式 */ + @JsonProperty("release_mode") + private Integer releaseMode; } 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..2d9aa84f84 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 @@ -232,6 +232,12 @@ public interface AfterSale { String AFTER_SALE_REASON_GET_URL = "https://api.weixin.qq.com/channels/ec/aftersale/reason/get"; /** 获取拒绝售后原因*/ String AFTER_SALE_REJECT_REASON_GET_URL = "https://api.weixin.qq.com/channels/ec/aftersale/rejectreason/get"; + /** 换货发货*/ + String AFTER_SALE_ACCEPT_EXCHANGE_RESHIP_URL = "https://api.weixin.qq.com/channels/ec/aftersale/acceptexchangereship"; + /** 换货拒绝发货*/ + String AFTER_SALE_REJECT_EXCHANGE_RESHIP_URL = "https://api.weixin.qq.com/channels/ec/aftersale/rejectexchangereship"; + /** 商家协商*/ + String AFTER_SALE_MERCHANT_UPDATE_URL = "https://api.weixin.qq.com/channels/ec/aftersale/merchantupdateaftersale"; } /** 纠纷相关接口 */ @@ -247,8 +253,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 b775fab23b..d3496f923a 100644 --- a/weixin-java-common/pom.xml +++ b/weixin-java-common/pom.xml @@ -6,7 +6,7 @@ com.github.binarywang wx-java - 4.7.0 + 4.7.9.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..70c4e47933 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"; - + /** * 小程序审核事件:审核延后 */ @@ -464,32 +465,40 @@ public static class EventType { /** * 名称审核事件 */ - public static final String WXA_NICKNAME_AUDIT = "wxa_nickname_audit" ; + public static final String WXA_NICKNAME_AUDIT = "wxa_nickname_audit"; /** - *小程序违规记录事件 - */ - public static final String WXA_ILLEGAL_RECORD= "wxa_illegal_record"; + * 小程序违规记录事件 + */ + public static final String WXA_ILLEGAL_RECORD = "wxa_illegal_record"; /** - *小程序申诉记录推送 - */ - public static final String WXA_APPEAL_RECORD= "wxa_appeal_record"; + * 小程序申诉记录推送 + */ + public static final String WXA_APPEAL_RECORD = "wxa_appeal_record"; /** * 隐私权限审核结果推送 */ - public static final String WXA_PRIVACY_APPLY= "wxa_privacy_apply"; + public static final String WXA_PRIVACY_APPLY = "wxa_privacy_apply"; /** * 类目审核结果事件推送 */ - public static final String WXA_CATEGORY_AUDIT= "wxa_category_audit"; + public static final String WXA_CATEGORY_AUDIT = "wxa_category_audit"; /** * 小程序微信认证支付成功事件 */ - public static final String WX_VERIFY_PAY_SUCC= "wx_verify_pay_succ"; + public static final String WX_VERIFY_PAY_SUCC = "wx_verify_pay_succ"; /** * 小程序微信认证派单事件 */ - public static final String WX_VERIFY_DISPATCH= "wx_verify_dispatch"; - } + public static final String WX_VERIFY_DISPATCH = "wx_verify_dispatch"; + /** + * 提醒需要上传发货信息事件:曾经发过货的小程序,订单超过48小时未发货时 + */ + public static final String TRADE_MANAGE_REMIND_SHIPPING = "trade_manage_remind_shipping"; + /** + * 订单完成发货时、订单结算时 + */ + public static final String TRADE_MANAGE_ORDER_SETTLEMENT = "trade_manage_order_settlement"; + } /** * 上传多媒体(临时素材)文件的类型. @@ -622,4 +631,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/RandomUtils.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/RandomUtils.java index bbb11992bc..a9017c0d16 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/RandomUtils.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/RandomUtils.java @@ -4,12 +4,24 @@ public class RandomUtils { private static final String RANDOM_STR = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; - private static final java.util.Random RANDOM = new java.util.Random(); + private static volatile java.util.Random random; + + private static java.util.Random getRandom() { + if (random == null) { + synchronized (RandomUtils.class) { + if (random == null) { + random = new java.util.Random(); + } + } + } + return random; + } public static String getRandomStr() { StringBuilder sb = new StringBuilder(); + java.util.Random r = getRandom(); for (int i = 0; i < 16; i++) { - sb.append(RANDOM_STR.charAt(RANDOM.nextInt(RANDOM_STR.length()))); + sb.append(RANDOM_STR.charAt(r.nextInt(RANDOM_STR.length()))); } return sb.toString(); } diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/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..0b0590b1e6 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; @@ -38,6 +37,19 @@ public class WxCryptUtil { private static final Base64 BASE64 = new Base64(); private static final Charset CHARSET = StandardCharsets.UTF_8; + private static volatile Random random; + + private static Random getRandom() { + if (random == null) { + synchronized (WxCryptUtil.class) { + if (random == null) { + random = new Random(); + } + } + } + return random; + } + private static final ThreadLocal BUILDER_LOCAL = ThreadLocal.withInitial(() -> { try { final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); @@ -110,10 +122,10 @@ private static int bytesNetworkOrder2Number(byte[] bytesInNetworkOrder) { */ private static String genRandomStr() { String base = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; - Random random = new Random(); + Random r = getRandom(); StringBuilder sb = new StringBuilder(); for (int i = 0; i < 16; i++) { - int number = random.nextInt(base.length()); + int number = r.nextInt(base.length()); sb.append(base.charAt(number)); } return sb.toString(); @@ -321,14 +333,28 @@ public String decrypt(String cipherText) { byte[] bytes = PKCS7Encoder.decode(original); // 分离16位随机字符串,网络字节序和AppId + if (bytes == null || bytes.length < 20) { + throw new WxRuntimeException("解密后数据长度异常,可能为错误的密文或EncodingAESKey"); + } byte[] networkOrder = Arrays.copyOfRange(bytes, 16, 20); int xmlLength = bytesNetworkOrder2Number(networkOrder); - xmlContent = new String(Arrays.copyOfRange(bytes, 20, 20 + xmlLength), CHARSET); - fromAppid = new String(Arrays.copyOfRange(bytes, 20 + xmlLength, bytes.length), CHARSET); + // 长度边界校验,避免非法长度导致的越界/参数异常 + int startIndex = 20; + int endIndex = startIndex + xmlLength; + if (xmlLength < 0 || endIndex > bytes.length) { + throw new WxRuntimeException("解密后数据格式非法:消息长度不正确,可能为错误的密文或EncodingAESKey"); + } + + xmlContent = new String(Arrays.copyOfRange(bytes, startIndex, endIndex), CHARSET); + fromAppid = new String(Arrays.copyOfRange(bytes, endIndex, bytes.length), CHARSET); } catch (Exception e) { - throw new WxRuntimeException(e); + if (e instanceof WxRuntimeException) { + throw (WxRuntimeException) e; + } else { + throw new WxRuntimeException(e); + } } // appid不相同的情况 暂时忽略这段判断 diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/BaseMediaDownloadRequestExecutor.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/BaseMediaDownloadRequestExecutor.java index 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..caa07d0eaf 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/json/GsonParser.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/json/GsonParser.java @@ -10,17 +10,16 @@ * @author niefy */ public class GsonParser { - private static final JsonParser JSON_PARSER = new JsonParser(); public static JsonObject parse(String json) { - return JSON_PARSER.parse(json).getAsJsonObject(); + return new JsonParser().parse(json).getAsJsonObject(); } public static JsonObject parse(Reader json) { - return JSON_PARSER.parse(json).getAsJsonObject(); + return new JsonParser().parse(json).getAsJsonObject(); } public static JsonObject parse(JsonReader json) { - return JSON_PARSER.parse(json).getAsJsonObject(); + return new JsonParser().parse(json).getAsJsonObject(); } } diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/json/WxGsonBuilder.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/json/WxGsonBuilder.java index ff260c16fb..6ea269f7e4 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/json/WxGsonBuilder.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/json/WxGsonBuilder.java @@ -1,5 +1,7 @@ package me.chanjar.weixin.common.util.json; +import com.google.gson.ExclusionStrategy; +import com.google.gson.FieldAttributes; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import me.chanjar.weixin.common.bean.WxAccessToken; @@ -7,6 +9,9 @@ import me.chanjar.weixin.common.bean.menu.WxMenu; import me.chanjar.weixin.common.error.WxError; import me.chanjar.weixin.common.bean.result.WxMediaUploadResult; +import me.chanjar.weixin.common.util.http.apache.ApacheHttpClientBuilder; + +import java.io.File; import java.util.Objects; /** @@ -25,6 +30,17 @@ public class WxGsonBuilder { INSTANCE.registerTypeAdapter(WxMediaUploadResult.class, new WxMediaUploadResultAdapter()); INSTANCE.registerTypeAdapter(WxNetCheckResult.class, new WxNetCheckResultGsonAdapter()); + INSTANCE.setExclusionStrategies(new ExclusionStrategy() { + @Override + public boolean shouldSkipField(FieldAttributes fieldAttributes) { + return false; + } + + @Override + public boolean shouldSkipClass(Class aClass) { + return aClass == File.class || aClass == ApacheHttpClientBuilder.class; + } + }); } public static Gson create() { 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/main/resources/META-INF/native-image/com.github.binarywang/weixin-java-common/native-image.properties b/weixin-java-common/src/main/resources/META-INF/native-image/com.github.binarywang/weixin-java-common/native-image.properties new file mode 100644 index 0000000000..e1e601713f --- /dev/null +++ b/weixin-java-common/src/main/resources/META-INF/native-image/com.github.binarywang/weixin-java-common/native-image.properties @@ -0,0 +1,4 @@ +Args = --initialize-at-run-time=org.apache.http.impl.auth.NTLMEngineImpl \ + --initialize-at-run-time=org.apache.http.impl.auth.NTLMEngine \ + --initialize-at-run-time=org.apache.http.impl.auth.KerberosScheme \ + --initialize-at-run-time=org.apache.http.impl.auth.SPNegoScheme diff --git a/weixin-java-common/src/main/resources/META-INF/native-image/com.github.binarywang/weixin-java-common/reflect-config.json b/weixin-java-common/src/main/resources/META-INF/native-image/com.github.binarywang/weixin-java-common/reflect-config.json new file mode 100644 index 0000000000..3bf76c8dab --- /dev/null +++ b/weixin-java-common/src/main/resources/META-INF/native-image/com.github.binarywang/weixin-java-common/reflect-config.json @@ -0,0 +1,14 @@ +[ + { + "name": "me.chanjar.weixin.common.util.RandomUtils", + "methods": [ + {"name": "getRandomStr", "parameterTypes": []} + ] + }, + { + "name": "me.chanjar.weixin.common.util.crypto.WxCryptUtil", + "allDeclaredConstructors": true, + "allDeclaredMethods": true, + "allDeclaredFields": true + } +] 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/json/GsonParserTest.java b/weixin-java-common/src/test/java/me/chanjar/weixin/common/util/json/GsonParserTest.java new file mode 100644 index 0000000000..ea069d4155 --- /dev/null +++ b/weixin-java-common/src/test/java/me/chanjar/weixin/common/util/json/GsonParserTest.java @@ -0,0 +1,47 @@ +package me.chanjar.weixin.common.util.json; + +import com.google.gson.JsonObject; +import com.google.gson.stream.JsonReader; +import org.testng.annotations.Test; + +import java.io.StringReader; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertNotNull; + +/** + * GsonParser 测试类 + * + * @author Binary Wang + */ +public class GsonParserTest { + + @Test + public void testParseString() { + String json = "{\"code\":\"ALREADY_EXISTS\",\"message\":\"当前订单已关闭,可查询订单了解关闭原因\"}"; + JsonObject jsonObject = GsonParser.parse(json); + assertNotNull(jsonObject); + assertEquals(jsonObject.get("code").getAsString(), "ALREADY_EXISTS"); + assertEquals(jsonObject.get("message").getAsString(), "当前订单已关闭,可查询订单了解关闭原因"); + } + + @Test + public void testParseReader() { + String json = "{\"code\":\"SUCCESS\",\"message\":\"处理成功\"}"; + StringReader reader = new StringReader(json); + JsonObject jsonObject = GsonParser.parse(reader); + assertNotNull(jsonObject); + assertEquals(jsonObject.get("code").getAsString(), "SUCCESS"); + assertEquals(jsonObject.get("message").getAsString(), "处理成功"); + } + + @Test + public void testParseJsonReader() { + String json = "{\"errcode\":0,\"errmsg\":\"ok\"}"; + JsonReader jsonReader = new JsonReader(new StringReader(json)); + JsonObject jsonObject = GsonParser.parse(jsonReader); + assertNotNull(jsonObject); + assertEquals(jsonObject.get("errcode").getAsInt(), 0); + assertEquals(jsonObject.get("errmsg").getAsString(), "ok"); + } +} 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/APPROVAL_WORKFLOW_GUIDE.md b/weixin-java-cp/APPROVAL_WORKFLOW_GUIDE.md new file mode 100644 index 0000000000..d2c533b5e5 --- /dev/null +++ b/weixin-java-cp/APPROVAL_WORKFLOW_GUIDE.md @@ -0,0 +1,141 @@ +# WeChat Enterprise Workflow Approval Guide +# 企业微信流程审批功能使用指南 + +## Overview / 概述 + +WxJava SDK provides comprehensive support for WeChat Enterprise workflow approval (企业微信流程审批), including both traditional OA approval and the approval process engine. + +WxJava SDK 提供全面的企业微信流程审批支持,包括传统OA审批和审批流程引擎。 + +## Current Implementation Status / 当前实现状态 + +### ✅ Fully Implemented APIs / 已完整实现的API + +1. **Submit Approval Application / 提交审批申请** + - Endpoint: `/cgi-bin/oa/applyevent` + - Documentation: [91853](https://work.weixin.qq.com/api/doc/90000/90135/91853) + - Implementation: `WxCpOaService.apply(WxCpOaApplyEventRequest)` + +2. **Get Approval Details / 获取审批申请详情** + - Endpoint: `/cgi-bin/oa/getapprovaldetail` + - Implementation: `WxCpOaService.getApprovalDetail(String spNo)` + +3. **Batch Get Approval Numbers / 批量获取审批单号** + - Endpoint: `/cgi-bin/oa/getapprovalinfo` + - Implementation: `WxCpOaService.getApprovalInfo(...)` + +4. **Approval Process Engine / 审批流程引擎** + - Endpoint: `/cgi-bin/corp/getopenapprovaldata` + - Implementation: `WxCpOaAgentService.getOpenApprovalData(String thirdNo)` + +5. **Template Management / 模板管理** + - Create: `WxCpOaService.createOaApprovalTemplate(...)` + - Update: `WxCpOaService.updateOaApprovalTemplate(...)` + - Get Details: `WxCpOaService.getTemplateDetail(...)` + +## Usage Examples / 使用示例 + +### 1. Submit Approval Application / 提交审批申请 + +```java +// Create approval request +WxCpOaApplyEventRequest request = new WxCpOaApplyEventRequest() + .setCreatorUserId("userId") + .setTemplateId("templateId") + .setUseTemplateApprover(0) + .setApprovers(Arrays.asList( + new WxCpOaApplyEventRequest.Approver() + .setAttr(2) + .setUserIds(new String[]{"approver1", "approver2"}) + )) + .setNotifiers(new String[]{"notifier1", "notifier2"}) + .setNotifyType(1) + .setApplyData(new WxCpOaApplyEventRequest.ApplyData() + .setContents(Arrays.asList( + new ApplyDataContent() + .setControl("Text") + .setId("Text-1234567890") + .setValue(new ContentValue().setText("Approval content")) + )) + ); + +// Submit approval +String spNo = wxCpService.getOaService().apply(request); +``` + +### 2. Get Approval Details / 获取审批详情 + +```java +// Get approval details by approval number +WxCpApprovalDetailResult result = wxCpService.getOaService() + .getApprovalDetail("approval_number"); + +WxCpApprovalDetailResult.WxCpApprovalDetail detail = result.getInfo(); +System.out.println("Approval Status: " + detail.getSpStatus()); +System.out.println("Approval Name: " + detail.getSpName()); +``` + +### 3. Batch Get Approval Information / 批量获取审批信息 + +```java +// Get approval info with filters +Date startTime = new Date(System.currentTimeMillis() - 7 * 24 * 60 * 60 * 1000); +Date endTime = new Date(); + +WxCpApprovalInfo approvalInfo = wxCpService.getOaService() + .getApprovalInfo(startTime, endTime, "0", 100, null); + +List spNumbers = approvalInfo.getSpNoList(); +``` + +### 4. Third-Party Application Support / 第三方应用支持 + +```java +// For third-party applications +WxCpTpOAService tpOaService = wxCpTpService.getOaService(); + +// Submit approval for specific corp +String spNo = tpOaService.apply(request, "corpId"); + +// Get approval details for specific corp +WxCpApprovalDetailResult detail = tpOaService.getApprovalDetail("spNo", "corpId"); +``` + +## Multi-Account Configuration / 多账号配置支持 + +WxJava supports multi-account configurations for enterprise scenarios: + +```java +// Spring Boot configuration example +@Autowired +private WxCpMultiServices wxCpMultiServices; + +// Get service for specific corp +WxCpService wxCpService = wxCpMultiServices.getWxCpService("corpId"); +WxCpOaService oaService = wxCpService.getOaService(); +``` + +## Available Data Models / 可用数据模型 + +- `WxCpOaApplyEventRequest` - Approval application request +- `WxCpApprovalDetailResult` - Approval details response +- `WxCpApprovalInfo` - Batch approval information +- `WxCpXmlApprovalInfo` - XML approval message handling +- `WxCpOaApprovalTemplate` - Approval template management + +## Documentation References / 文档参考 + +- [Submit Approval Application (91853)](https://work.weixin.qq.com/api/doc/90000/90135/91853) +- [Get Approval Details (91983)](https://work.weixin.qq.com/api/doc/90000/90135/91983) +- [Batch Get Approval Numbers (91816)](https://work.weixin.qq.com/api/doc/90000/90135/91816) +- [Approval Process Engine (90269)](https://developer.work.weixin.qq.com/document/path/90269) + +## Conclusion / 结论 + +WxJava already provides comprehensive support for WeChat Enterprise workflow approval. The "new version" (新版) approval functionality referenced in issue requests is **already fully implemented** and available for use. + +WxJava 已经提供了企业微信流程审批的全面支持。问题中提到的"新版"流程审批功能**已经完全实现**并可使用。 + +For questions about specific usage, please refer to the test cases in `WxCpOaServiceImplTest` and the comprehensive API documentation. + +有关具体使用问题,请参考 `WxCpOaServiceImplTest` 中的测试用例和全面的API文档。 \ No newline at end of file diff --git a/weixin-java-cp/INTELLIGENT_ROBOT.md b/weixin-java-cp/INTELLIGENT_ROBOT.md new file mode 100644 index 0000000000..f2641bd6b4 --- /dev/null +++ b/weixin-java-cp/INTELLIGENT_ROBOT.md @@ -0,0 +1,107 @@ +# 企业微信智能机器人接口 + +本模块提供企业微信智能机器人相关的API接口实现。 + +## 官方文档 + +- [企业微信智能机器人接口](https://developer.work.weixin.qq.com/document/path/101039) + +## 接口说明 + +### 获取服务实例 + +```java +WxCpService wxCpService = ...; // 初始化企业微信服务 +WxCpIntelligentRobotService robotService = wxCpService.getIntelligentRobotService(); +``` + +### 创建智能机器人 + +```java +WxCpIntelligentRobotCreateRequest request = new WxCpIntelligentRobotCreateRequest(); +request.setName("我的智能机器人"); +request.setDescription("这是一个智能客服机器人"); +request.setAvatar("http://example.com/avatar.jpg"); + +WxCpIntelligentRobotCreateResponse response = robotService.createRobot(request); +String robotId = response.getRobotId(); +``` + +### 更新智能机器人 + +```java +WxCpIntelligentRobotUpdateRequest request = new WxCpIntelligentRobotUpdateRequest(); +request.setRobotId("robot_id_here"); +request.setName("更新后的机器人名称"); +request.setDescription("更新后的描述"); +request.setStatus(1); // 1:启用, 0:停用 + +robotService.updateRobot(request); +``` + +### 查询智能机器人 + +```java +String robotId = "robot_id_here"; +WxCpIntelligentRobot robot = robotService.getRobot(robotId); + +System.out.println("机器人名称: " + robot.getName()); +System.out.println("机器人状态: " + robot.getStatus()); +``` + +### 智能对话 + +```java +WxCpIntelligentRobotChatRequest request = new WxCpIntelligentRobotChatRequest(); +request.setRobotId("robot_id_here"); +request.setUserid("user123"); +request.setMessage("你好,请问如何使用这个功能?"); +request.setSessionId("session123"); // 可选,用于保持会话连续性 + +WxCpIntelligentRobotChatResponse response = robotService.chat(request); +String reply = response.getReply(); +String sessionId = response.getSessionId(); +``` + +### 重置会话 + +```java +String robotId = "robot_id_here"; +String userid = "user123"; +String sessionId = "session123"; + +robotService.resetSession(robotId, userid, sessionId); +``` + +### 删除智能机器人 + +```java +String robotId = "robot_id_here"; +robotService.deleteRobot(robotId); +``` + +## 主要类说明 + +### 请求类 + +- `WxCpIntelligentRobotCreateRequest`: 创建机器人请求 +- `WxCpIntelligentRobotUpdateRequest`: 更新机器人请求 +- `WxCpIntelligentRobotChatRequest`: 智能对话请求 + +### 响应类 + +- `WxCpIntelligentRobotCreateResponse`: 创建机器人响应 +- `WxCpIntelligentRobotChatResponse`: 智能对话响应 +- `WxCpIntelligentRobot`: 机器人信息实体 + +### 服务接口 + +- `WxCpIntelligentRobotService`: 智能机器人服务接口 +- `WxCpIntelligentRobotServiceImpl`: 智能机器人服务实现 + +## 注意事项 + +1. 需要确保企业微信应用具有智能机器人相关权限 +2. 智能机器人功能可能需要特定的企业微信版本支持 +3. 会话ID可以用于保持对话的连续性,提升用户体验 +4. 机器人状态: 0表示停用,1表示启用 \ No newline at end of file diff --git a/weixin-java-cp/pom.xml b/weixin-java-cp/pom.xml index 85a37ba2f9..00a6b2d06c 100644 --- a/weixin-java-cp/pom.xml +++ b/weixin-java-cp/pom.xml @@ -7,7 +7,7 @@ com.github.binarywang wx-java - 4.7.0 + 4.7.9.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/WxCpExternalContactService.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpExternalContactService.java index f55d2f7b93..7f3cdeab7c 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpExternalContactService.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpExternalContactService.java @@ -38,6 +38,7 @@ public interface WxCpExternalContactService { * 用户需要妥善存储返回的config_id,config_id丢失可能导致用户无法编辑或删除「联系我」。 * 临时会话模式不占用「联系我」数量,但每日最多添加10万个,并且仅支持单人。 * 临时会话模式的二维码,添加好友完成后该二维码即刻失效。 + * 文档地址 * * * @param info 客户联系「联系我」方式 @@ -59,6 +60,25 @@ public interface WxCpExternalContactService { */ WxCpContactWayInfo getContactWay(String configId) throws WxErrorException; + /** + * 获取企业已配置的「联系我」列表 + * + *
+   * 获取企业配置的「联系我」二维码和「联系我」小程序插件列表。不包含临时会话。
+   * 注意,该接口仅可获取2021年7月10日以后创建的「联系我」
+   * 
+ * + * 文档地址: 获取企业已配置的「联系我」列表 + * + * @param startTime 「联系我」创建起始时间戳, 默认为90天前 + * @param endTime 「联系我」创建结束时间戳, 默认为当前时间 + * @param cursor 分页查询使用的游标,为上次请求返回的 next_cursor + * @param limit 每次查询的分页大小,默认为100条,最多支持1000条 + * @return contact way configId + * @throws WxErrorException the wx error exception + */ + WxCpContactWayList listContactWay(Long startTime, Long endTime, String cursor, Long limit) 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/WxCpIntelligentRobotService.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpIntelligentRobotService.java new file mode 100644 index 0000000000..f68092918f --- /dev/null +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpIntelligentRobotService.java @@ -0,0 +1,67 @@ +package me.chanjar.weixin.cp.api; + +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.cp.bean.intelligentrobot.*; + +/** + * 企业微信智能机器人接口 + * 官方文档: https://developer.work.weixin.qq.com/document/path/101039 + * + * @author Binary Wang + */ +public interface WxCpIntelligentRobotService { + + /** + * 创建智能机器人 + * + * @param request 创建请求参数 + * @return 创建结果 + * @throws WxErrorException 微信接口异常 + */ + WxCpIntelligentRobotCreateResponse createRobot(WxCpIntelligentRobotCreateRequest request) throws WxErrorException; + + /** + * 删除智能机器人 + * + * @param robotId 机器人ID + * @throws WxErrorException 微信接口异常 + */ + void deleteRobot(String robotId) throws WxErrorException; + + /** + * 更新智能机器人 + * + * @param request 更新请求参数 + * @throws WxErrorException 微信接口异常 + */ + void updateRobot(WxCpIntelligentRobotUpdateRequest request) throws WxErrorException; + + /** + * 查询智能机器人 + * + * @param robotId 机器人ID + * @return 机器人信息 + * @throws WxErrorException 微信接口异常 + */ + WxCpIntelligentRobot getRobot(String robotId) throws WxErrorException; + + /** + * 智能机器人会话 + * + * @param request 聊天请求参数 + * @return 聊天响应 + * @throws WxErrorException 微信接口异常 + */ + WxCpIntelligentRobotChatResponse chat(WxCpIntelligentRobotChatRequest request) throws WxErrorException; + + /** + * 重置智能机器人会话 + * + * @param robotId 机器人ID + * @param userid 用户ID + * @param sessionId 会话ID + * @throws WxErrorException 微信接口异常 + */ + void resetSession(String robotId, String userid, String sessionId) throws WxErrorException; + +} \ No newline at end of file diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpKfService.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpKfService.java index 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/WxCpSchoolUserService.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpSchoolUserService.java index 26cfe3a019..a92bfcc100 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpSchoolUserService.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpSchoolUserService.java @@ -334,7 +334,7 @@ WxCpBaseResp updateStudent(@NonNull String studentUserId, String newStudentUserI * 请求方式:GET(HTTPS) * 请求地址:https://qyapi.weixin.qq.com/cgi-bin/school/department/list?access_token=ACCESS_TOKEN&id=ID * - * @param id the id + * @param id 部门id。获取指定部门及其下的子部门。 如果不填,默认获取全量组织架构 * @return wx cp department list * @throws WxErrorException the wx error exception */ diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpService.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpService.java index 9bcb161534..0b601ca502 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpService.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpService.java @@ -587,4 +587,11 @@ public interface WxCpService extends WxService { * @return */ WxCpCorpGroupService getCorpGroupService(); + + /** + * 获取智能机器人服务 + * + * @return 智能机器人服务 intelligent robot service + */ + WxCpIntelligentRobotService getIntelligentRobotService(); } 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..bc18c9bc7a 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/BaseWxCpServiceImpl.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/BaseWxCpServiceImpl.java @@ -74,6 +74,7 @@ public abstract class BaseWxCpServiceImpl implements WxCpService, RequestH private final WxCpMeetingService meetingService = new WxCpMeetingServiceImpl(this); private final WxCpCorpGroupService corpGroupService = new WxCpCorpGroupServiceImpl(this); + private final WxCpIntelligentRobotService intelligentRobotService = new WxCpIntelligentRobotServiceImpl(this); /** * 全局的是否正在刷新access token的锁. @@ -110,7 +111,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; } } @@ -702,4 +703,9 @@ public WxCpMeetingService getMeetingService() { public WxCpCorpGroupService getCorpGroupService() { return corpGroupService; } + + @Override + public WxCpIntelligentRobotService getIntelligentRobotService() { + return this.intelligentRobotService; + } } 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/WxCpExternalContactServiceImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpExternalContactServiceImpl.java index c2fbdfe6ef..8e3a8d7b95 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpExternalContactServiceImpl.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpExternalContactServiceImpl.java @@ -65,6 +65,17 @@ public WxCpContactWayInfo getContactWay(String configId) throws WxErrorException return WxCpContactWayInfo.fromJson(this.mainService.post(url, json.toString())); } + @Override + public WxCpContactWayList listContactWay(Long startTime, Long endTime, String cursor, Long limit) throws WxErrorException { + JsonObject json = new JsonObject(); + json.addProperty("start_time", startTime); + json.addProperty("end_time", endTime); + json.addProperty("cursor", cursor); + json.addProperty("limit", limit); + final String url = this.mainService.getWxCpConfigStorage().getApiUrl(LIST_CONTACT_WAY); + return WxCpContactWayList.fromJson(this.mainService.post(url, json.toString())); + } + @Override public WxCpBaseResp updateContactWay(WxCpContactWayInfo info) throws WxErrorException { if (StringUtils.isBlank(info.getContactWay().getConfigId())) { 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/WxCpIntelligentRobotServiceImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpIntelligentRobotServiceImpl.java new file mode 100644 index 0000000000..c3bb23b38f --- /dev/null +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpIntelligentRobotServiceImpl.java @@ -0,0 +1,64 @@ +package me.chanjar.weixin.cp.api.impl; + +import com.google.gson.JsonObject; +import lombok.RequiredArgsConstructor; +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.cp.api.WxCpIntelligentRobotService; +import me.chanjar.weixin.cp.api.WxCpService; +import me.chanjar.weixin.cp.bean.intelligentrobot.*; +import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder; + +import static me.chanjar.weixin.cp.constant.WxCpApiPathConsts.IntelligentRobot.*; + +/** + * 企业微信智能机器人接口实现 + * + * @author Binary Wang + */ +@RequiredArgsConstructor +public class WxCpIntelligentRobotServiceImpl implements WxCpIntelligentRobotService { + + private final WxCpService cpService; + + @Override + public WxCpIntelligentRobotCreateResponse createRobot(WxCpIntelligentRobotCreateRequest request) throws WxErrorException { + String responseText = this.cpService.post(CREATE_ROBOT, request.toJson()); + return WxCpIntelligentRobotCreateResponse.fromJson(responseText); + } + + @Override + public void deleteRobot(String robotId) throws WxErrorException { + JsonObject jsonObject = new JsonObject(); + jsonObject.addProperty("robot_id", robotId); + this.cpService.post(DELETE_ROBOT, jsonObject.toString()); + } + + @Override + public void updateRobot(WxCpIntelligentRobotUpdateRequest request) throws WxErrorException { + this.cpService.post(UPDATE_ROBOT, request.toJson()); + } + + @Override + public WxCpIntelligentRobot getRobot(String robotId) throws WxErrorException { + JsonObject jsonObject = new JsonObject(); + jsonObject.addProperty("robot_id", robotId); + String responseText = this.cpService.post(GET_ROBOT, jsonObject.toString()); + return WxCpIntelligentRobot.fromJson(responseText); + } + + @Override + public WxCpIntelligentRobotChatResponse chat(WxCpIntelligentRobotChatRequest request) throws WxErrorException { + String responseText = this.cpService.post(CHAT, request.toJson()); + return WxCpIntelligentRobotChatResponse.fromJson(responseText); + } + + @Override + public void resetSession(String robotId, String userid, String sessionId) throws WxErrorException { + JsonObject jsonObject = new JsonObject(); + jsonObject.addProperty("robot_id", robotId); + jsonObject.addProperty("userid", userid); + jsonObject.addProperty("session_id", sessionId); + this.cpService.post(RESET_SESSION, jsonObject.toString()); + } + +} \ No newline at end of file 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..cdf559ad7a 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
@@ -11,6 +11,7 @@
 import me.chanjar.weixin.cp.api.WxCpMsgAuditService;
 import me.chanjar.weixin.cp.api.WxCpService;
 import me.chanjar.weixin.cp.bean.msgaudit.*;
+import me.chanjar.weixin.cp.config.WxCpConfigStorage;
 import me.chanjar.weixin.cp.util.crypto.WxCpCryptUtil;
 import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder;
 import org.apache.commons.lang3.StringUtils;
@@ -35,20 +36,59 @@
 public class WxCpMsgAuditServiceImpl implements WxCpMsgAuditService {
   private final WxCpService cpService;
 
+  /**
+   * SDK初始化有效期,根据企微文档为7200秒
+   */
+  private static final int SDK_EXPIRES_TIME = 7200;
+
   @Override
   public WxCpChatDatas getChatDatas(long seq, @NonNull long limit, String proxy, String passwd,
                                     @NonNull long timeout) throws Exception {
-    String configPath = cpService.getWxCpConfigStorage().getMsgAuditLibPath();
+    // 获取或初始化SDK
+    long sdk = this.initSdk();
+
+    long slice = Finance.NewSlice();
+    long ret = Finance.GetChatData(sdk, seq, limit, proxy, passwd, timeout, slice);
+    if (ret != 0) {
+      Finance.FreeSlice(slice);
+      throw new WxErrorException("getchatdata err ret " + ret);
+    }
+
+    // 拉取会话存档
+    String content = Finance.GetContentFromSlice(slice);
+    Finance.FreeSlice(slice);
+    WxCpChatDatas chatDatas = WxCpChatDatas.fromJson(content);
+    if (chatDatas.getErrCode().intValue() != 0) {
+      throw new WxErrorException(chatDatas.toJson());
+    }
+
+    chatDatas.setSdk(sdk);
+    return chatDatas;
+  }
+
+  /**
+   * 获取或初始化SDK,如果SDK已过期则重新初始化
+   *
+   * @return sdk id
+   * @throws WxErrorException 初始化失败时抛出异常
+   */
+  private synchronized long initSdk() throws WxErrorException {
+    WxCpConfigStorage configStorage = cpService.getWxCpConfigStorage();
+
+    // 检查SDK是否已缓存且未过期
+    if (!configStorage.isMsgAuditSdkExpired()) {
+      long cachedSdk = configStorage.getMsgAuditSdk();
+      if (cachedSdk > 0) {
+        return cachedSdk;
+      }
+    }
+
+    // SDK未初始化或已过期,需要重新初始化
+    String configPath = configStorage.getMsgAuditLibPath();
     if (StringUtils.isEmpty(configPath)) {
       throw new WxErrorException("请配置会话存档sdk文件的路径,不要配错了!!");
     }
 
-    /**
-     * 完整的文件库路径:
-     *
-     * /www/osfile/libcrypto-1_1-x64.dll,libssl-1_1-x64.dll,libcurl-x64.dll,WeWorkFinanceSdk.dll,
-     * libWeWorkFinanceSdk_Java.so
-     */
     // 替换斜杠
     String replacePath = configPath.replace("\\", "/");
     // 获取最后一个斜杠的下标,用作分割路径
@@ -66,9 +106,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 {
@@ -79,36 +119,22 @@ public WxCpChatDatas getChatDatas(long seq, @NonNull long limit, String proxy, S
 
     Finance.loadingLibraries(osLib, prefixPath);
     long sdk = Finance.NewSdk();
-    //因为会话存档单独有个secret,优先使用会话存档的secret
-    String msgAuditSecret = cpService.getWxCpConfigStorage().getMsgAuditSecret();
+    // 因为会话存档单独有个secret,优先使用会话存档的secret
+    String msgAuditSecret = configStorage.getMsgAuditSecret();
     if (StringUtils.isEmpty(msgAuditSecret)) {
-      msgAuditSecret = cpService.getWxCpConfigStorage().getCorpSecret();
+      msgAuditSecret = configStorage.getCorpSecret();
     }
-    long ret = Finance.Init(sdk, cpService.getWxCpConfigStorage().getCorpId(), msgAuditSecret);
+    long ret = Finance.Init(sdk, configStorage.getCorpId(), msgAuditSecret);
     if (ret != 0) {
       Finance.DestroySdk(sdk);
       throw new WxErrorException("init sdk err ret " + ret);
     }
 
-    long slice = Finance.NewSlice();
-    ret = Finance.GetChatData(sdk, seq, limit, proxy, passwd, timeout, slice);
-    if (ret != 0) {
-      Finance.FreeSlice(slice);
-      Finance.DestroySdk(sdk);
-      throw new WxErrorException("getchatdata err ret " + ret);
-    }
-
-    // 拉取会话存档
-    String content = Finance.GetContentFromSlice(slice);
-    Finance.FreeSlice(slice);
-    WxCpChatDatas chatDatas = WxCpChatDatas.fromJson(content);
-    if (chatDatas.getErrCode().intValue() != 0) {
-      Finance.DestroySdk(sdk);
-      throw new WxErrorException(chatDatas.toJson());
-    }
+    // 缓存SDK
+    configStorage.updateMsgAuditSdk(sdk, SDK_EXPIRES_TIME);
+    log.debug("初始化会话存档SDK成功,sdk={}", sdk);
 
-    chatDatas.setSdk(sdk);
-    return chatDatas;
+    return sdk;
   }
 
   @Override
@@ -128,36 +154,27 @@ public WxCpChatModel getDecryptData(@NonNull long sdk, @NonNull WxCpChatDatas.Wx
    * @throws Exception the exception
    */
   public String decryptChatData(long sdk, WxCpChatDatas.WxCpChatData chatData, Integer pkcs1) throws Exception {
-    /**
-     * 企业获取的会话内容,使用企业自行配置的消息加密公钥进行加密,企业可用自行保存的私钥解开会话内容数据。
-     * msgAuditPriKey 会话存档私钥不能为空
-     */
+    // 企业获取的会话内容,使用企业自行配置的消息加密公钥进行加密,企业可用自行保存的私钥解开会话内容数据。
+    // msgAuditPriKey 会话存档私钥不能为空
     String priKey = cpService.getWxCpConfigStorage().getMsgAuditPriKey();
     if (StringUtils.isEmpty(priKey)) {
       throw new WxErrorException("请配置会话存档私钥【msgAuditPriKey】");
     }
 
     String decryptByPriKey = WxCpCryptUtil.decryptPriKey(chatData.getEncryptRandomKey(), priKey, pkcs1);
-    /**
-     * 每次使用DecryptData解密会话存档前需要调用NewSlice获取一个slice,在使用完slice中数据后,还需要调用FreeSlice释放。
-     */
+    // 每次使用DecryptData解密会话存档前需要调用NewSlice获取一个slice,在使用完slice中数据后,还需要调用FreeSlice释放。
     long msg = Finance.NewSlice();
 
-    /**
-     * 解密会话存档内容
-     * sdk不会要求用户传入rsa私钥,保证用户会话存档数据只有自己能够解密。
-     * 此处需要用户先用rsa私钥解密encrypt_random_key后,作为encrypt_key参数传入sdk来解密encrypt_chat_msg获取会话存档明文。
-     */
+    // 解密会话存档内容
+    // sdk不会要求用户传入rsa私钥,保证用户会话存档数据只有自己能够解密。
+    // 此处需要用户先用rsa私钥解密encrypt_random_key后,作为encrypt_key参数传入sdk来解密encrypt_chat_msg获取会话存档明文。
     int ret = Finance.DecryptData(sdk, decryptByPriKey, chatData.getEncryptChatMsg(), msg);
     if (ret != 0) {
       Finance.FreeSlice(msg);
-      Finance.DestroySdk(sdk);
       throw new WxErrorException("msg err ret " + ret);
     }
 
-    /**
-     * 明文
-     */
+    // 明文
     String plainText = Finance.GetContentFromSlice(msg);
     Finance.FreeSlice(msg);
     return plainText;
@@ -209,7 +226,6 @@ public void getMediaFile(@NonNull long sdk, @NonNull String sdkfileid, String pr
       ret = Finance.GetMediaData(sdk, indexbuf, sdkfileid, proxy, passwd, timeout, mediaData);
       if (ret != 0) {
         Finance.FreeMediaData(mediaData);
-        Finance.DestroySdk(sdk);
         throw new WxErrorException("getmediadata err ret " + ret);
       }
 
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpOAuth2ServiceImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpOAuth2ServiceImpl.java
index 2a64f52bc7..d04a051c0e 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpOAuth2ServiceImpl.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpOAuth2ServiceImpl.java
@@ -13,6 +13,8 @@
 import me.chanjar.weixin.cp.bean.workbench.WxCpSecondVerificationInfo;
 import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder;
 
+import java.util.Optional;
+
 import static me.chanjar.weixin.common.api.WxConsts.OAuth2Scope.*;
 import static me.chanjar.weixin.cp.constant.WxCpApiPathConsts.OAuth2.*;
 
@@ -74,9 +76,9 @@ public WxCpOauth2UserInfo getUserInfo(Integer agentId, String code) throws WxErr
     JsonObject jo = GsonParser.parse(responseText);
 
     return WxCpOauth2UserInfo.builder()
-      .userId(GsonHelper.getString(jo, "UserId"))
+      .userId(Optional.ofNullable(GsonHelper.getString(jo, "UserId")).orElse(GsonHelper.getString(jo, "userid")))
       .deviceId(GsonHelper.getString(jo, "DeviceId"))
-      .openId(GsonHelper.getString(jo, "OpenId"))
+      .openId(Optional.ofNullable(GsonHelper.getString(jo, "OpenId")).orElse(GsonHelper.getString(jo, "openid")))
       .userTicket(GsonHelper.getString(jo, "user_ticket"))
       .expiresIn(GsonHelper.getString(jo, "expires_in"))
       .externalUserId(GsonHelper.getString(jo, "external_userid"))
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 fac1689e08..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
@@ -16,6 +16,7 @@
 import org.apache.commons.lang3.StringUtils;
 
 import java.util.List;
+import java.util.Objects;
 
 import static me.chanjar.weixin.cp.constant.WxCpApiPathConsts.ExternalContact.*;
 import static me.chanjar.weixin.cp.constant.WxCpApiPathConsts.School.*;
@@ -98,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));
@@ -246,7 +247,7 @@ public String convertToOpenId(@NonNull String externalUserId) throws WxErrorExce
 
   @Override
   public WxCpDepartmentList listDepartment(Integer id) throws WxErrorException {
-    String apiUrl = this.cpService.getWxCpConfigStorage().getApiUrl(DEPARTMENT_LIST) + id;
+    String apiUrl = Objects.isNull(id) ? this.cpService.getWxCpConfigStorage().getApiUrl(DEPARTMENT_LIST) : String.format("%s?id=%s", this.cpService.getWxCpConfigStorage().getApiUrl(DEPARTMENT_LIST), id);
     String responseContent = this.cpService.get(apiUrl, null);
     return WxCpDepartmentList.fromJson(responseContent);
   }
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..511c440e64 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
@@ -86,12 +86,12 @@ public void initHttp() {
       OkHttpClient.Builder clientBuilder = new OkHttpClient.Builder();
       clientBuilder.proxy(getRequestHttpProxy().getProxy());
       //设置授权
-      clientBuilder.authenticator(new Authenticator() {
+      clientBuilder.proxyAuthenticator(new Authenticator() {
         @Override
         public Request authenticate(Route route, Response response) throws IOException {
           String credential = Credentials.basic(httpProxy.getProxyUsername(), httpProxy.getProxyPassword());
           return response.request().newBuilder()
-            .header("Authorization", credential)
+            .header("Proxy-Authorization", credential)
             .build();
         }
       });
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/WxCpContactWayInfo.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/external/WxCpContactWayInfo.java
index 3a6a61902c..5da6a8fd5a 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/external/WxCpContactWayInfo.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/external/WxCpContactWayInfo.java
@@ -152,6 +152,13 @@ public static class ContactWay implements Serializable {
     @SerializedName("unionid")
     private String unionId;
 
+
+    /**
+     *非必填,是否开启同一外部企业客户只能添加同一个员工,默认为否,开启后,同一个企业的客户会优先添加到同一个跟进人
+     */
+    @SerializedName("is_exclusive")
+    private boolean isExclusive;
+
     /**
      * 
      * 非必填
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/external/WxCpContactWayList.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/external/WxCpContactWayList.java
new file mode 100644
index 0000000000..aeaeeea439
--- /dev/null
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/external/WxCpContactWayList.java
@@ -0,0 +1,69 @@
+package me.chanjar.weixin.cp.bean.external;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import me.chanjar.weixin.cp.bean.WxCpBaseResp;
+import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * 「联系我」方式 列表返回对象
+ *
+ * @author imyzt
+ */
+@EqualsAndHashCode(callSuper = true)
+@Data
+@NoArgsConstructor
+public class WxCpContactWayList extends WxCpBaseResp implements Serializable {
+  private static final long serialVersionUID = -8697184659526210472L;
+
+  @SerializedName("contact_way")
+  private List contactWay;
+
+  /**
+   * 分页参数,用于查询下一个分页的数据,为空时表示没有更多的分页
+   */
+  @SerializedName("next_cursor")
+  private String nextCursor;
+
+  /**
+   * The type Contact way.
+   */
+  @Getter
+  @Setter
+  public static class ContactWay implements Serializable {
+    private static final long serialVersionUID = -8697184659526210472L;
+
+    /**
+     * 联系方式的配置id
+     */
+    @SerializedName("config_id")
+    private String configId;
+  }
+
+  /**
+   * From json wx cp contact way list.
+   *
+   * @param json the json
+   * @return the wx cp contact way list
+   */
+  public static WxCpContactWayList fromJson(String json) {
+    return WxCpGsonBuilder.create().fromJson(json, WxCpContactWayList.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/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/intelligentrobot/WxCpIntelligentRobot.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/intelligentrobot/WxCpIntelligentRobot.java
new file mode 100644
index 0000000000..60d518cac3
--- /dev/null
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/intelligentrobot/WxCpIntelligentRobot.java
@@ -0,0 +1,77 @@
+package me.chanjar.weixin.cp.bean.intelligentrobot;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import me.chanjar.weixin.cp.bean.WxCpBaseResp;
+import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder;
+
+import java.io.Serializable;
+
+/**
+ * 智能机器人信息
+ *
+ * @author Binary Wang
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class WxCpIntelligentRobot extends WxCpBaseResp implements Serializable {
+  private static final long serialVersionUID = -1L;
+
+  /**
+   * 机器人ID
+   */
+  @SerializedName("robot_id")
+  private String robotId;
+
+  /**
+   * 机器人名称
+   */
+  @SerializedName("name")
+  private String name;
+
+  /**
+   * 机器人描述
+   */
+  @SerializedName("description")
+  private String description;
+
+  /**
+   * 机器人头像
+   */
+  @SerializedName("avatar")
+  private String avatar;
+
+  /**
+   * 机器人状态 0:停用 1:启用
+   */
+  @SerializedName("status")
+  private Integer status;
+
+  /**
+   * 创建时间
+   */
+  @SerializedName("create_time")
+  private Long createTime;
+
+  /**
+   * 更新时间
+   */
+  @SerializedName("update_time")
+  private Long updateTime;
+
+  /**
+   * From json wx cp intelligent robot.
+   *
+   * @param json the json
+   * @return the wx cp intelligent robot
+   */
+  public static WxCpIntelligentRobot fromJson(String json) {
+    return WxCpGsonBuilder.create().fromJson(json, WxCpIntelligentRobot.class);
+  }
+
+  public String toJson() {
+    return WxCpGsonBuilder.create().toJson(this);
+  }
+
+}
\ No newline at end of file
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/intelligentrobot/WxCpIntelligentRobotChatRequest.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/intelligentrobot/WxCpIntelligentRobotChatRequest.java
new file mode 100644
index 0000000000..d94ea93623
--- /dev/null
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/intelligentrobot/WxCpIntelligentRobotChatRequest.java
@@ -0,0 +1,49 @@
+package me.chanjar.weixin.cp.bean.intelligentrobot;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder;
+
+import java.io.Serializable;
+
+/**
+ * 智能机器人聊天请求
+ *
+ * @author Binary Wang
+ */
+@Data
+public class WxCpIntelligentRobotChatRequest implements Serializable {
+  private static final long serialVersionUID = -1L;
+
+  /**
+   * 机器人ID
+   */
+  @SerializedName("robot_id")
+  private String robotId;
+
+  /**
+   * 用户ID
+   */
+  @SerializedName("userid")
+  private String userid;
+
+  /**
+   * 消息内容
+   */
+  @SerializedName("message")
+  private String message;
+
+  /**
+   * 会话ID,可选,用于保持会话连续性
+   */
+  @SerializedName("session_id")
+  private String sessionId;
+
+  public String toJson() {
+    return WxCpGsonBuilder.create().toJson(this);
+  }
+
+  public static WxCpIntelligentRobotChatRequest fromJson(String json) {
+    return WxCpGsonBuilder.create().fromJson(json, WxCpIntelligentRobotChatRequest.class);
+  }
+}
\ No newline at end of file
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/intelligentrobot/WxCpIntelligentRobotChatResponse.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/intelligentrobot/WxCpIntelligentRobotChatResponse.java
new file mode 100644
index 0000000000..c7e56a4d03
--- /dev/null
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/intelligentrobot/WxCpIntelligentRobotChatResponse.java
@@ -0,0 +1,46 @@
+package me.chanjar.weixin.cp.bean.intelligentrobot;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import me.chanjar.weixin.cp.bean.WxCpBaseResp;
+import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder;
+
+import java.io.Serializable;
+
+/**
+ * 智能机器人聊天响应
+ *
+ * @author Binary Wang
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class WxCpIntelligentRobotChatResponse extends WxCpBaseResp implements Serializable {
+  private static final long serialVersionUID = -1L;
+
+  /**
+   * 机器人回复内容
+   */
+  @SerializedName("reply")
+  private String reply;
+
+  /**
+   * 会话ID
+   */
+  @SerializedName("session_id")
+  private String sessionId;
+
+  /**
+   * 消息ID
+   */
+  @SerializedName("msg_id")
+  private String msgId;
+
+  public static WxCpIntelligentRobotChatResponse fromJson(String json) {
+    return WxCpGsonBuilder.create().fromJson(json, WxCpIntelligentRobotChatResponse.class);
+  }
+
+  public String toJson() {
+    return WxCpGsonBuilder.create().toJson(this);
+  }
+}
\ No newline at end of file
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/intelligentrobot/WxCpIntelligentRobotCreateRequest.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/intelligentrobot/WxCpIntelligentRobotCreateRequest.java
new file mode 100644
index 0000000000..46dd5f609b
--- /dev/null
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/intelligentrobot/WxCpIntelligentRobotCreateRequest.java
@@ -0,0 +1,43 @@
+package me.chanjar.weixin.cp.bean.intelligentrobot;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder;
+
+import java.io.Serializable;
+
+/**
+ * 创建智能机器人请求
+ *
+ * @author Binary Wang
+ */
+@Data
+public class WxCpIntelligentRobotCreateRequest implements Serializable {
+  private static final long serialVersionUID = -1L;
+
+  /**
+   * 机器人名称
+   */
+  @SerializedName("name")
+  private String name;
+
+  /**
+   * 机器人描述
+   */
+  @SerializedName("description")
+  private String description;
+
+  /**
+   * 机器人头像
+   */
+  @SerializedName("avatar")
+  private String avatar;
+
+  public String toJson() {
+    return WxCpGsonBuilder.create().toJson(this);
+  }
+
+  public static WxCpIntelligentRobotCreateRequest fromJson(String json) {
+    return WxCpGsonBuilder.create().fromJson(json, WxCpIntelligentRobotCreateRequest.class);
+  }
+}
\ No newline at end of file
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/intelligentrobot/WxCpIntelligentRobotCreateResponse.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/intelligentrobot/WxCpIntelligentRobotCreateResponse.java
new file mode 100644
index 0000000000..449d91f7d3
--- /dev/null
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/intelligentrobot/WxCpIntelligentRobotCreateResponse.java
@@ -0,0 +1,34 @@
+package me.chanjar.weixin.cp.bean.intelligentrobot;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import me.chanjar.weixin.cp.bean.WxCpBaseResp;
+import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder;
+
+import java.io.Serializable;
+
+/**
+ * 创建智能机器人响应
+ *
+ * @author Binary Wang
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class WxCpIntelligentRobotCreateResponse extends WxCpBaseResp implements Serializable {
+  private static final long serialVersionUID = -1L;
+
+  /**
+   * 机器人ID
+   */
+  @SerializedName("robot_id")
+  private String robotId;
+
+  public static WxCpIntelligentRobotCreateResponse fromJson(String json) {
+    return WxCpGsonBuilder.create().fromJson(json, WxCpIntelligentRobotCreateResponse.class);
+  }
+
+  public String toJson() {
+    return WxCpGsonBuilder.create().toJson(this);
+  }
+}
\ No newline at end of file
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/intelligentrobot/WxCpIntelligentRobotUpdateRequest.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/intelligentrobot/WxCpIntelligentRobotUpdateRequest.java
new file mode 100644
index 0000000000..027812a7e9
--- /dev/null
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/intelligentrobot/WxCpIntelligentRobotUpdateRequest.java
@@ -0,0 +1,55 @@
+package me.chanjar.weixin.cp.bean.intelligentrobot;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder;
+
+import java.io.Serializable;
+
+/**
+ * 更新智能机器人请求
+ *
+ * @author Binary Wang
+ */
+@Data
+public class WxCpIntelligentRobotUpdateRequest implements Serializable {
+  private static final long serialVersionUID = -1L;
+
+  /**
+   * 机器人ID
+   */
+  @SerializedName("robot_id")
+  private String robotId;
+
+  /**
+   * 机器人名称
+   */
+  @SerializedName("name")
+  private String name;
+
+  /**
+   * 机器人描述
+   */
+  @SerializedName("description")
+  private String description;
+
+  /**
+   * 机器人头像
+   */
+  @SerializedName("avatar")
+  private String avatar;
+
+  /**
+   * 机器人状态 0:停用 1:启用
+   */
+  @SerializedName("status")
+  private Integer status;
+
+  public String toJson() {
+    return WxCpGsonBuilder.create().toJson(this);
+  }
+
+  public static WxCpIntelligentRobotUpdateRequest fromJson(String json) {
+    return WxCpGsonBuilder.create().fromJson(json, WxCpIntelligentRobotUpdateRequest.class);
+  }
+}
\ No newline at end of file
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/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 d843cad6cf..21a29abf8f 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
@@ -25,9 +25,6 @@ public class WxCpChatModel implements Serializable {
   @SerializedName("action")
   private String action;
 
-  @SerializedName("send")
-  private String send;
-
   @SerializedName("from")
   private String from;
 
@@ -205,6 +202,12 @@ public class WxCpChatModel implements Serializable {
   @SerializedName("sphfeed")
   private SphFeed sphFeed;
 
+  /**
+   * 音视频通话消息
+   */
+  @SerializedName("voiptext")
+  private VoipText voipText;
+
   /**
    * From json wx cp chat model.
    *
@@ -606,7 +609,7 @@ public static class File implements Serializable {
     private String sdkFileId;
 
     @SerializedName("filesize")
-    private Integer fileSize;
+    private Long fileSize;
 
     /**
      * From json file.
@@ -1336,4 +1339,40 @@ public String toJson() {
   }
 
 
+  /**
+   * 音视频通话消息
+   */
+  @Getter
+  @Setter
+  public static class VoipText implements Serializable {
+    private static final long serialVersionUID = -5028321625140879571L;
+
+    @SerializedName("callduration")
+    private Integer callDuration;
+
+    @SerializedName("invitetype")
+    private Integer inviteType;
+
+    /**
+     * From json voip text.
+     *
+     * @param json the json
+     * @return the voip text
+     */
+    public static VoipText fromJson(String json) {
+      return WxCpGsonBuilder.create().fromJson(json, VoipText.class);
+    }
+
+    /**
+     * To json string.
+     *
+     * @return the string
+     */
+    public String toJson() {
+      return WxCpGsonBuilder.create().toJson(this);
+    }
+
+  }
+
+
 }
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/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/WxCpOaApplyEventRequest.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/WxCpOaApplyEventRequest.java
index 8aebb66003..30ae6497f8 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/WxCpOaApplyEventRequest.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/WxCpOaApplyEventRequest.java
@@ -44,7 +44,13 @@ public class WxCpOaApplyEventRequest implements Serializable {
   private Integer chooseDepartment;
 
   /**
-   * 审批流程信息,用于指定审批申请的审批流程,支持单人审批、多人会签、多人或签,可能有多个审批节点,仅use_template_approver为0时生效。
+   * 审批流程信息(新版流程列表),用于指定审批申请的审批流程,支持单人审批、多人会签、多人或签,可能有多个审批节点,仅use_template_approver为0时生效。
+   */
+  @SerializedName("process")
+  private Process process;
+
+  /**
+   * 审批流程信息(旧版),用于指定审批申请的审批流程,支持单人审批、多人会签、多人或签,可能有多个审批节点,仅use_template_approver为0时生效。
    */
   @SerializedName("approver")
   private List approvers;
@@ -118,4 +124,46 @@ public static class ApplyData implements Serializable {
     private List contents;
   }
 
+  /**
+   * 审批流程信息(新版).
+   */
+  @Data
+  @Accessors(chain = true)
+  public static class Process implements Serializable {
+    private static final long serialVersionUID = 4758206091546930988L;
+
+    /**
+     * 审批流程节点列表,当use_template_approver为0时必填
+     */
+    @SerializedName("node_list")
+    private List nodeList;
+  }
+
+  /**
+   * 审批流程节点.
+   */
+  @Data
+  @Accessors(chain = true)
+  public static class ProcessNode implements Serializable {
+    private static final long serialVersionUID = 1758206091546930988L;
+
+    /**
+     * 节点类型:1-审批人,2-抄送人,3-抄送人
+     */
+    @SerializedName("type")
+    private Integer type;
+
+    /**
+     * 多人审批方式:1-全签,2-或签,3-依次审批
+     */
+    @SerializedName("apv_rel")
+    private Integer apvRel;
+
+    /**
+     * 审批节点审批人userid列表,若为多人会签、多人或签,需填写每个人的userid
+     */
+    @SerializedName("userid")
+    private String[] userIds;
+  }
+
 }
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 2a497d15fc..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
@@ -6,6 +6,7 @@
 import lombok.Data;
 import lombok.NoArgsConstructor;
 import lombok.experimental.Accessors;
+import me.chanjar.weixin.cp.bean.oa.templatedata.TemplateTips;
 import me.chanjar.weixin.cp.bean.oa.templatedata.TemplateTitle;
 import me.chanjar.weixin.cp.bean.oa.templatedata.control.*;
 import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder;
@@ -16,7 +17,7 @@
 /**
  * 审批模板详情
  *
- * @author gyv12345 @163.com / Wang_Wong
+ * @author gyv12345@163.com / Wang_Wong
  */
 @Data
 @Builder
@@ -94,6 +95,9 @@ public static class TemplateConfig implements Serializable {
     @SerializedName("vacation_list")
     private TemplateVacation vacationList;
 
+    @SerializedName("tips")
+    private TemplateTips tips;
+
   }
 
   @Data
@@ -117,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/TemplateConfig.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/templatedata/TemplateConfig.java
index 1a00baad0f..91ee8b7cde 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/templatedata/TemplateConfig.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/templatedata/TemplateConfig.java
@@ -37,4 +37,6 @@ public class TemplateConfig implements Serializable {
   @SerializedName("vacation_list")
   private TemplateVacation vacationList;
 
+  private TemplateTips tips;
+
 }
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/oa/templatedata/TemplateTips.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/templatedata/TemplateTips.java
new file mode 100644
index 0000000000..58daeb007c
--- /dev/null
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/templatedata/TemplateTips.java
@@ -0,0 +1,18 @@
+package me.chanjar.weixin.cp.bean.oa.templatedata;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * @author mrsiu@msn.com
+ * @version 1.0
+ * @date 2025/1/16 09:40
+ */
+@Data
+public class TemplateTips {
+
+  @SerializedName("tips_content")
+  private List tipsContent;
+}
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/templatedata/TemplateTipsContent.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/templatedata/TemplateTipsContent.java
new file mode 100644
index 0000000000..939e6819a0
--- /dev/null
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/templatedata/TemplateTipsContent.java
@@ -0,0 +1,15 @@
+package me.chanjar.weixin.cp.bean.oa.templatedata;
+
+import lombok.Data;
+
+/**
+ * @author mrsiu@msn.com
+ * @version 1.0
+ * @date 2025/1/16 09:42
+ */
+@Data
+public class TemplateTipsContent {
+
+  private TemplateTipsText text;
+  private String lang;
+}
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/templatedata/TemplateTipsSubText.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/templatedata/TemplateTipsSubText.java
new file mode 100644
index 0000000000..ac4681038c
--- /dev/null
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/templatedata/TemplateTipsSubText.java
@@ -0,0 +1,14 @@
+package me.chanjar.weixin.cp.bean.oa.templatedata;
+
+import lombok.Data;
+
+/**
+ * @author mrsiu@msn.com
+ * @version 1.0
+ * @date 2025/1/16 09:45
+ */
+@Data
+public class TemplateTipsSubText {
+  private Integer type;
+  private TemplateTipsSubTextContent content;
+}
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/templatedata/TemplateTipsSubTextContent.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/templatedata/TemplateTipsSubTextContent.java
new file mode 100644
index 0000000000..9c99b2688e
--- /dev/null
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/templatedata/TemplateTipsSubTextContent.java
@@ -0,0 +1,16 @@
+package me.chanjar.weixin.cp.bean.oa.templatedata;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+
+/**
+ * @author mrsiu@msn.com
+ * @version 1.0
+ * @date 2025/1/16 09:46
+ */
+@Data
+public class TemplateTipsSubTextContent {
+  @SerializedName("plain_text")
+  private TemplateTipsSubTextContentPlainText plainText;
+  private TemplateTipsSubTextContentLink link;
+}
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/templatedata/TemplateTipsSubTextContentLink.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/templatedata/TemplateTipsSubTextContentLink.java
new file mode 100644
index 0000000000..4cd198409a
--- /dev/null
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/templatedata/TemplateTipsSubTextContentLink.java
@@ -0,0 +1,14 @@
+package me.chanjar.weixin.cp.bean.oa.templatedata;
+
+import lombok.Data;
+
+/**
+ * @author mrsiu@msn.com
+ * @version 1.0
+ * @date 2025/1/16 09:49
+ */
+@Data
+public class TemplateTipsSubTextContentLink {
+  private String title;
+  private String url;
+}
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/templatedata/TemplateTipsSubTextContentPlainText.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/templatedata/TemplateTipsSubTextContentPlainText.java
new file mode 100644
index 0000000000..12969cdcdb
--- /dev/null
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/templatedata/TemplateTipsSubTextContentPlainText.java
@@ -0,0 +1,13 @@
+package me.chanjar.weixin.cp.bean.oa.templatedata;
+
+import lombok.Data;
+
+/**
+ * @author  mrsiu@msn.com
+ * @date  2025/1/16 09:47
+ * @version  1.0
+ */
+@Data
+public class TemplateTipsSubTextContentPlainText {
+     private String content;
+}
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/templatedata/TemplateTipsText.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/templatedata/TemplateTipsText.java
new file mode 100644
index 0000000000..100c5bb137
--- /dev/null
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/templatedata/TemplateTipsText.java
@@ -0,0 +1,17 @@
+package me.chanjar.weixin.cp.bean.oa.templatedata;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * @author  mrsiu@msn.com
+ * @date  2025/1/16 09:43
+ * @version  1.0
+ */
+@Data
+public class TemplateTipsText {
+    @SerializedName("sub_text")
+    private List subText;
+}
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/WxCpConfigStorage.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/WxCpConfigStorage.java
index 36203aab11..8b968e540c 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/WxCpConfigStorage.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/WxCpConfigStorage.java
@@ -260,7 +260,36 @@ public interface WxCpConfigStorage {
 
   /**
    * 获取会话存档的secret
+   *
    * @return msg audit secret
    */
   String getMsgAuditSecret();
+
+  /**
+   * 获取会话存档SDK
+   * 会话存档SDK初始化后有效期为7200秒,无需每次重新初始化
+   *
+   * @return sdk id,如果未初始化或已过期返回0
+   */
+  long getMsgAuditSdk();
+
+  /**
+   * 检查会话存档SDK是否已过期
+   *
+   * @return true: 已过期, false: 未过期
+   */
+  boolean isMsgAuditSdkExpired();
+
+  /**
+   * 更新会话存档SDK
+   *
+   * @param sdk             sdk id
+   * @param expiresInSeconds 过期时间(秒)
+   */
+  void updateMsgAuditSdk(long sdk, int expiresInSeconds);
+
+  /**
+   * 使会话存档SDK过期
+   */
+  void expireMsgAuditSdk();
 }
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/WxCpDefaultConfigImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/impl/WxCpDefaultConfigImpl.java
index 57647b3712..4bf13f24ea 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/impl/WxCpDefaultConfigImpl.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/impl/WxCpDefaultConfigImpl.java
@@ -49,6 +49,11 @@ public class WxCpDefaultConfigImpl implements WxCpConfigStorage, Serializable {
   private volatile String msgAuditSecret;
   private volatile String msgAuditPriKey;
   private volatile String msgAuditLibPath;
+  /**
+   * 会话存档SDK及其过期时间
+   */
+  private volatile long msgAuditSdk;
+  private volatile long msgAuditSdkExpiresTime;
   private volatile String oauth2redirectUri;
   private volatile String httpProxyHost;
   private volatile int httpProxyPort;
@@ -444,10 +449,34 @@ public String getMsgAuditSecret() {
 
   /**
    * 设置会话存档secret
-   * @param msgAuditSecret
+   *
+   * @param msgAuditSecret the msg audit secret
+   * @return this
    */
   public WxCpDefaultConfigImpl setMsgAuditSecret(String msgAuditSecret) {
     this.msgAuditSecret = msgAuditSecret;
     return this;
   }
+
+  @Override
+  public long getMsgAuditSdk() {
+    return this.msgAuditSdk;
+  }
+
+  @Override
+  public boolean isMsgAuditSdkExpired() {
+    return System.currentTimeMillis() > this.msgAuditSdkExpiresTime;
+  }
+
+  @Override
+  public synchronized void updateMsgAuditSdk(long sdk, int expiresInSeconds) {
+    this.msgAuditSdk = sdk;
+    // 预留200秒的时间
+    this.msgAuditSdkExpiresTime = System.currentTimeMillis() + (expiresInSeconds - 200) * 1000L;
+  }
+
+  @Override
+  public void expireMsgAuditSdk() {
+    this.msgAuditSdkExpiresTime = 0;
+  }
 }
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/impl/WxCpRedisConfigImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/impl/WxCpRedisConfigImpl.java
index 4225cff0af..15c0ff6727 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/impl/WxCpRedisConfigImpl.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/impl/WxCpRedisConfigImpl.java
@@ -50,6 +50,11 @@ public class WxCpRedisConfigImpl implements WxCpConfigStorage {
   private volatile File tmpDirFile;
   private volatile ApacheHttpClientBuilder apacheHttpClientBuilder;
   private volatile String webhookKey;
+  /**
+   * 会话存档SDK及其过期时间(SDK是本地JVM变量,不适合存储到Redis)
+   */
+  private volatile long msgAuditSdk;
+  private volatile long msgAuditSdkExpiresTime;
 
   /**
    * Instantiates a new Wx cp redis config.
@@ -470,4 +475,26 @@ public String getWebhookKey() {
   public String getMsgAuditSecret() {
     return null;
   }
+
+  @Override
+  public long getMsgAuditSdk() {
+    return this.msgAuditSdk;
+  }
+
+  @Override
+  public boolean isMsgAuditSdkExpired() {
+    return System.currentTimeMillis() > this.msgAuditSdkExpiresTime;
+  }
+
+  @Override
+  public synchronized void updateMsgAuditSdk(long sdk, int expiresInSeconds) {
+    this.msgAuditSdk = sdk;
+    // 预留200秒的时间
+    this.msgAuditSdkExpiresTime = System.currentTimeMillis() + (expiresInSeconds - 200) * 1000L;
+  }
+
+  @Override
+  public void expireMsgAuditSdk() {
+    this.msgAuditSdkExpiresTime = 0;
+  }
 }
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/impl/WxCpTpDefaultConfigImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/impl/WxCpTpDefaultConfigImpl.java
index 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 b53f7549d7..91314e5872 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";
   }
 
   /**
@@ -691,7 +701,7 @@ interface School {
     /**
      * The constant DEPARTMENT_LIST.
      */
-    String DEPARTMENT_LIST = "/cgi-bin/school/department/list?id=";
+    String DEPARTMENT_LIST = "/cgi-bin/school/department/list";
 
     /**
      * The constant GET_PAYMENT_RESULT.
@@ -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.
      */
@@ -1085,6 +1099,10 @@ interface ExternalContact {
      * The constant GET_CONTACT_WAY.
      */
     String GET_CONTACT_WAY = "/cgi-bin/externalcontact/get_contact_way";
+    /**
+     * The constant LIST_CONTACT_WAY.
+     */
+    String LIST_CONTACT_WAY = "/cgi-bin/externalcontact/list_contact_way";
     /**
      * The constant UPDATE_CONTACT_WAY.
      */
@@ -1613,4 +1631,40 @@ interface IdConvert {
      */
     String CONVERT_TMP_EXTERNAL_USER_ID = "/cgi-bin/idconvert/convert_tmp_external_userid";
   }
+
+  /**
+   * 智能机器人相关接口
+   * 官方文档: https://developer.work.weixin.qq.com/document/path/101039
+   */
+  interface IntelligentRobot {
+    /**
+     * 创建智能机器人
+     */
+    String CREATE_ROBOT = "/cgi-bin/intelligent_robot/create";
+    
+    /**
+     * 删除智能机器人
+     */
+    String DELETE_ROBOT = "/cgi-bin/intelligent_robot/delete";
+    
+    /**
+     * 更新智能机器人
+     */
+    String UPDATE_ROBOT = "/cgi-bin/intelligent_robot/update";
+    
+    /**
+     * 查询智能机器人
+     */
+    String GET_ROBOT = "/cgi-bin/intelligent_robot/get";
+    
+    /**
+     * 智能机器人会话
+     */
+    String CHAT = "/cgi-bin/intelligent_robot/chat";
+    
+    /**
+     * 重置智能机器人会话
+     */
+    String RESET_SESSION = "/cgi-bin/intelligent_robot/reset_session";
+  }
 }
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..c4dab9cf20
--- /dev/null
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/impl/WxCpTpServiceOkHttpImpl.java
@@ -0,0 +1,130 @@
+package me.chanjar.weixin.cp.tp.service.impl;
+
+import com.google.gson.JsonObject;
+import lombok.extern.slf4j.Slf4j;
+import me.chanjar.weixin.common.bean.WxAccessToken;
+import me.chanjar.weixin.common.enums.WxType;
+import me.chanjar.weixin.common.error.WxError;
+import me.chanjar.weixin.common.error.WxErrorException;
+import me.chanjar.weixin.common.error.WxRuntimeException;
+import me.chanjar.weixin.common.util.http.HttpClientType;
+import me.chanjar.weixin.common.util.http.okhttp.DefaultOkHttpClientBuilder;
+import me.chanjar.weixin.common.util.http.okhttp.OkHttpProxyInfo;
+import me.chanjar.weixin.common.util.json.GsonParser;
+import me.chanjar.weixin.cp.api.impl.BaseWxCpServiceImpl;
+import me.chanjar.weixin.cp.config.WxCpConfigStorage;
+import me.chanjar.weixin.cp.constant.WxCpApiPathConsts;
+import okhttp3.*;
+
+import java.io.IOException;
+
+import static me.chanjar.weixin.cp.constant.WxCpApiPathConsts.GET_TOKEN;
+
+/**
+ * The type Wx cp service ok http.
+ *
+ * @author someone
+ */
+@Slf4j
+public class WxCpTpServiceOkHttpImpl extends BaseWxCpTpServiceImpl {
+  private OkHttpClient httpClient;
+  private OkHttpProxyInfo httpProxy;
+
+  @Override
+  public OkHttpClient getRequestHttpClient() {
+    return httpClient;
+  }
+
+  @Override
+  public OkHttpProxyInfo getRequestHttpProxy() {
+    return httpProxy;
+  }
+
+  @Override
+  public HttpClientType getRequestType() {
+    return HttpClientType.OK_HTTP;
+  }
+
+  @Override
+  public String getSuiteAccessToken(boolean forceRefresh) throws WxErrorException {
+    if (!this.configStorage.isSuiteAccessTokenExpired() && !forceRefresh) {
+      return this.configStorage.getSuiteAccessToken();
+    }
+
+    synchronized (this.globalSuiteAccessTokenRefreshLock) {
+      // 得到 httpClient
+      OkHttpClient client = getRequestHttpClient();
+
+      JsonObject jsonObject = new JsonObject();
+      jsonObject.addProperty("suite_id", this.configStorage.getSuiteId());
+      jsonObject.addProperty("suite_secret", this.configStorage.getSuiteSecret());
+      jsonObject.addProperty("suite_ticket", this.getSuiteTicket());
+      String jsonBody = jsonObject.toString();
+
+      RequestBody requestBody = RequestBody.create(
+        MediaType.get("application/json; charset=utf-8"),
+        jsonBody
+      );
+
+      // 构建 POST 请求
+      Request request = new Request.Builder()
+        .url(this.configStorage.getApiUrl(WxCpApiPathConsts.Tp.GET_SUITE_TOKEN)) // URL 不包含查询参数
+        .post(requestBody) // 使用 POST 方法
+        .build();
+
+      String resultContent = null;
+      try (Response response = client.newCall(request).execute()) {
+        if (!response.isSuccessful()) {
+          throw new IOException("Unexpected response code: " + response);
+        }
+        resultContent = response.body().string();
+      } catch (IOException e) {
+        log.error("获取 suite token 失败: {}", e.getMessage(), e);
+        throw new WxRuntimeException("获取 suite token 失败", e);
+      }
+
+      WxError error = WxError.fromJson(resultContent, WxType.CP);
+      if (error.getErrorCode() != 0) {
+        throw new WxErrorException(error);
+      }
+
+      jsonObject = GsonParser.parse(resultContent);
+      String suiteAccussToken = jsonObject.get("suite_access_token").getAsString();
+      int expiresIn = jsonObject.get("expires_in").getAsInt();
+      this.configStorage.updateSuiteAccessToken(suiteAccussToken, expiresIn);
+    }
+    return this.configStorage.getSuiteAccessToken();
+  }
+
+  @Override
+  public void initHttp() {
+    log.debug("WxCpServiceOkHttpImpl initHttp");
+    //设置代理
+    if (configStorage.getHttpProxyHost() != null && configStorage.getHttpProxyPort() > 0) {
+      httpProxy = OkHttpProxyInfo.httpProxy(configStorage.getHttpProxyHost(),
+        configStorage.getHttpProxyPort(),
+        configStorage.getHttpProxyUsername(),
+        configStorage.getHttpProxyPassword());
+      OkHttpClient.Builder clientBuilder = new OkHttpClient.Builder();
+      clientBuilder.proxy(getRequestHttpProxy().getProxy());
+      //设置授权
+      clientBuilder.proxyAuthenticator(new Authenticator() {
+        @Override
+        public Request authenticate(Route route, Response response) throws IOException {
+          String credential = Credentials.basic(httpProxy.getProxyUsername(), httpProxy.getProxyPassword());
+          return response.request().newBuilder()
+            .header("Proxy-Authorization", credential)
+            .build();
+        }
+      });
+      httpClient = clientBuilder.build();
+    } else {
+      httpClient = DefaultOkHttpClientBuilder.get().build();
+    }
+  }
+
+//  @Override
+//  public WxCpConfigStorage getWxCpConfigStorage() {
+//    return this.configStorage;
+//  }
+}
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/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/WxCpGsonBuilder.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/util/json/WxCpGsonBuilder.java
index 7b53aa337a..48228a0686 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/util/json/WxCpGsonBuilder.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/util/json/WxCpGsonBuilder.java
@@ -1,9 +1,12 @@
 package me.chanjar.weixin.cp.util.json;
 
+import com.google.gson.ExclusionStrategy;
+import com.google.gson.FieldAttributes;
 import com.google.gson.Gson;
 import com.google.gson.GsonBuilder;
 import me.chanjar.weixin.common.bean.menu.WxMenu;
 import me.chanjar.weixin.common.error.WxError;
+import me.chanjar.weixin.common.util.http.apache.ApacheHttpClientBuilder;
 import me.chanjar.weixin.common.util.json.WxErrorAdapter;
 import me.chanjar.weixin.cp.bean.WxCpChat;
 import me.chanjar.weixin.cp.bean.WxCpDepart;
@@ -11,6 +14,7 @@
 import me.chanjar.weixin.cp.bean.WxCpUser;
 import me.chanjar.weixin.cp.bean.kf.WxCpKfGetCorpStatisticResp;
 
+import java.io.File;
 import java.util.Objects;
 
 /**
@@ -32,6 +36,18 @@ public class WxCpGsonBuilder {
     INSTANCE.registerTypeAdapter(WxMenu.class, new WxCpMenuGsonAdapter());
     INSTANCE.registerTypeAdapter(WxCpTag.class, new WxCpTagGsonAdapter());
     INSTANCE.registerTypeAdapter(WxCpKfGetCorpStatisticResp.StatisticList.class, new StatisticListAdapter());
+
+    INSTANCE.setExclusionStrategies(new ExclusionStrategy() {
+      @Override
+      public boolean shouldSkipField(FieldAttributes fieldAttributes) {
+        return false;
+      }
+
+      @Override
+      public boolean shouldSkipClass(Class aClass) {
+        return aClass == File.class || aClass == ApacheHttpClientBuilder.class;
+      }
+    });
   }
 
   /**
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/WxCpExternalContactServiceImplTest.java b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/impl/WxCpExternalContactServiceImplTest.java
index c629165ca4..4bd80928bd 100644
--- a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/impl/WxCpExternalContactServiceImplTest.java
+++ b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/impl/WxCpExternalContactServiceImplTest.java
@@ -4,6 +4,9 @@
 
 import com.google.common.collect.Lists;
 import com.google.inject.Inject;
+
+import java.time.LocalDateTime;
+import java.time.ZoneOffset;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Date;
@@ -90,6 +93,20 @@ public void testGetContactWay() throws WxErrorException {
     assertNotNull(contactWayInfo);
   }
 
+  /**
+   * Test list contact way.
+   *
+   * @throws WxErrorException the wx error exception
+   */
+  @Test
+  public void testListContactWay() throws WxErrorException {
+    long startTime = LocalDateTime.now().minusDays(1).toEpochSecond(ZoneOffset.of("+8"));
+    long endTime = LocalDateTime.now().toEpochSecond(ZoneOffset.of("+8"));
+    WxCpContactWayList wxCpContactWayList = this.wxCpService.getExternalContactService().listContactWay(startTime, endTime, null, 100L);
+    System.out.println(wxCpContactWayList.toJson());
+    assertNotNull(wxCpContactWayList);
+  }
+
   /**
    * Test update contact way.
    *
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/WxCpIntelligentRobotServiceImplTest.java b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/impl/WxCpIntelligentRobotServiceImplTest.java
new file mode 100644
index 0000000000..2765b49916
--- /dev/null
+++ b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/impl/WxCpIntelligentRobotServiceImplTest.java
@@ -0,0 +1,88 @@
+package me.chanjar.weixin.cp.api.impl;
+
+import me.chanjar.weixin.cp.api.WxCpService;
+import me.chanjar.weixin.cp.api.ApiTestModule;
+import me.chanjar.weixin.cp.bean.intelligentrobot.*;
+import org.testng.annotations.Guice;
+import org.testng.annotations.Test;
+
+import javax.inject.Inject;
+
+/**
+ * 智能机器人接口测试
+ *
+ * @author Binary Wang
+ */
+@Test
+@Guice(modules = ApiTestModule.class)
+public class WxCpIntelligentRobotServiceImplTest {
+
+  @Inject
+  private WxCpService wxCpService;
+
+  @Test
+  public void testCreateRobot() {
+    // 测试创建智能机器人请求对象创建
+    WxCpIntelligentRobotCreateRequest request = new WxCpIntelligentRobotCreateRequest();
+    request.setName("测试机器人");
+    request.setDescription("这是一个测试的智能机器人");
+    request.setAvatar("avatar_url");
+
+    // 验证JSON序列化
+    String json = request.toJson();
+    assert json.contains("测试机器人");
+    assert json.contains("这是一个测试的智能机器人");
+    
+    // 验证反序列化
+    WxCpIntelligentRobotCreateRequest fromJson = WxCpIntelligentRobotCreateRequest.fromJson(json);
+    assert fromJson.getName().equals("测试机器人");
+  }
+
+  @Test
+  public void testChatRequest() {
+    // 测试聊天请求对象创建
+    WxCpIntelligentRobotChatRequest request = new WxCpIntelligentRobotChatRequest();
+    request.setRobotId("robot123");
+    request.setUserid("user123");
+    request.setMessage("你好,机器人");
+    request.setSessionId("session123");
+
+    // 验证JSON序列化
+    String json = request.toJson();
+    assert json.contains("robot123");
+    assert json.contains("你好,机器人");
+    
+    // 验证反序列化
+    WxCpIntelligentRobotChatRequest fromJson = WxCpIntelligentRobotChatRequest.fromJson(json);
+    assert fromJson.getRobotId().equals("robot123");
+    assert fromJson.getMessage().equals("你好,机器人");
+  }
+
+  @Test
+  public void testUpdateRequest() {
+    // 测试更新请求对象创建
+    WxCpIntelligentRobotUpdateRequest request = new WxCpIntelligentRobotUpdateRequest();
+    request.setRobotId("robot123");
+    request.setName("更新后的机器人");
+    request.setDescription("更新后的描述");
+    request.setStatus(1);
+
+    // 验证JSON序列化
+    String json = request.toJson();
+    assert json.contains("robot123");
+    assert json.contains("更新后的机器人");
+    
+    // 验证反序列化
+    WxCpIntelligentRobotUpdateRequest fromJson = WxCpIntelligentRobotUpdateRequest.fromJson(json);
+    assert fromJson.getRobotId().equals("robot123");
+    assert fromJson.getName().equals("更新后的机器人");
+    assert fromJson.getStatus().equals(1);
+  }
+
+  @Test
+  public void testServiceIntegration() {
+    // 验证服务可以正确获取
+    assert this.wxCpService.getIntelligentRobotService() != null;
+    assert this.wxCpService.getIntelligentRobotService() instanceof WxCpIntelligentRobotServiceImpl;
+  }
+}
\ No newline at end of file
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/api/impl/WxCpSchoolUserServiceImplTest.java b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/impl/WxCpSchoolUserServiceImplTest.java
new file mode 100644
index 0000000000..da1cc25542
--- /dev/null
+++ b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/impl/WxCpSchoolUserServiceImplTest.java
@@ -0,0 +1,139 @@
+package me.chanjar.weixin.cp.api.impl;
+
+import com.google.gson.Gson;
+import me.chanjar.weixin.common.error.WxErrorException;
+import me.chanjar.weixin.cp.api.WxCpService;
+import me.chanjar.weixin.cp.bean.school.user.WxCpDepartmentList;
+import me.chanjar.weixin.cp.config.impl.WxCpDefaultConfigImpl;
+import org.mockito.Mockito;
+import org.testng.annotations.Test;
+
+import static me.chanjar.weixin.cp.constant.WxCpApiPathConsts.School.DEPARTMENT_LIST;
+import static org.testng.Assert.assertEquals;
+
+public class WxCpSchoolUserServiceImplTest {
+
+
+  String allDeptListJson = "{\n" +
+    "\t\"errcode\": 0,\n" +
+    "\t\"errmsg\": \"ok\",\n" +
+    "\t\"departments\": [\n" +
+    "\t\t{\n" +
+    "\t\t\t\"name\": \"一年级\",\n" +
+    "\t\t\t\"parentid\": 1,\n" +
+    "\t\t\t\"id\": 2,\n" +
+    "\t\t\t\"type\":2,\n" +
+    "\t\t\t\"register_year\":2018,\n" +
+    "\t\t\t\"standard_grade\":1,\n" +
+    "\t\t\t\"order\":1,\n" +
+    "\t\t\t\"department_admins\": [\n" +
+    "\t\t\t\t{\n" +
+    "\t\t\t\t\t\"userid\": \"zhangsan\",\n" +
+    "\t\t\t\t\t\"type\": 1\n" +
+    "\t\t\t\t},\n" +
+    "\t\t\t\t{\n" +
+    "\t\t\t\t\t\"userid\": \"lisi\",\n" +
+    "\t\t\t\t\t\"type\": 2\n" +
+    "\t\t\t\t}\n" +
+    "\t\t\t],\n" +
+    "            \"is_graduated\": 0\n" +
+    "\t\t},\n" +
+    "\t\t{\n" +
+    "\t\t\t\"name\": \"一年级一班\",\n" +
+    "\t\t\t\"parentid\": 1,\n" +
+    "\t\t\t\"id\": 3,\n" +
+    "\t\t\t\"type\": 1,\n" +
+    "\t\t\t\"department_admins\": [\n" +
+    "\t\t\t\t{\n" +
+    "\t\t\t\t\t\"userid\": \"zhangsan\",\n" +
+    "\t\t\t\t\t\"type\": 3,\n" +
+    "\t\t\t\t\t\"subject\":\"语文\"\n" +
+    "\t\t\t\t},\n" +
+    "\t\t\t\t{\n" +
+    "\t\t\t\t\t\"userid\": \"lisi\",\n" +
+    "\t\t\t\t\t\"type\": 4,\n" +
+    "\t\t\t\t\t\"subject\":\"数学\"\n" +
+    "\t\t\t\t}\n" +
+    "\t\t\t],\n" +
+    "\t\t\t\"open_group_chat\": 1,\n" +
+    "            \"group_chat_id\": \"group_chat_id\"\n" +
+    "\t\t}\n" +
+    "\t]\n" +
+    "}\n";
+
+  String deptId3Json = "{\n" +
+    "  \"errcode\": 0,\n" +
+    "  \"errmsg\": \"ok\",\n" +
+    "  \"departments\": [\n" +
+    "    {\n" +
+    "      \"name\": \"一年级一班\",\n" +
+    "      \"parentid\": 1,\n" +
+    "      \"id\": 3,\n" +
+    "      \"type\": 1,\n" +
+    "      \"department_admins\": [\n" +
+    "        {\n" +
+    "          \"userid\": \"zhangsan\",\n" +
+    "          \"type\": 3,\n" +
+    "          \"subject\":\"语文\"\n" +
+    "        },\n" +
+    "        {\n" +
+    "          \"userid\": \"lisi\",\n" +
+    "          \"type\": 4,\n" +
+    "          \"subject\":\"数学\"\n" +
+    "        }\n" +
+    "      ],\n" +
+    "      \"open_group_chat\": 1,\n" +
+    "      \"group_chat_id\": \"group_chat_id\"\n" +
+    "    }\n" +
+    "  ]\n" +
+    "}";
+
+  String deptId2Json = "{\n" +
+    "  \"errcode\": 0,\n" +
+    "  \"errmsg\": \"ok\",\n" +
+    "  \"departments\": []\n" +
+    "}\n";
+
+
+  @Test
+  public void testListDepartmentWhenIdIsNull() throws WxErrorException {
+
+    WxCpService mockCpService = Mockito.mock(WxCpService.class);
+    WxCpSchoolUserServiceImpl wxCpSchoolUserService = new WxCpSchoolUserServiceImpl(mockCpService);
+    Mockito.when(mockCpService.getWxCpConfigStorage()).thenReturn(new WxCpDefaultConfigImpl());
+    Mockito.when(mockCpService.get(mockCpService.getWxCpConfigStorage().getApiUrl(DEPARTMENT_LIST), null)).thenReturn(allDeptListJson);
+    WxCpDepartmentList wxCpDepartmentList = wxCpSchoolUserService.listDepartment(null);
+    //WxCpDepartmentList没有重写Equals和Hashcode,不能直接比较
+    Gson gson = new Gson();
+    assertEquals(gson.toJson(wxCpDepartmentList), gson.toJson(gson.fromJson(allDeptListJson, WxCpDepartmentList.class)), "should be equal");
+
+  }
+
+  @Test
+  public void testListDepartmentWhenIdIs2() throws WxErrorException {
+
+    WxCpService mockCpService = Mockito.mock(WxCpService.class);
+    WxCpSchoolUserServiceImpl wxCpSchoolUserService = new WxCpSchoolUserServiceImpl(mockCpService);
+    Mockito.when(mockCpService.getWxCpConfigStorage()).thenReturn(new WxCpDefaultConfigImpl());
+    Gson gson = new Gson();
+    int deptId = 2;
+    Mockito.when(mockCpService.get(String.format("%s?id=%s", mockCpService.getWxCpConfigStorage().getApiUrl(DEPARTMENT_LIST), deptId), null)).thenReturn(deptId2Json);
+    //WxCpDepartmentList没有重写Equals和Hashcode,不能直接比较
+    assertEquals(gson.toJson(wxCpSchoolUserService.listDepartment(deptId)), gson.toJson(gson.fromJson(deptId2Json, WxCpDepartmentList.class)), "should be equal");
+
+  }
+
+  @Test
+  public void testListDepartmentWhenIdIs3() throws WxErrorException {
+
+    WxCpService mockCpService = Mockito.mock(WxCpService.class);
+    WxCpSchoolUserServiceImpl wxCpSchoolUserService = new WxCpSchoolUserServiceImpl(mockCpService);
+    Mockito.when(mockCpService.getWxCpConfigStorage()).thenReturn(new WxCpDefaultConfigImpl());
+    Gson gson = new Gson();
+    int deptId = 3;
+    Mockito.when(mockCpService.get(String.format("%s?id=%s", mockCpService.getWxCpConfigStorage().getApiUrl(DEPARTMENT_LIST), deptId), null)).thenReturn(deptId3Json);
+    //WxCpDepartmentList没有重写Equals和Hashcode,不能直接比较
+    assertEquals(gson.toJson(wxCpSchoolUserService.listDepartment(deptId)), gson.toJson(gson.fromJson(deptId3Json, WxCpDepartmentList.class)), "should be equal");
+
+  }
+}
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/WxCpOaApplyEventRequestTest.java b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/bean/oa/WxCpOaApplyEventRequestTest.java
index f2dedb4ddf..e6fc2e8004 100644
--- a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/bean/oa/WxCpOaApplyEventRequestTest.java
+++ b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/bean/oa/WxCpOaApplyEventRequestTest.java
@@ -95,4 +95,99 @@ public void testToJson() {
 
     assertThat(request.toJson()).isEqualTo(GsonParser.parse(json).toString());
   }
+
+  /**
+   * Test to json with process.
+   */
+  @Test
+  public void testToJsonWithProcess() {
+    String json = "{\n" +
+      "    \"creator_userid\": \"WangXiaoMing\",\n" +
+      "    \"template_id\": \"3Tka1eD6v6JfzhDMqPd3aMkFdxqtJMc2ZRioeFXkaaa\",\n" +
+      "    \"use_template_approver\":0,\n" +
+      "    \"process\": {\n" +
+      "        \"node_list\": [\n" +
+      "            {\n" +
+      "                \"type\": 1,\n" +
+      "                \"apv_rel\": 2,\n" +
+      "                \"userid\": [\"WuJunJie\",\"WangXiaoMing\"]\n" +
+      "            },\n" +
+      "            {\n" +
+      "                \"type\": 1,\n" +
+      "                \"apv_rel\": 1,\n" +
+      "                \"userid\": [\"LiuXiaoGang\"]\n" +
+      "            },\n" +
+      "            {\n" +
+      "                \"type\": 2,\n" +
+      "                \"userid\": [\"ZhangSan\",\"LiSi\"]\n" +
+      "            }\n" +
+      "        ]\n" +
+      "    },\n" +
+      "    \"apply_data\": {\n" +
+      "         \"contents\": [\n" +
+      "                {\n" +
+      "                    \"control\": \"Text\",\n" +
+      "                    \"id\": \"Text-15111111111\",\n" +
+      "                    \"value\": {\n" +
+      "                        \"text\": \"文本填写的内容\"\n" +
+      "                    }\n" +
+      "                }\n" +
+      "            ]\n" +
+      "    },\n" +
+      "    \"summary_list\": [\n" +
+      "        {\n" +
+      "            \"summary_info\": [{\n" +
+      "                \"text\": \"摘要第1行\",\n" +
+      "                \"lang\": \"zh_CN\"\n" +
+      "            }]\n" +
+      "        },\n" +
+      "        {\n" +
+      "            \"summary_info\": [{\n" +
+      "                \"text\": \"摘要第2行\",\n" +
+      "                \"lang\": \"zh_CN\"\n" +
+      "            }]\n" +
+      "        },\n" +
+      "        {\n" +
+      "            \"summary_info\": [{\n" +
+      "                \"text\": \"摘要第3行\",\n" +
+      "                \"lang\": \"zh_CN\"\n" +
+      "            }]\n" +
+      "        }\n" +
+      "    ]\n" +
+      "}";
+
+    WxCpOaApplyEventRequest request = new WxCpOaApplyEventRequest();
+    request.setCreatorUserId("WangXiaoMing")
+      .setTemplateId("3Tka1eD6v6JfzhDMqPd3aMkFdxqtJMc2ZRioeFXkaaa")
+      .setUseTemplateApprover(0)
+      .setProcess(new WxCpOaApplyEventRequest.Process()
+        .setNodeList(Arrays.asList(
+          new WxCpOaApplyEventRequest.ProcessNode()
+            .setType(1)
+            .setApvRel(2)
+            .setUserIds(new String[]{"WuJunJie", "WangXiaoMing"}),
+          new WxCpOaApplyEventRequest.ProcessNode()
+            .setType(1)
+            .setApvRel(1)
+            .setUserIds(new String[]{"LiuXiaoGang"}),
+          new WxCpOaApplyEventRequest.ProcessNode()
+            .setType(2)
+            .setUserIds(new String[]{"ZhangSan", "LiSi"})
+        )))
+      .setApplyData(new WxCpOaApplyEventRequest.ApplyData()
+        .setContents(Collections.singletonList(new ApplyDataContent()
+          .setControl("Text").setId("Text-15111111111").setValue(new ContentValue().setText("文本填写的内容")))))
+      .setSummaryList(Arrays.asList(new SummaryInfo()
+          .setSummaryInfoData(Collections.singletonList(new SummaryInfo.SummaryInfoData().setLang("zh_CN").setText(
+            "摘要第1行"))),
+        new SummaryInfo()
+          .setSummaryInfoData(Collections.singletonList(new SummaryInfo.SummaryInfoData().setLang("zh_CN").setText(
+            "摘要第2行"))),
+        new SummaryInfo()
+          .setSummaryInfoData(Collections.singletonList(new SummaryInfo.SummaryInfoData().setLang("zh_CN").setText(
+            "摘要第3行")))))
+    ;
+
+    assertThat(request.toJson()).isEqualTo(GsonParser.parse(json).toString());
+  }
 }
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/demo/WxCpApprovalWorkflowDemo.java b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/demo/WxCpApprovalWorkflowDemo.java
new file mode 100644
index 0000000000..07aed001de
--- /dev/null
+++ b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/demo/WxCpApprovalWorkflowDemo.java
@@ -0,0 +1,190 @@
+package me.chanjar.weixin.cp.demo;
+
+import me.chanjar.weixin.cp.api.WxCpService;
+import me.chanjar.weixin.cp.bean.oa.WxCpApprovalDetailResult;
+import me.chanjar.weixin.cp.bean.oa.WxCpApprovalInfo;
+import me.chanjar.weixin.cp.bean.oa.WxCpOaApplyEventRequest;
+import me.chanjar.weixin.cp.bean.oa.WxCpOaApprovalTemplateResult;
+import me.chanjar.weixin.cp.bean.oa.applydata.ApplyDataContent;
+import me.chanjar.weixin.cp.bean.oa.applydata.ContentValue;
+
+import java.util.Arrays;
+import java.util.Date;
+
+/**
+ * 企业微信流程审批功能演示代码
+ * WeChat Enterprise Workflow Approval Demo
+ * 
+ * 演示如何使用WxJava SDK中已实现的完整审批流程功能
+ * Demonstrates how to use the comprehensive approval workflow features already implemented in WxJava SDK
+ * 
+ * 文档参考 Documentation Reference: https://work.weixin.qq.com/api/doc/90000/90135/91853
+ */
+public class WxCpApprovalWorkflowDemo {
+
+    private WxCpService wxCpService;
+
+    public WxCpApprovalWorkflowDemo(WxCpService wxCpService) {
+        this.wxCpService = wxCpService;
+    }
+
+    /**
+     * 示例1: 提交审批申请
+     * Example 1: Submit Approval Application
+     * API: /cgi-bin/oa/applyevent (Document 91853)
+     */
+    public String submitApprovalApplication() throws Exception {
+        // 构建审批申请请求
+        WxCpOaApplyEventRequest request = new WxCpOaApplyEventRequest()
+            .setCreatorUserId("creator_user_id")          // 申请人userid
+            .setTemplateId("3Tka1eD6v6JfzhDMqPd3aMkFdxqtJMc2ZRioUBGCNS") // 模板id
+            .setUseTemplateApprover(0)                    // 不使用模板中的审批流
+            .setApprovers(Arrays.asList(
+                new WxCpOaApplyEventRequest.Approver()
+                    .setAttr(2)                           // 审批类型: 或签
+                    .setUserIds(new String[]{"approver1", "approver2"})
+            ))
+            .setNotifiers(new String[]{"notifier1", "notifier2"}) // 抄送人
+            .setNotifyType(1)                             // 抄送方式: 提单时抄送
+            .setApplyData(new WxCpOaApplyEventRequest.ApplyData()
+                .setContents(Arrays.asList(
+                    // 文本控件
+                    new ApplyDataContent()
+                        .setControl("Text")
+                        .setId("Text-1234567890")
+                        .setValue(new ContentValue().setText("这是一个审批申请的文本内容")),
+                    
+                    // 数字控件
+                    new ApplyDataContent()
+                        .setControl("Number")
+                        .setId("Number-1234567890")
+                        .setValue(new ContentValue().setNewNumber("1000")),
+                    
+                    // 金额控件
+                    new ApplyDataContent()
+                        .setControl("Money")
+                        .setId("Money-1234567890")
+                        .setValue(new ContentValue().setNewMoney("10000"))
+                ))
+            );
+
+        // 提交审批申请
+        String spNo = wxCpService.getOaService().apply(request);
+        System.out.println("审批申请提交成功,审批单号: " + spNo);
+        
+        return spNo;
+    }
+
+    /**
+     * 示例2: 获取审批申请详情
+     * Example 2: Get Approval Application Details
+     * API: /cgi-bin/oa/getapprovaldetail
+     */
+    public void getApprovalDetails(String spNo) throws Exception {
+        WxCpApprovalDetailResult result = wxCpService.getOaService().getApprovalDetail(spNo);
+        
+        WxCpApprovalDetailResult.WxCpApprovalDetail detail = result.getInfo();
+        
+        System.out.println("审批单号: " + detail.getSpNo());
+        System.out.println("审批名称: " + detail.getSpName());
+        System.out.println("审批状态: " + detail.getSpStatus());
+        System.out.println("申请人: " + detail.getApplier().getUserId());
+        System.out.println("申请时间: " + detail.getApplyTime());
+        
+        // 打印审批记录
+        if (detail.getSpRecords() != null) {
+            Arrays.stream(detail.getSpRecords()).forEach(record -> {
+                System.out.println("审批节点状态: " + record.getStatus());
+                System.out.println("审批人: " + record.getDetails());
+            });
+        }
+    }
+
+    /**
+     * 示例3: 批量获取审批单号
+     * Example 3: Batch Get Approval Numbers
+     * API: /cgi-bin/oa/getapprovalinfo
+     */
+    public void batchGetApprovalInfo() throws Exception {
+        // 获取最近7天的审批单
+        Date startTime = new Date(System.currentTimeMillis() - 7 * 24 * 60 * 60 * 1000L);
+        Date endTime = new Date();
+        
+        WxCpApprovalInfo approvalInfo = wxCpService.getOaService()
+            .getApprovalInfo(startTime, endTime, "0", 100, null);
+        
+        System.out.println("获取到的审批单数量: " + (approvalInfo.getSpNoList() != null ? approvalInfo.getSpNoList().size() : 0));
+        
+        // 遍历审批单号
+        if (approvalInfo.getSpNoList() != null) {
+            approvalInfo.getSpNoList().forEach(spNo -> {
+                System.out.println("审批单号: " + spNo);
+                // 可以进一步调用 getApprovalDetails 获取详情
+            });
+        }
+    }
+
+    /**
+     * 示例4: 模板管理
+     * Example 4: Template Management
+     */
+    public void templateManagement() throws Exception {
+        // 获取模板详情
+        String templateId = "3Tka1eD6v6JfzhDMqPd3aMkFdxqtJMc2ZRioUBGCNS";
+        WxCpOaApprovalTemplateResult templateResult = wxCpService.getOaService().getTemplateDetail(templateId);
+        
+        System.out.println("模板名称: " + templateResult.getTemplateNames());
+        System.out.println("模板内容: " + templateResult.getTemplateContent());
+    }
+
+    /**
+     * 完整的审批流程演示
+     * Complete Approval Workflow Demo
+     */
+    public void completeWorkflowDemo() {
+        try {
+            System.out.println("=== 企业微信流程审批完整演示 ===");
+            
+            // 1. 提交审批申请
+            System.out.println("\n1. 提交审批申请...");
+            String spNo = submitApprovalApplication();
+            
+            // 2. 获取审批详情
+            System.out.println("\n2. 获取审批详情...");
+            getApprovalDetails(spNo);
+            
+            // 3. 批量获取审批信息
+            System.out.println("\n3. 批量获取审批信息...");
+            batchGetApprovalInfo();
+            
+            // 4. 模板管理
+            System.out.println("\n4. 模板管理...");
+            templateManagement();
+            
+            System.out.println("\n=== 演示完成 ===");
+            System.out.println("WxJava SDK 已经完整支持企业微信流程审批功能!");
+            
+        } catch (Exception e) {
+            System.err.println("演示过程中发生错误: " + e.getMessage());
+            e.printStackTrace();
+        }
+    }
+
+    public static void main(String[] args) {
+        // 注意: 这里需要配置真实的企业微信服务
+        // Note: You need to configure real WeChat Enterprise service here
+        
+        System.out.println("企业微信流程审批功能演示");
+        System.out.println("该演示代码展示了WxJava SDK中已经完整实现的审批流程功能");
+        System.out.println("包括文档91853中描述的所有核心功能");
+        System.out.println("");
+        System.out.println("主要功能:");
+        System.out.println("- 提交审批申请 (/cgi-bin/oa/applyevent)");
+        System.out.println("- 获取审批详情 (/cgi-bin/oa/getapprovaldetail)"); 
+        System.out.println("- 批量获取审批单号 (/cgi-bin/oa/getapprovalinfo)");
+        System.out.println("- 模板管理功能");
+        System.out.println("- 审批流程引擎支持");
+        System.out.println("");
+        System.out.println("如需运行演示,请配置正确的企业微信服务参数。");
+    }
+}
\ 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 a9bb5f37dc..ac308a4164 100644
--- a/weixin-java-miniapp/pom.xml
+++ b/weixin-java-miniapp/pom.xml
@@ -7,7 +7,7 @@
   
     com.github.binarywang
     wx-java
-    4.7.0
+    4.7.9.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/WxMaComplaintService.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaComplaintService.java new file mode 100644 index 0000000000..bd12f60850 --- /dev/null +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaComplaintService.java @@ -0,0 +1,159 @@ +package cn.binarywang.wx.miniapp.api; + +import cn.binarywang.wx.miniapp.bean.complaint.*; +import me.chanjar.weixin.common.error.WxErrorException; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; + +/** + * 小程序交易投诉接口 + * + * @author Binary Wang + * created on 2025-01-01 + */ +public interface WxMaComplaintService { + + /** + *
+   * 查询投诉单列表API
+   * 商户可通过调用此接口,查询指定时间段的所有用户投诉信息,以分页输出查询结果。
+   * 文档详见: https://developers.weixin.qq.com/miniprogram/dev/platform-capabilities/business-capabilities/guarantee/complaint.html
+   * 
+ * + * @param request {@link WxMaComplaintRequest} 查询投诉单列表请求数据 + * @return {@link WxMaComplaintResult} 微信返回的投诉单列表 + * @throws WxErrorException the wx error exception + */ + WxMaComplaintResult queryComplaints(WxMaComplaintRequest request) throws WxErrorException; + + /** + *
+   * 查询投诉单详情API
+   * 商户可通过调用此接口,查询指定投诉单的用户投诉详情,包含投诉内容、投诉关联订单、投诉人联系方式等信息,方便商户处理投诉。
+   * 文档详见: https://developers.weixin.qq.com/miniprogram/dev/platform-capabilities/business-capabilities/guarantee/complaint.html
+   * 
+ * + * @param request {@link WxMaComplaintDetailRequest} 投诉单详情请求数据 + * @return {@link WxMaComplaintDetailResult} 微信返回的投诉单详情 + * @throws WxErrorException the wx error exception + */ + WxMaComplaintDetailResult getComplaint(WxMaComplaintDetailRequest request) throws WxErrorException; + + /** + *
+   * 查询投诉协商历史API
+   * 商户可通过调用此接口,查询指定投诉的用户商户协商历史,以分页输出查询结果,方便商户根据处理历史来制定后续处理方案。
+   * 文档详见: https://developers.weixin.qq.com/miniprogram/dev/platform-capabilities/business-capabilities/guarantee/complaint.html
+   * 
+ * + * @param request {@link WxMaNegotiationHistoryRequest} 请求数据 + * @return {@link WxMaNegotiationHistoryResult} 微信返回结果 + * @throws WxErrorException the wx error exception + */ + WxMaNegotiationHistoryResult queryNegotiationHistorys(WxMaNegotiationHistoryRequest request) throws WxErrorException; + + /** + *
+   * 创建投诉通知回调地址API
+   * 商户通过调用此接口创建投诉通知回调URL,当用户产生新投诉且投诉状态已变更时,微信会通过回调URL通知商户。
+   * 文档详见: https://developers.weixin.qq.com/miniprogram/dev/platform-capabilities/business-capabilities/guarantee/complaint.html
+   * 
+ * + * @param request {@link WxMaComplaintNotifyUrlRequest} 请求数据 + * @return {@link WxMaComplaintNotifyUrlResult} 微信返回结果 + * @throws WxErrorException the wx error exception + */ + WxMaComplaintNotifyUrlResult addComplaintNotifyUrl(WxMaComplaintNotifyUrlRequest request) throws WxErrorException; + + /** + *
+   * 查询投诉通知回调地址API
+   * 商户通过调用此接口查询投诉通知的回调URL。
+   * 文档详见: https://developers.weixin.qq.com/miniprogram/dev/platform-capabilities/business-capabilities/guarantee/complaint.html
+   * 
+ * + * @return {@link WxMaComplaintNotifyUrlResult} 微信返回结果 + * @throws WxErrorException the wx error exception + */ + WxMaComplaintNotifyUrlResult getComplaintNotifyUrl() throws WxErrorException; + + /** + *
+   * 更新投诉通知回调地址API
+   * 商户通过调用此接口更新投诉通知的回调URL。
+   * 文档详见: https://developers.weixin.qq.com/miniprogram/dev/platform-capabilities/business-capabilities/guarantee/complaint.html
+   * 
+ * + * @param request {@link WxMaComplaintNotifyUrlRequest} 请求数据 + * @return {@link WxMaComplaintNotifyUrlResult} 微信返回结果 + * @throws WxErrorException the wx error exception + */ + WxMaComplaintNotifyUrlResult updateComplaintNotifyUrl(WxMaComplaintNotifyUrlRequest request) throws WxErrorException; + + /** + *
+   * 删除投诉通知回调地址API
+   * 当商户不再需要推送通知时,可通过调用此接口删除投诉通知的回调URL,取消通知回调。
+   * 文档详见: https://developers.weixin.qq.com/miniprogram/dev/platform-capabilities/business-capabilities/guarantee/complaint.html
+   * 
+ * + * @throws WxErrorException the wx error exception + */ + void deleteComplaintNotifyUrl() throws WxErrorException; + + /** + *
+   * 提交回复API
+   * 商户可通过调用此接口,提交回复内容。其中上传图片凭证需首先调用商户上传反馈图片接口,得到图片id,再将id填入请求。
+   * 回复可配置文字链,传入跳转链接文案和跳转链接字段,用户点击即可跳转对应页面
+   * 文档详见: https://developers.weixin.qq.com/miniprogram/dev/platform-capabilities/business-capabilities/guarantee/complaint.html
+   * 
+ * + * @param request {@link WxMaResponseRequest} 请求数据 + * @throws WxErrorException the wx error exception + */ + void submitResponse(WxMaResponseRequest request) throws WxErrorException; + + /** + *
+   * 反馈处理完成API
+   * 商户可通过调用此接口,反馈投诉单已处理完成。
+   * 文档详见: https://developers.weixin.qq.com/miniprogram/dev/platform-capabilities/business-capabilities/guarantee/complaint.html
+   * 
+ * + * @param request {@link WxMaCompleteRequest} 请求数据 + * @throws WxErrorException the wx error exception + */ + void complete(WxMaCompleteRequest request) throws WxErrorException; + + /** + *
+   * 商户上传反馈图片API
+   * 商户可通过调用此接口上传反馈图片凭证,上传成功后可在提交回复时使用。
+   * 文档详见: https://developers.weixin.qq.com/miniprogram/dev/platform-capabilities/business-capabilities/guarantee/complaint.html
+   * 
+ * + * @param imageFile 需要上传的图片文件 + * @return String 微信返回的媒体文件标识Id + * @throws WxErrorException the wx error exception + * @throws IOException IO异常 + */ + String uploadResponseImage(File imageFile) throws WxErrorException, IOException; + + /** + *
+   * 商户上传反馈图片API
+   * 商户可通过调用此接口上传反馈图片凭证,上传成功后可在提交回复时使用。
+   * 文档详见: https://developers.weixin.qq.com/miniprogram/dev/platform-capabilities/business-capabilities/guarantee/complaint.html
+   * 
+ * + * @param inputStream 需要上传的图片文件流 + * @param fileName 需要上传的图片文件名 + * @return String 微信返回的媒体文件标识Id + * @throws WxErrorException the wx error exception + * @throws IOException IO异常 + */ + String uploadResponseImage(InputStream inputStream, String fileName) throws WxErrorException, IOException; +} \ No newline at end of file diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaCustomserviceWorkService.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaCustomserviceWorkService.java new file mode 100644 index 0000000000..bf119bc596 --- /dev/null +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaCustomserviceWorkService.java @@ -0,0 +1,57 @@ +package cn.binarywang.wx.miniapp.api; + +import cn.binarywang.wx.miniapp.bean.customservice.WxMaCustomserviceResult; +import me.chanjar.weixin.common.error.WxErrorException; + + +/** + *
+ *  小程序 - 微信客服 相关接口
+ *  负责处理 https://api.weixin.qq.com/customservice/work/**
+ *  文档:https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/kf-work/getKfWorkBound.html
+ *  绑定的企业ID,需和小程序主体一致。
+ *  目前仅支持绑定非个人小程序。
+ *  Created by tryking123 on 2025/8/18.
+ * 
+ * + * @author tryking123 + */ +public interface WxMaCustomserviceWorkService { + + /** + * 查询小程序的微信客服绑定情况 + */ + String GET_CUSTOMSERVICE_URL = "https://api.weixin.qq.com/customservice/work/get"; + /** + * 为小程序绑定微信客服 注:此接口绑定的企业ID需完成企业认证 + */ + String BIND_CUSTOMSERVICE_URL = "https://api.weixin.qq.com/customservice/work/bind"; + /** + * 为小程序解除绑定微信客服 + */ + String UNBIND_CUSTOMSERVICE_URL = "https://api.weixin.qq.com/customservice/work/unbind"; + + /** + * 查询小程序的微信客服绑定情况 + * + * @return 成功示例json { "errcode": 0,"entityName": "XXXXX有限公司","corpid": "wwee11111xxxxxxx","bindTime": 1694611289 } + * @throws WxErrorException + */ + WxMaCustomserviceResult getCustomservice() throws WxErrorException; + + /** + * 绑定微信客服 + * @param corpid 企业ID,获取方式参考:https://developer.work.weixin.qq.com/document/path/90665#corpid + * @return 成功示例json { "errcode": 0 } + * @throws WxErrorException + */ + WxMaCustomserviceResult bindCustomservice(String corpid) throws WxErrorException; + + /** + * 解除绑定微信客服 + * @param corpid 企业ID,获取方式参考:https://developer.work.weixin.qq.com/document/path/90665#corpid + * @return 成功示例json { "errcode": 0 } + * @throws WxErrorException + */ + WxMaCustomserviceResult unbindCustomservice(String corpid) throws WxErrorException; +} diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaExpressDeliveryReturnService.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaExpressDeliveryReturnService.java index 4254d18dfe..6d950f6801 100644 --- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaExpressDeliveryReturnService.java +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaExpressDeliveryReturnService.java @@ -5,18 +5,47 @@ import me.chanjar.weixin.common.error.WxErrorException; /** - * 退货组件 + * 微信小程序物流退货组件接口。 + * 用于处理退货单相关操作,包括新增、查询和取消退货单。 + * 文档:https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/express/express.html + * */ public interface WxMaExpressDeliveryReturnService { - /** - * 获取支持的快递公司列表 - */ + /** 新增退货单接口地址 */ String ADD_DELIVERY_RETURN_URL = "https://api.weixin.qq.com/cgi-bin/express/delivery/return/add"; + /** 获取退货单接口地址 */ String GET_DELIVERY_RETURN_URL = "https://api.weixin.qq.com/cgi-bin/express/delivery/return/get"; + /** 取消退货单接口地址 */ String UNBIND_DELIVERY_RETURN_URL = "https://api.weixin.qq.com/cgi-bin/express/delivery/return/unbind"; + /** + * 新增退货单。 + * 用于创建新的退货单,返回退货单信息。 + * + * @param wxMaExpressDeliveryReturnAddRequest 退货单新增请求对象 + * @return 退货单信息结果 + * @throws WxErrorException 新增失败时抛出 + */ WxMaExpressReturnInfoResult addDeliveryReturn(WxMaExpressDeliveryReturnAddRequest wxMaExpressDeliveryReturnAddRequest) throws WxErrorException; + + /** + * 获取退货单信息。 + * 根据退货单ID查询退货单的详细信息。 + * + * @param returnId 退货单ID + * @return 退货单信息结果 + * @throws WxErrorException 获取失败时抛出 + */ WxMaExpressReturnInfoResult getDeliveryReturn(String returnId) throws WxErrorException; + + /** + * 取消退货单。 + * 取消指定的退货单,取消后的退货单将无法继续使用。 + * + * @param returnId 退货单ID + * @return 操作结果 + * @throws WxErrorException 取消失败时抛出 + */ WxMaExpressReturnInfoResult unbindDeliveryReturn(String returnId) throws WxErrorException; } diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaKefuService.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaKefuService.java new file mode 100644 index 0000000000..b8b5a03809 --- /dev/null +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaKefuService.java @@ -0,0 +1,128 @@ +package cn.binarywang.wx.miniapp.api; + +import cn.binarywang.wx.miniapp.bean.kefu.WxMaKfInfo; +import cn.binarywang.wx.miniapp.bean.kefu.WxMaKfList; +import cn.binarywang.wx.miniapp.bean.kefu.WxMaKfSession; +import cn.binarywang.wx.miniapp.bean.kefu.WxMaKfSessionList; +import cn.binarywang.wx.miniapp.bean.kefu.request.WxMaKfAccountRequest; +import me.chanjar.weixin.common.error.WxErrorException; + +/** + *
+ * 小程序客服管理接口.
+ * 不同于 WxMaCustomserviceWorkService (企业微信客服绑定) 和 WxMaMsgService.sendKefuMsg (发送客服消息),
+ * 此接口专门处理小程序客服账号管理、会话管理等功能。
+ * 
+ * 注意:小程序客服管理接口与公众号客服管理接口在API端点和功能上有所不同。
+ * 
+ * + * @author Binary Wang + */ +public interface WxMaKefuService { + + /** + *
+   * 获取客服基本信息
+   * 详情请见:获取客服基本信息
+   * 接口url格式:https://api.weixin.qq.com/cgi-bin/customservice/getkflist?access_token=ACCESS_TOKEN
+   * 
+ * + * @return 客服列表 + * @throws WxErrorException 异常 + */ + WxMaKfList kfList() throws WxErrorException; + + /** + *
+   * 添加客服账号
+   * 详情请见:添加客服账号
+   * 接口url格式:https://api.weixin.qq.com/customservice/kfaccount/add?access_token=ACCESS_TOKEN
+   * 
+ * + * @param request 客服账号信息 + * @return 是否成功 + * @throws WxErrorException 异常 + */ + boolean kfAccountAdd(WxMaKfAccountRequest request) throws WxErrorException; + + /** + *
+   * 修改客服账号
+   * 详情请见:修改客服账号
+   * 接口url格式:https://api.weixin.qq.com/customservice/kfaccount/update?access_token=ACCESS_TOKEN
+   * 
+ * + * @param request 客服账号信息 + * @return 是否成功 + * @throws WxErrorException 异常 + */ + boolean kfAccountUpdate(WxMaKfAccountRequest request) throws WxErrorException; + + /** + *
+   * 删除客服账号
+   * 详情请见:删除客服账号
+   * 接口url格式:https://api.weixin.qq.com/customservice/kfaccount/del?access_token=ACCESS_TOKEN&kf_account=KFACCOUNT
+   * 
+ * + * @param kfAccount 客服账号 + * @return 是否成功 + * @throws WxErrorException 异常 + */ + boolean kfAccountDel(String kfAccount) throws WxErrorException; + + /** + *
+   * 创建会话
+   * 详情请见:创建会话
+   * 接口url格式:https://api.weixin.qq.com/customservice/kfsession/create?access_token=ACCESS_TOKEN
+   * 
+ * + * @param openid 用户openid + * @param kfAccount 客服账号 + * @return 是否成功 + * @throws WxErrorException 异常 + */ + boolean kfSessionCreate(String openid, String kfAccount) throws WxErrorException; + + /** + *
+   * 关闭会话
+   * 详情请见:关闭会话
+   * 接口url格式:https://api.weixin.qq.com/customservice/kfsession/close?access_token=ACCESS_TOKEN
+   * 
+ * + * @param openid 用户openid + * @param kfAccount 客服账号 + * @return 是否成功 + * @throws WxErrorException 异常 + */ + boolean kfSessionClose(String openid, String kfAccount) throws WxErrorException; + + /** + *
+   * 获取客户的会话状态
+   * 详情请见:获取客户的会话状态
+   * 接口url格式:https://api.weixin.qq.com/customservice/kfsession/getsession?access_token=ACCESS_TOKEN&openid=OPENID
+   * 
+ * + * @param openid 用户openid + * @return 会话信息 + * @throws WxErrorException 异常 + */ + WxMaKfSession kfSessionGet(String openid) throws WxErrorException; + + /** + *
+   * 获取客服的会话列表
+   * 详情请见:获取客服的会话列表
+   * 接口url格式:https://api.weixin.qq.com/customservice/kfsession/getsessionlist?access_token=ACCESS_TOKEN&kf_account=KFACCOUNT
+   * 
+ * + * @param kfAccount 客服账号 + * @return 会话列表 + * @throws WxErrorException 异常 + */ + WxMaKfSessionList kfSessionList(String kfAccount) throws WxErrorException; + +} \ No newline at end of file diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaOrderManagementService.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaOrderManagementService.java new file mode 100644 index 0000000000..91980e9427 --- /dev/null +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaOrderManagementService.java @@ -0,0 +1,39 @@ +package cn.binarywang.wx.miniapp.api; + +import cn.binarywang.wx.miniapp.bean.order.WxMaOrderManagementGetOrderDetailPath; +import cn.binarywang.wx.miniapp.bean.order.WxMaOrderManagementResult; +import me.chanjar.weixin.common.error.WxErrorException; + +/** + * @author xzh + * @Description + * @createTime 2025/01/16 15:20 + */ +public interface WxMaOrderManagementService { + + /** + * 查询订单详情路径 + * 注意事项 + * 如果没有配置过订单详情路径,会返回成功,其中path为''。 + * + * @return WxMaOrderManagementGetOrderDetailPath + * @throws WxErrorException e + */ + WxMaOrderManagementGetOrderDetailPath getOrderDetailPath() + throws WxErrorException; + + + /** + * 配置订单详情路径 + * 注意事项 + * 调用接口前需要先完成订单中心授权协议签署。 + * 请确保配置的path可正常跳转到小程序,并且path必须包含字符串“${商品订单号}”。 + * + * @param path 订单详情路径 + * @return WxMaOrderManagementResult + * @throws WxErrorException e + */ + WxMaOrderManagementResult updateOrderDetailPath(String path) + throws 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 9d55df3797..26ced8bedd 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,557 +37,588 @@ 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(); + + /** + * 获取小程序客服管理服务。 * - * @return WxMaJsapiService jsapi service + * @return 客服管理服务对象WxMaKefuService + */ + WxMaKefuService getKefuService(); + + /** + * 获取jsapi操作相关服务对象。 + * + * @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(); /** - * 小程序openApi管理 + * 获取小程序订单管理服务对象。 * - * @return getWxMaOpenApiService + * @return 订单管理服务对象WxMaOrderManagementService + */ + WxMaOrderManagementService getWxMaOrderManagementService(); + + /** + * 获取小程序openApi管理服务对象。 + * + * @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(); + + /** + * 获取交易投诉服务对象。 + *
+ * 文档:https://developers.weixin.qq.com/miniprogram/dev/platform-capabilities/business-capabilities/guarantee/complaint.html + * + * @return 交易投诉服务对象WxMaComplaintService + */ + WxMaComplaintService getComplaintService(); } diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaShareService.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaShareService.java index 8c6030e53c..d32aee2c16 100644 --- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaShareService.java +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaShareService.java @@ -1,5 +1,6 @@ package cn.binarywang.wx.miniapp.api; +import cn.binarywang.wx.miniapp.bean.WxMaGroupEnterInfo; import cn.binarywang.wx.miniapp.bean.WxMaShareInfo; /** @@ -18,4 +19,16 @@ public interface WxMaShareService { */ WxMaShareInfo getShareInfo(String sessionKey, String encryptedData, String ivStr); + /** + * 解密群入口敏感数据. + * 对应 wx.getGroupEnterInfo 接口返回的 encryptedData 解密 + * + * @param sessionKey 会话密钥 + * @param encryptedData 消息密文 + * @param ivStr 加密算法的初始向量 + * @return 群入口信息 + * @see wx.getGroupEnterInfo 官方文档 + */ + WxMaGroupEnterInfo getGroupEnterInfo(String sessionKey, String encryptedData, String ivStr); + } diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaUserService.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaUserService.java index 8c6a8ef871..21696701d2 100644 --- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaUserService.java +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaUserService.java @@ -1,5 +1,6 @@ package cn.binarywang.wx.miniapp.api; +import cn.binarywang.wx.miniapp.bean.WxMaCode2VerifyInfoResult; import cn.binarywang.wx.miniapp.bean.WxMaJscode2SessionResult; import cn.binarywang.wx.miniapp.bean.WxMaPhoneNumberInfo; import cn.binarywang.wx.miniapp.bean.WxMaUserInfo; @@ -87,4 +88,18 @@ public interface WxMaUserService { * @return . */ boolean checkUserInfo(String sessionKey, String rawData, String signature); + + /** + * 多端登录验证接口. + *

+ * 通过 code 换取用户登录态信息,用于多端登录场景(如手表端)。 + *

+ * 文档地址:多端登录 + * + * @param code 登录时获取的 code + * @param checkcode 手表授权页面返回的 checkcode + * @return 登录验证结果,包含 session_key、openid、unionid 和 is_limit 字段 + * @throws WxErrorException 调用微信接口失败时抛出 + */ + WxMaCode2VerifyInfoResult getCode2VerifyInfo(String code, String checkcode) throws WxErrorException; } diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaVodService.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaVodService.java index 547d280962..217a699c5a 100644 --- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaVodService.java +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaVodService.java @@ -6,39 +6,181 @@ import java.io.File; import java.util.List; +/** + * 小程序短剧管理服务接口。 + * 提供短剧视频上传、管理、审核、播放等相关功能。 + * 文档:https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/vod.html + * + */ public interface WxMaVodService { + + /** + * 获取媒体列表。 + * 分页获取已上传的媒体文件列表。 + * + * @param request 获取媒体列表请求对象 + * @return 媒体信息列表 + * @throws WxErrorException 获取失败时抛出 + */ List 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 8af0626b92..93bb2656e6 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,8 @@ 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 WxMaKefuService maKefuService = new WxMaKefuServiceImpl(this); private final WxMaInternetService internetService = new WxMaInternetServiceImpl(this); private final WxMaSettingService settingService = new WxMaSettingServiceImpl(this); private final WxMaJsapiService jsapiService = new WxMaJsapiServiceImpl(this); @@ -153,6 +156,9 @@ public abstract class BaseWxMaServiceImpl implements WxMaService, RequestH private final WxMaOrderShippingService wxMaOrderShippingService = new WxMaOrderShippingServiceImpl(this); + private final WxMaOrderManagementService wxMaOrderManagementService = + new WxMaOrderManagementServiceImpl(this); + private final WxMaOpenApiService wxMaOpenApiService = new WxMaOpenApiServiceImpl(this); private final WxMaVodService wxMaVodService = new WxMaVodServiceImpl(this); private final WxMaXPayService wxMaXPayService = new WxMaXPayServiceImpl(this); @@ -160,13 +166,14 @@ public abstract class BaseWxMaServiceImpl implements WxMaService, RequestH new WxMaExpressDeliveryReturnServiceImpl(this); private final WxMaPromotionService wxMaPromotionService = new WxMaPromotionServiceImpl(this); private final WxMaIntracityService intracityService = new WxMaIntracityServiceImpl(this); + private final WxMaComplaintService complaintService = new WxMaComplaintServiceImpl(this); private Map configMap = new HashMap<>(); private int retrySleepMillis = 1000; private int maxRetryTimes = 5; @Override - public RequestHttp getRequestHttp() { + public RequestHttp getRequestHttp() { return this; } @@ -229,7 +236,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; } } @@ -294,7 +301,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 @@ -358,7 +365,7 @@ public R execute(RequestExecutor executor, String uri, T data) @Override public WxMaApiResponse execute( - ApiSignaturePostRequestExecutor executor, + ApiSignaturePostRequestExecutor executor, String uri, Map headers, String data) @@ -420,8 +427,9 @@ private R executeInternal( } String accessToken = getAccessToken(false); - if (StringUtils.isNotEmpty(this.getWxMaConfig().getApiHostUrl())) { - uri = uri.replace("https://api.weixin.qq.com", this.getWxMaConfig().getApiHostUrl()); + String effectiveApiHostUrl = this.getWxMaConfig().getEffectiveApiHostUrl(); + if (!WxMaConfig.DEFAULT_API_HOST_URL.equals(effectiveApiHostUrl)) { + uri = uri.replace(WxMaConfig.DEFAULT_API_HOST_URL, effectiveApiHostUrl); } String uriWithAccessToken = @@ -455,7 +463,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; @@ -499,7 +512,10 @@ public WxMaConfig getWxMaConfig() { @Override public void setWxMaConfig(WxMaConfig maConfig) { final String appid = maConfig.getAppid(); - this.setMultiConfigs(ImmutableMap.of(appid, maConfig), appid); + Map map = new HashMap<>(); + map.put(appid, maConfig); + Map configMap = Collections.unmodifiableMap(map); + this.setMultiConfigs(configMap, appid); } @Override @@ -639,6 +655,16 @@ public WxMaCodeService getCodeService() { return this.codeService; } + @Override + public WxMaCustomserviceWorkService getCustomserviceWorkService() { + return this.customserviceWorkService; + } + + @Override + public WxMaKefuService getKefuService() { + return this.maKefuService; + } + @Override public WxMaJsapiService getJsapiService() { return this.jsapiService; @@ -814,6 +840,16 @@ public WxMaOrderShippingService getWxMaOrderShippingService() { return this.wxMaOrderShippingService; } + /** + * 小程序订单管理服务 + * + * @return WxMaOrderManagementService + */ + @Override + public WxMaOrderManagementService getWxMaOrderManagementService() { + return this.wxMaOrderManagementService; + } + @Override public WxMaOpenApiService getWxMaOpenApiService() { return this.wxMaOpenApiService; @@ -876,6 +912,10 @@ public String postWithSignature(String url, JsonObject jsonObject) throws WxErro String rndStr = UUID.randomUUID().toString().replace("-", "").substring(0, 30); String aesKey = this.getWxMaConfig().getApiSignatureAesKey(); String aesKeySn = this.getWxMaConfig().getApiSignatureAesKeySn(); + String rsaKeySn = this.getWxMaConfig().getApiSignatureRsaPrivateKeySn(); + if (rsaKeySn == null || rsaKeySn.isEmpty()) { + throw new SecurityException("ApiSignatureRsaPrivateKeySn不能为空,请检查配置"); + } jsonObject.addProperty("_n", rndStr); jsonObject.addProperty("_appid", appId); @@ -920,7 +960,7 @@ public String postWithSignature(String url, JsonObject jsonObject) throws WxErro String requestJson = reqData.toString(); // 计算签名 RSA - String payload = urlPath + "\n" + appId + "\n" + timestamp + "\n" + requestJson; + String payload = urlPath + "\n" + appId + "\n" + timestamp + "\n" + rsaKeySn + "\n" + requestJson; byte[] dataBuffer = payload.getBytes(StandardCharsets.UTF_8); RSAPrivateKey priKey; try { @@ -949,6 +989,7 @@ public String postWithSignature(String url, JsonObject jsonObject) throws WxErro header.put("Wechatmp-Signature", signatureString); header.put("Wechatmp-Appid", appId); header.put("Wechatmp-TimeStamp", String.valueOf(timestamp)); + header.put("Wechatmp-Serial", rsaKeySn); log.debug("发送请求uri:{}, headers:{}, postData:{}", url, header, requestJson); WxMaApiResponse response = this.execute(ApiSignaturePostRequestExecutor.create(this), url, header, requestJson); @@ -1002,4 +1043,9 @@ private String aesDecodeResponse(WxMaApiResponse response, String aad, SecretKey public WxMaIntracityService getIntracityService() { return this.intracityService; } + + @Override + public WxMaComplaintService getComplaintService() { + return this.complaintService; + } } 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/WxMaComplaintServiceImpl.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaComplaintServiceImpl.java new file mode 100644 index 0000000000..c534bd6938 --- /dev/null +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaComplaintServiceImpl.java @@ -0,0 +1,99 @@ +package cn.binarywang.wx.miniapp.api.impl; + +import cn.binarywang.wx.miniapp.api.WxMaComplaintService; +import cn.binarywang.wx.miniapp.api.WxMaService; +import cn.binarywang.wx.miniapp.bean.complaint.*; +import cn.binarywang.wx.miniapp.json.WxMaGsonBuilder; +import com.google.gson.JsonObject; +import lombok.RequiredArgsConstructor; +import me.chanjar.weixin.common.bean.CommonUploadParam; +import me.chanjar.weixin.common.error.WxError; +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.common.util.fs.FileUtils; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.util.UUID; + +import static cn.binarywang.wx.miniapp.constant.WxMaApiUrlConstants.Complaint.*; + +/** + * 小程序交易投诉接口实现 + * + * @author Binary Wang + * created on 2025-01-01 + */ +@RequiredArgsConstructor +public class WxMaComplaintServiceImpl implements WxMaComplaintService { + private static final String JSON_CONTENT_TYPE = "application/json"; + private final WxMaService wxMaService; + + @Override + public WxMaComplaintResult queryComplaints(WxMaComplaintRequest request) throws WxErrorException { + String responseContent = this.wxMaService.post(QUERY_COMPLAINTS_URL, request.toJson()); + return WxMaGsonBuilder.create().fromJson(responseContent, WxMaComplaintResult.class); + } + + @Override + public WxMaComplaintDetailResult getComplaint(WxMaComplaintDetailRequest request) throws WxErrorException { + String responseContent = this.wxMaService.post(GET_COMPLAINT_URL, request.toJson()); + return WxMaGsonBuilder.create().fromJson(responseContent, WxMaComplaintDetailResult.class); + } + + @Override + public WxMaNegotiationHistoryResult queryNegotiationHistorys(WxMaNegotiationHistoryRequest request) throws WxErrorException { + String responseContent = this.wxMaService.post(QUERY_NEGOTIATION_HISTORY_URL, request.toJson()); + return WxMaGsonBuilder.create().fromJson(responseContent, WxMaNegotiationHistoryResult.class); + } + + @Override + public WxMaComplaintNotifyUrlResult addComplaintNotifyUrl(WxMaComplaintNotifyUrlRequest request) throws WxErrorException { + String responseContent = this.wxMaService.post(ADD_COMPLAINT_NOTIFY_URL, request.toJson()); + return WxMaGsonBuilder.create().fromJson(responseContent, WxMaComplaintNotifyUrlResult.class); + } + + @Override + public WxMaComplaintNotifyUrlResult getComplaintNotifyUrl() throws WxErrorException { + String responseContent = this.wxMaService.get(GET_COMPLAINT_NOTIFY_URL, null); + return WxMaGsonBuilder.create().fromJson(responseContent, WxMaComplaintNotifyUrlResult.class); + } + + @Override + public WxMaComplaintNotifyUrlResult updateComplaintNotifyUrl(WxMaComplaintNotifyUrlRequest request) throws WxErrorException { + String responseContent = this.wxMaService.post(UPDATE_COMPLAINT_NOTIFY_URL, request.toJson()); + return WxMaGsonBuilder.create().fromJson(responseContent, WxMaComplaintNotifyUrlResult.class); + } + + @Override + public void deleteComplaintNotifyUrl() throws WxErrorException { + this.wxMaService.post(DELETE_COMPLAINT_NOTIFY_URL, "{}"); + } + + @Override + public void submitResponse(WxMaResponseRequest request) throws WxErrorException { + this.wxMaService.post(SUBMIT_RESPONSE_URL, request.toJson()); + } + + @Override + public void complete(WxMaCompleteRequest request) throws WxErrorException { + this.wxMaService.post(COMPLETE_COMPLAINT_URL, request.toJson()); + } + + @Override + public String uploadResponseImage(File imageFile) throws WxErrorException, IOException { + String result = this.wxMaService.upload(UPLOAD_RESPONSE_IMAGE_URL, + CommonUploadParam.fromFile("image", imageFile)); + JsonObject jsonResult = WxMaGsonBuilder.create().fromJson(result, JsonObject.class); + return jsonResult.get("media_id").getAsString(); + } + + @Override + public String uploadResponseImage(InputStream inputStream, String fileName) throws WxErrorException, IOException { + try { + return this.uploadResponseImage(FileUtils.createTmpFile(inputStream, UUID.randomUUID().toString(), fileName)); + } catch (IOException e) { + throw new WxErrorException(WxError.builder().errorMsg(e.getMessage()).build(), e); + } + } +} \ No newline at end of file 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/WxMaExpressDeliveryReturnServiceImpl.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaExpressDeliveryReturnServiceImpl.java index aef8a2cad2..df4d9780c9 100644 --- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaExpressDeliveryReturnServiceImpl.java +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaExpressDeliveryReturnServiceImpl.java @@ -14,23 +14,23 @@ public class WxMaExpressDeliveryReturnServiceImpl implements WxMaExpressDelivery @Override public WxMaExpressReturnInfoResult addDeliveryReturn(WxMaExpressDeliveryReturnAddRequest wxMaExpressDeliveryReturnAddRequest) throws WxErrorException { - String result= this.service.get(ADD_DELIVERY_RETURN_URL,wxMaExpressDeliveryReturnAddRequest.toJson()); + String result = this.service.post(ADD_DELIVERY_RETURN_URL, wxMaExpressDeliveryReturnAddRequest.toJson()); return WxMaExpressReturnInfoResult.fromJson(result); } @Override public WxMaExpressReturnInfoResult getDeliveryReturn(String returnId) throws WxErrorException { JsonObject param = new JsonObject(); - param.addProperty("return_id",returnId); - String result= this.service.get(GET_DELIVERY_RETURN_URL,param.toString()); + param.addProperty("return_id", returnId); + String result = this.service.post(GET_DELIVERY_RETURN_URL, param); return WxMaExpressReturnInfoResult.fromJson(result); } @Override public WxMaExpressReturnInfoResult unbindDeliveryReturn(String returnId) throws WxErrorException { JsonObject param = new JsonObject(); - param.addProperty("return_id",returnId); - String result= this.service.get(UNBIND_DELIVERY_RETURN_URL,param.toString()); + param.addProperty("return_id", returnId); + String result = this.service.post(UNBIND_DELIVERY_RETURN_URL, param); return WxMaExpressReturnInfoResult.fromJson(result); } } 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/WxMaIntracityServiceImpl.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaIntracityServiceImpl.java index 3e21dab79f..e2681f7cc7 100644 --- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaIntracityServiceImpl.java +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaIntracityServiceImpl.java @@ -170,7 +170,7 @@ public WxMaStoreFlowResponse qu @Override public WxMaStoreBalance balanceQuery(String wxStoreId, String serviceTransId, PayMode payMode) throws WxErrorException { - if (wxStoreId == null && (payMode != null && payMode != PayMode.STORE)) { + if (wxStoreId == null && (payMode == null || payMode == PayMode.STORE)) { throw new IllegalArgumentException("payMode是PAY_MODE_STORE或null时,必须传递wxStoreId"); } Map request = new HashMap<>(); diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaKefuServiceImpl.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaKefuServiceImpl.java new file mode 100644 index 0000000000..2fa39603a0 --- /dev/null +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaKefuServiceImpl.java @@ -0,0 +1,92 @@ +package cn.binarywang.wx.miniapp.api.impl; + +import cn.binarywang.wx.miniapp.api.WxMaKefuService; +import cn.binarywang.wx.miniapp.api.WxMaService; +import cn.binarywang.wx.miniapp.bean.kefu.WxMaKfInfo; +import cn.binarywang.wx.miniapp.bean.kefu.WxMaKfList; +import cn.binarywang.wx.miniapp.bean.kefu.WxMaKfSession; +import cn.binarywang.wx.miniapp.bean.kefu.WxMaKfSessionList; +import cn.binarywang.wx.miniapp.bean.kefu.request.WxMaKfAccountRequest; +import cn.binarywang.wx.miniapp.bean.kefu.request.WxMaKfSessionRequest; +import lombok.RequiredArgsConstructor; +import me.chanjar.weixin.common.error.WxErrorException; + +/** + * 小程序客服管理服务实现. + * + * @author Binary Wang + */ +@RequiredArgsConstructor +public class WxMaKefuServiceImpl implements WxMaKefuService { + + // 小程序客服管理接口URL + private static final String KFLIST_GET_URL = "https://api.weixin.qq.com/cgi-bin/customservice/getkflist"; + private static final String KFACCOUNT_ADD_URL = "https://api.weixin.qq.com/customservice/kfaccount/add"; + private static final String KFACCOUNT_UPDATE_URL = "https://api.weixin.qq.com/customservice/kfaccount/update"; + private static final String KFACCOUNT_DEL_URL = "https://api.weixin.qq.com/customservice/kfaccount/del?kf_account=%s"; + private static final String KFSESSION_CREATE_URL = "https://api.weixin.qq.com/customservice/kfsession/create"; + private static final String KFSESSION_CLOSE_URL = "https://api.weixin.qq.com/customservice/kfsession/close"; + private static final String KFSESSION_GET_URL = "https://api.weixin.qq.com/customservice/kfsession/getsession?openid=%s"; + private static final String KFSESSION_LIST_URL = "https://api.weixin.qq.com/customservice/kfsession/getsessionlist?kf_account=%s"; + + private final WxMaService service; + + @Override + public WxMaKfList kfList() throws WxErrorException { + String responseContent = this.service.get(KFLIST_GET_URL, null); + return WxMaKfList.fromJson(responseContent); + } + + @Override + public boolean kfAccountAdd(WxMaKfAccountRequest request) throws WxErrorException { + String responseContent = this.service.post(KFACCOUNT_ADD_URL, request.toJson()); + return responseContent != null; + } + + @Override + public boolean kfAccountUpdate(WxMaKfAccountRequest request) throws WxErrorException { + String responseContent = this.service.post(KFACCOUNT_UPDATE_URL, request.toJson()); + return responseContent != null; + } + + @Override + public boolean kfAccountDel(String kfAccount) throws WxErrorException { + String url = String.format(KFACCOUNT_DEL_URL, kfAccount); + String responseContent = this.service.get(url, null); + return responseContent != null; + } + + @Override + public boolean kfSessionCreate(String openid, String kfAccount) throws WxErrorException { + WxMaKfSessionRequest request = WxMaKfSessionRequest.builder() + .kfAccount(kfAccount) + .openid(openid) + .build(); + String responseContent = this.service.post(KFSESSION_CREATE_URL, request.toJson()); + return responseContent != null; + } + + @Override + public boolean kfSessionClose(String openid, String kfAccount) throws WxErrorException { + WxMaKfSessionRequest request = WxMaKfSessionRequest.builder() + .kfAccount(kfAccount) + .openid(openid) + .build(); + String responseContent = this.service.post(KFSESSION_CLOSE_URL, request.toJson()); + return responseContent != null; + } + + @Override + public WxMaKfSession kfSessionGet(String openid) throws WxErrorException { + String url = String.format(KFSESSION_GET_URL, openid); + String responseContent = this.service.get(url, null); + return WxMaKfSession.fromJson(responseContent); + } + + @Override + public WxMaKfSessionList kfSessionList(String kfAccount) throws WxErrorException { + String url = String.format(KFSESSION_LIST_URL, kfAccount); + String responseContent = this.service.get(url, null); + return WxMaKfSessionList.fromJson(responseContent); + } +} \ No newline at end of file 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
new file mode 100644
index 0000000000..27d7c01487
--- /dev/null
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaOrderManagementServiceImpl.java
@@ -0,0 +1,71 @@
+package cn.binarywang.wx.miniapp.api.impl;
+
+import cn.binarywang.wx.miniapp.api.WxMaOrderManagementService;
+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.json.WxMaGsonBuilder;
+import com.google.gson.JsonObject;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import me.chanjar.weixin.common.api.WxConsts;
+import me.chanjar.weixin.common.enums.WxType;
+import me.chanjar.weixin.common.error.WxError;
+import me.chanjar.weixin.common.error.WxErrorException;
+import me.chanjar.weixin.common.util.json.GsonHelper;
+import me.chanjar.weixin.common.util.json.GsonParser;
+
+import static cn.binarywang.wx.miniapp.constant.WxMaApiUrlConstants.OrderManagement.*;
+
+
+/**
+ * @author xzh
+ * @Description
+ * @createTime 2025/01/16 15:31
+ */
+@Slf4j
+@RequiredArgsConstructor
+public class WxMaOrderManagementServiceImpl implements WxMaOrderManagementService {
+
+  private final WxMaService wxMaService;
+
+  /**
+   * 查询订单详情路径
+   * 注意事项
+   * 如果没有配置过订单详情路径,会返回成功,其中path为''。
+   *
+   * @return WxMaOrderManagementGetOrderDetailPath
+   * @throws WxErrorException e
+   */
+  @Override
+  public WxMaOrderManagementGetOrderDetailPath getOrderDetailPath() throws WxErrorException {
+    return request(GET_ORDER_DETAIL_PATH, new Object(), WxMaOrderManagementGetOrderDetailPath.class);
+
+  }
+
+  /**
+   * 配置订单详情路径
+   * 注意事项
+   * 调用接口前需要先完成订单中心授权协议签署。
+   * 请确保配置的path可正常跳转到小程序,并且path必须包含字符串“${商品订单号}”。
+   *
+   * @param path 订单详情路径
+   * @return WxMaOrderManagementResult
+   * @throws WxErrorException e
+   */
+  @Override
+  public WxMaOrderManagementResult updateOrderDetailPath(String path) throws WxErrorException {
+    JsonObject jsonObject = GsonHelper.buildJsonObject("path", path);
+    return request(UPDATE_ORDER_DETAIL_PATH, jsonObject, WxMaOrderManagementResult.class);
+
+  }
+
+  private  T request(String url, Object request, Class resultT) throws WxErrorException {
+    String responseContent = this.wxMaService.post(url, request);
+    JsonObject jsonObject = GsonParser.parse(responseContent);
+    if (jsonObject.get(WxConsts.ERR_CODE).getAsInt() != 0) {
+      throw new WxErrorException(WxError.fromJson(responseContent, WxType.MiniApp));
+    }
+    return WxMaGsonBuilder.create().fromJson(responseContent, resultT);
+  }
+}
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..73b4994347 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,78 +58,46 @@ public HttpHost getRequestHttpProxy() {
   }
 
   @Override
-  public HttpType getRequestType() {
-    return HttpType.APACHE_HTTP;
+  public HttpClientType getRequestType() {
+    return HttpClientType.APACHE_HTTP;
   }
 
   @Override
   protected String doGetAccessTokenRequest() throws IOException {
-
     String url = StringUtils.isNotEmpty(this.getWxMaConfig().getAccessTokenUrl()) ?
-      this.getWxMaConfig().getAccessTokenUrl() : StringUtils.isNotEmpty(this.getWxMaConfig().getApiHostUrl()) ?
-      WxMaService.GET_ACCESS_TOKEN_URL.replace("https://api.weixin.qq.com", this.getWxMaConfig().getApiHostUrl()) :
-      WxMaService.GET_ACCESS_TOKEN_URL;
-
+      this.getWxMaConfig().getAccessTokenUrl() :
+      WxMaService.GET_ACCESS_TOKEN_URL.replace(
+        WxMaConfig.DEFAULT_API_HOST_URL, this.getWxMaConfig().getEffectiveApiHostUrl());
 
     url = String.format(url, this.getWxMaConfig().getAppid(), this.getWxMaConfig().getSecret());
 
-    HttpGet httpGet = 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 = StringUtils.isNotEmpty(this.getWxMaConfig().getAccessTokenUrl()) ?
-      this.getWxMaConfig().getAccessTokenUrl() : StringUtils.isNotEmpty(this.getWxMaConfig().getApiHostUrl()) ?
-      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) {
-        }
-      }
+      this.getWxMaConfig().getAccessTokenUrl() :
+      GET_STABLE_ACCESS_TOKEN.replace(
+        WxMaConfig.DEFAULT_API_HOST_URL, this.getWxMaConfig().getEffectiveApiHostUrl());
+
+    HttpPost httpPost = new HttpPost(url);
+    if (this.getRequestHttpProxy() != null) {
+      RequestConfig config = RequestConfig.custom().setProxy(this.getRequestHttpProxy()).build();
+      httpPost.setConfig(config);
     }
+    WxMaStableAccessTokenRequest wxMaAccessTokenRequest = new WxMaStableAccessTokenRequest();
+    wxMaAccessTokenRequest.setAppid(this.getWxMaConfig().getAppid());
+    wxMaAccessTokenRequest.setSecret(this.getWxMaConfig().getSecret());
+    wxMaAccessTokenRequest.setGrantType("client_credential");
+    wxMaAccessTokenRequest.setForceRefresh(forceRefresh);
+    httpPost.setEntity(new StringEntity(wxMaAccessTokenRequest.toJson(), ContentType.APPLICATION_JSON));
+    return getRequestHttpClient().execute(httpPost, 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..94806121ab 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,16 +43,16 @@ public ProxyInfo getRequestHttpProxy() {
   }
 
   @Override
-  public HttpType getRequestType() {
-    return HttpType.JODD_HTTP;
+  public HttpClientType getRequestType() {
+    return HttpClientType.JODD_HTTP;
   }
 
   @Override
   protected String doGetAccessTokenRequest() throws IOException {
     String url = StringUtils.isNotEmpty(this.getWxMaConfig().getAccessTokenUrl()) ?
-      this.getWxMaConfig().getAccessTokenUrl() : StringUtils.isNotEmpty(this.getWxMaConfig().getApiHostUrl()) ?
-      WxMaService.GET_ACCESS_TOKEN_URL.replace("https://api.weixin.qq.com", this.getWxMaConfig().getApiHostUrl()) :
-      WxMaService.GET_ACCESS_TOKEN_URL;
+      this.getWxMaConfig().getAccessTokenUrl() :
+      WxMaService.GET_ACCESS_TOKEN_URL.replace(
+        WxMaConfig.DEFAULT_API_HOST_URL, this.getWxMaConfig().getEffectiveApiHostUrl());
 
     url = String.format(url, this.getWxMaConfig().getAppid(), this.getWxMaConfig().getSecret());
     HttpRequest request = HttpRequest.get(url);
@@ -67,11 +67,10 @@ protected String doGetAccessTokenRequest() throws IOException {
 
   @Override
   protected String doGetStableAccessTokenRequest(boolean forceRefresh) throws IOException {
-
     String url = StringUtils.isNotEmpty(this.getWxMaConfig().getAccessTokenUrl()) ?
-      this.getWxMaConfig().getAccessTokenUrl() : StringUtils.isNotEmpty(this.getWxMaConfig().getApiHostUrl()) ?
-      GET_STABLE_ACCESS_TOKEN.replace("https://api.weixin.qq.com", this.getWxMaConfig().getApiHostUrl()) :
-      GET_STABLE_ACCESS_TOKEN;
+      this.getWxMaConfig().getAccessTokenUrl() :
+      GET_STABLE_ACCESS_TOKEN.replace(
+        WxMaConfig.DEFAULT_API_HOST_URL, this.getWxMaConfig().getEffectiveApiHostUrl());
 
     WxMaStableAccessTokenRequest wxMaAccessTokenRequest = new WxMaStableAccessTokenRequest();
     wxMaAccessTokenRequest.setAppid(this.getWxMaConfig().getAppid());
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..096af76caa 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.*;
@@ -32,12 +32,12 @@ public void initHttp() {
       OkHttpClient.Builder clientBuilder = new OkHttpClient.Builder();
       clientBuilder.proxy(getRequestHttpProxy().getProxy());
       //设置授权
-      clientBuilder.authenticator(new Authenticator() {
+      clientBuilder.proxyAuthenticator(new Authenticator() {
         @Override
         public Request authenticate(Route route, Response response) throws IOException {
           String credential = Credentials.basic(httpProxy.getProxyUsername(), httpProxy.getProxyPassword());
           return response.request().newBuilder()
-            .header("Authorization", credential)
+            .header("Proxy-Authorization", credential)
             .build();
         }
       });
@@ -58,16 +58,16 @@ public OkHttpProxyInfo getRequestHttpProxy() {
   }
 
   @Override
-  public HttpType getRequestType() {
-    return HttpType.OK_HTTP;
+  public HttpClientType getRequestType() {
+    return HttpClientType.OK_HTTP;
   }
 
   @Override
   protected String doGetAccessTokenRequest() throws IOException {
     String url = StringUtils.isNotEmpty(this.getWxMaConfig().getAccessTokenUrl()) ?
-      this.getWxMaConfig().getAccessTokenUrl() : StringUtils.isNotEmpty(this.getWxMaConfig().getApiHostUrl()) ?
-      WxMaService.GET_ACCESS_TOKEN_URL.replace("https://api.weixin.qq.com", this.getWxMaConfig().getApiHostUrl()) :
-      WxMaService.GET_ACCESS_TOKEN_URL;
+      this.getWxMaConfig().getAccessTokenUrl() :
+      WxMaService.GET_ACCESS_TOKEN_URL.replace(
+        WxMaConfig.DEFAULT_API_HOST_URL, this.getWxMaConfig().getEffectiveApiHostUrl());
 
     url = String.format(url, this.getWxMaConfig().getAppid(), this.getWxMaConfig().getSecret());
     Request request = new Request.Builder().url(url).get().build();
@@ -79,9 +79,10 @@ protected String doGetAccessTokenRequest() throws IOException {
   @Override
   protected String doGetStableAccessTokenRequest(boolean forceRefresh) throws IOException {
     String url = StringUtils.isNotEmpty(this.getWxMaConfig().getAccessTokenUrl()) ?
-      this.getWxMaConfig().getAccessTokenUrl() : StringUtils.isNotEmpty(this.getWxMaConfig().getApiHostUrl()) ?
-      GET_STABLE_ACCESS_TOKEN.replace("https://api.weixin.qq.com", this.getWxMaConfig().getApiHostUrl()) :
-      GET_STABLE_ACCESS_TOKEN;
+      this.getWxMaConfig().getAccessTokenUrl() :
+      GET_STABLE_ACCESS_TOKEN.replace(
+        WxMaConfig.DEFAULT_API_HOST_URL, this.getWxMaConfig().getEffectiveApiHostUrl());
+
     WxMaStableAccessTokenRequest wxMaAccessTokenRequest = new WxMaStableAccessTokenRequest();
     wxMaAccessTokenRequest.setAppid(this.getWxMaConfig().getAppid());
     wxMaAccessTokenRequest.setSecret(this.getWxMaConfig().getSecret());
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaShareServiceImpl.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaShareServiceImpl.java
index fd1981aa03..a3a8e6176f 100644
--- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaShareServiceImpl.java
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaShareServiceImpl.java
@@ -2,6 +2,7 @@
 
 import cn.binarywang.wx.miniapp.api.WxMaService;
 import cn.binarywang.wx.miniapp.api.WxMaShareService;
+import cn.binarywang.wx.miniapp.bean.WxMaGroupEnterInfo;
 import cn.binarywang.wx.miniapp.bean.WxMaShareInfo;
 import cn.binarywang.wx.miniapp.util.crypt.WxMaCryptUtils;
 import lombok.RequiredArgsConstructor;
@@ -18,4 +19,9 @@ public WxMaShareInfo getShareInfo(String sessionKey, String encryptedData, Strin
     return WxMaShareInfo.fromJson(WxMaCryptUtils.decrypt(sessionKey, encryptedData, ivStr));
 
   }
+
+  @Override
+  public WxMaGroupEnterInfo getGroupEnterInfo(String sessionKey, String encryptedData, String ivStr) {
+    return WxMaGroupEnterInfo.fromJson(WxMaCryptUtils.decrypt(sessionKey, encryptedData, ivStr));
+  }
 }
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/WxMaUserServiceImpl.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaUserServiceImpl.java
index c9f5c2e335..5c850ee8a2 100644
--- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaUserServiceImpl.java
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaUserServiceImpl.java
@@ -2,6 +2,7 @@
 
 import cn.binarywang.wx.miniapp.api.WxMaService;
 import cn.binarywang.wx.miniapp.api.WxMaUserService;
+import cn.binarywang.wx.miniapp.bean.WxMaCode2VerifyInfoResult;
 import cn.binarywang.wx.miniapp.bean.WxMaJscode2SessionResult;
 import cn.binarywang.wx.miniapp.bean.WxMaPhoneNumberInfo;
 import cn.binarywang.wx.miniapp.bean.WxMaUserInfo;
@@ -18,6 +19,7 @@
 
 import java.util.Map;
 
+import static cn.binarywang.wx.miniapp.constant.WxMaApiUrlConstants.User.CODE_2_VERIFY_INFO_URL;
 import static cn.binarywang.wx.miniapp.constant.WxMaApiUrlConstants.User.GET_PHONE_NUMBER_URL;
 import static cn.binarywang.wx.miniapp.constant.WxMaApiUrlConstants.User.SET_USER_STORAGE;
 
@@ -86,4 +88,13 @@ public boolean checkUserInfo(String sessionKey, String rawData, String signature
     return generatedSignature.equals(signature);
   }
 
+  @Override
+  public WxMaCode2VerifyInfoResult getCode2VerifyInfo(String code, String checkcode) throws WxErrorException {
+    JsonObject param = new JsonObject();
+    param.addProperty("code", code);
+    param.addProperty("checkcode", checkcode);
+    String responseContent = this.service.post(CODE_2_VERIFY_INFO_URL, param.toString());
+    return WxMaCode2VerifyInfoResult.fromJson(responseContent);
+  }
+
 }
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/WxMaCode2VerifyInfoResult.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/WxMaCode2VerifyInfoResult.java
new file mode 100644
index 0000000000..b36a3a9d86
--- /dev/null
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/WxMaCode2VerifyInfoResult.java
@@ -0,0 +1,44 @@
+package cn.binarywang.wx.miniapp.bean;
+
+import cn.binarywang.wx.miniapp.json.WxMaGsonBuilder;
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.io.Serializable;
+
+/**
+ * 
+ * 多端登录验证接口的响应
+ * 文档地址:https://developers.weixin.qq.com/miniprogram/dev/platform-capabilities/miniapp/openapi/code2Verifyinfo.html
+ *
+ * 微信返回报文:{"errcode": 0, "errmsg": "ok", "session_key":"xxx", "openid":"xxx", "unionid":"xxx", "is_limit": false}
+ * 
+ * + * @author Binary Wang + */ +@Data +@EqualsAndHashCode(callSuper = false) +public class WxMaCode2VerifyInfoResult implements Serializable { + private static final long serialVersionUID = -2468325025088437364L; + + @SerializedName("session_key") + private String sessionKey; + + @SerializedName("openid") + private String openid; + + @SerializedName("unionid") + private String unionid; + + /** + * 是否为受限用户 + */ + @SerializedName("is_limit") + private Boolean isLimit; + + public static WxMaCode2VerifyInfoResult fromJson(String json) { + return WxMaGsonBuilder.create().fromJson(json, WxMaCode2VerifyInfoResult.class); + } + +} diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/WxMaGroupEnterInfo.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/WxMaGroupEnterInfo.java new file mode 100644 index 0000000000..e65ec602da --- /dev/null +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/WxMaGroupEnterInfo.java @@ -0,0 +1,46 @@ +package cn.binarywang.wx.miniapp.bean; + +import cn.binarywang.wx.miniapp.json.WxMaGsonBuilder; +import com.google.gson.annotations.SerializedName; +import lombok.Data; + +import java.io.Serializable; + +/** + * 微信小程序群入口信息. + * 对应 wx.getGroupEnterInfo 接口返回的解密数据 + * + * @see wx.getGroupEnterInfo 官方文档 + */ +@Data +public class WxMaGroupEnterInfo implements Serializable { + private static final long serialVersionUID = -8053613683499632227L; + + /** + * 多聊群下返回的群唯一标识. + */ + @SerializedName("opengid") + private String openGId; + + /** + * 单聊群下返回的群唯一标识. + */ + @SerializedName("open_single_roomid") + private String openSingleRoomid; + + /** + * 用户在当前群的唯一标识. + */ + @SerializedName("group_openid") + private String groupOpenid; + + /** + * 聊天室类型. + */ + @SerializedName("chat_type") + private Integer chatType; + + public static WxMaGroupEnterInfo fromJson(String json) { + return WxMaGsonBuilder.create().fromJson(json, WxMaGroupEnterInfo.class); + } +} diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/WxMaKefuMessage.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/WxMaKefuMessage.java index 0cd77c7937..cdce681175 100644 --- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/WxMaKefuMessage.java +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/WxMaKefuMessage.java @@ -40,6 +40,9 @@ public class WxMaKefuMessage implements Serializable { @SerializedName("miniprogrampage") private KfMaPage maPage; + @SerializedName("aimsgcontext") + private AiMsgContext aiMsgContext; + @Data @AllArgsConstructor @NoArgsConstructor @@ -90,6 +93,16 @@ public static class KfMaPage implements Serializable { private String thumbMediaId; } + @Data + @AllArgsConstructor + @NoArgsConstructor + public static class AiMsgContext implements Serializable { + private static final long serialVersionUID = 1L; + + @SerializedName("msgid") + private String msgId; + } + /** * 获得文本消息builder. */ diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/WxMaMessage.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/WxMaMessage.java index 7a004b845c..88450403e3 100644 --- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/WxMaMessage.java +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/WxMaMessage.java @@ -31,7 +31,7 @@ public class WxMaMessage implements Serializable { private static final long serialVersionUID = -3586245291677274914L; /** - * 使用dom4j解析的存放所有xml属性和值的map. + * 使用dom4j解析的存放所有xml或json属性和值的map. */ private Map allFieldsMap; @@ -212,6 +212,107 @@ public class WxMaMessage implements Serializable { @XStreamAlias("SubscribeMsgSentEvent") private WxMaSubscribeMsgEvent.SubscribeMsgSentEvent subscribeMsgSentEvent; + // 小程序基本信息 + + //region 小程序基本信息 infoType=notify_3rd_wxa_auth_and_icp + + /** + * 返回值 + */ + @XStreamAlias("ret") + private String ret; + + /** + * 一级类目id + */ + @XStreamAlias("first") + private String first; + + /** + * 二级类目id + */ + @XStreamAlias("second") + private String second; + + /** + * 驳回原因 + */ + @XStreamAlias("reason") + private String reason; + + /** + * 小程序代码审核驳回原因 + */ + @XStreamAlias("Reason") + private String weAppReason; + + /** + * 昵称 + */ + @XStreamAlias("nickname") + private String nickname; + + /** + * 原始通知内容 + */ + private String context; + + /** + * 微信支付订单号 + */ + @XStreamAlias("transaction_id") + private String transactionId; + /** + * 商户号 + */ + @XStreamAlias("merchant_id") + private String merchantId; + /** + * 子商户号 + */ + @XStreamAlias("sub_merchant_id") + private String subMerchantId; + /** + * 商户订单号 + */ + @XStreamAlias("merchant_trade_no") + private String merchantTradeNo; + /** + * 支付成功时间,秒级时间戳 + */ + @XStreamAlias("pay_time") + private Long payTime; + /** + * 消息文本内容 + */ + @XStreamAlias("msg") + private String msg; + /** + * 发货时间,秒级时间戳 + */ + @XStreamAlias("shipped_time") + private Long shippedTime; + /** + * 预计结算时间,秒级时间戳。发货时推送才有该字段 + */ + @XStreamAlias("estimated_settlement_time") + private Long estimatedSettlementTime; + /** + * 确认收货方式:1. 手动确认收货;2. 自动确认收货。结算时推送才有该字段 + */ + @XStreamAlias("confirm_receive_method") + private Integer confirmReceiveMethod; + /** + * 确认收货时间,秒级时间戳。结算时推送才有该字段 + */ + @XStreamAlias("confirm_receive_time") + private Long confirmReceiveTime; + /** + * 订单结算时间,秒级时间戳。结算时推送才有该字段 + */ + @XStreamAlias("settlement_time") + private Long settlementTime; + /** * 不要直接使用这个字段, * 这个字段只是为了适配 SubscribeMsgPopupEvent SubscribeMsgChangeEvent SubscribeMsgSentEvent @@ -261,7 +362,9 @@ public static WxMaMessage fromEncryptedXml(String encryptedXml, WxMaConfig wxMaConfig, String timestamp, String nonce, String msgSignature) { String plainText = new WxMaCryptUtils(wxMaConfig).decryptXml(msgSignature, timestamp, nonce, encryptedXml); - return fromXml(plainText); + WxMaMessage wxMaMessage = fromXml(plainText); + wxMaMessage.setContext(plainText); + return wxMaMessage; } public static WxMaMessage fromEncryptedXml(InputStream is, WxMaConfig wxMaConfig, String timestamp, @@ -287,6 +390,7 @@ public static WxMaMessage fromJson(String json) { } message.setUselessMsg(null); } + message.setAllFieldsMap(WxMaGsonBuilder.create().fromJson(json, Map.class)); return message; } diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/complaint/WxMaComplaintDetailRequest.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/complaint/WxMaComplaintDetailRequest.java new file mode 100644 index 0000000000..759f7392bf --- /dev/null +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/complaint/WxMaComplaintDetailRequest.java @@ -0,0 +1,38 @@ +package cn.binarywang.wx.miniapp.bean.complaint; + +import cn.binarywang.wx.miniapp.json.WxMaGsonBuilder; +import com.google.gson.annotations.SerializedName; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * 小程序投诉单详情请求实体 + * + * @author Binary Wang + * created on 2025-01-01 + */ +@Data +@Builder(builderMethodName = "newBuilder") +@NoArgsConstructor +@AllArgsConstructor +public class WxMaComplaintDetailRequest implements Serializable { + private static final long serialVersionUID = 3244929701614280806L; + + /** + *
+   * 字段名:投诉单号
+   * 是否必填:是
+   * 描述:投诉单对应的投诉单号
+   * 
+ */ + @SerializedName("complaint_id") + private String complaintId; + + public String toJson() { + return WxMaGsonBuilder.create().toJson(this); + } +} \ No newline at end of file diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/complaint/WxMaComplaintDetailResult.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/complaint/WxMaComplaintDetailResult.java new file mode 100644 index 0000000000..52a0be1704 --- /dev/null +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/complaint/WxMaComplaintDetailResult.java @@ -0,0 +1,166 @@ +package cn.binarywang.wx.miniapp.bean.complaint; + +import cn.binarywang.wx.miniapp.bean.WxMaBaseResponse; +import com.google.gson.annotations.SerializedName; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.io.Serializable; +import java.util.List; + +/** + * 小程序投诉单详情结果 + * + * @author Binary Wang + * created on 2025-01-01 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class WxMaComplaintDetailResult extends WxMaBaseResponse { + + /** + *
+   * 字段名:投诉单号
+   * 是否必填:是
+   * 描述:投诉单对应的投诉单号
+   * 
+ */ + @SerializedName("complaint_id") + private String complaintId; + + /** + *
+   * 字段名:投诉时间
+   * 是否必填:是
+   * 描述:用户提交投诉的时间,遵循rfc3339标准格式,格式为YYYY-MM-DDTHH:mm:ss+TIMEZONE
+   * 
+ */ + @SerializedName("complaint_time") + private String complaintTime; + + /** + *
+   * 字段名:投诉详情
+   * 是否必填:是
+   * 描述:用户提交的投诉详情
+   * 
+ */ + @SerializedName("complaint_detail") + private String complaintDetail; + + /** + *
+   * 字段名:投诉状态
+   * 是否必填:是
+   * 描述:投诉单状态:WAITING_MERCHANT_RESPONSE-等待商户回复 MERCHANT_RESPONSED-商户已回复 WAITING_USER_RESPONSE-等待用户回复 USER_RESPONSED-用户已回复 COMPLAINT_COMPLETED-投诉已完成
+   * 
+ */ + @SerializedName("complaint_state") + private String complaintState; + + /** + *
+   * 字段名:投诉人openid
+   * 是否必填:是
+   * 描述:投诉人在小程序的openid
+   * 
+ */ + @SerializedName("openid") + private String openid; + + /** + *
+   * 字段名:投诉人联系方式
+   * 是否必填:否
+   * 描述:投诉人联系方式,可能为空
+   * 
+ */ + @SerializedName("phone_number") + private String phoneNumber; + + /** + *
+   * 字段名:被投诉订单信息
+   * 是否必填:是
+   * 描述:被投诉的订单信息
+   * 
+ */ + @SerializedName("complaint_order_info") + private ComplaintOrderInfo complaintOrderInfo; + + /** + *
+   * 字段名:投诉材料
+   * 是否必填:否
+   * 描述:用户上传的投诉相关的图片凭证
+   * 
+ */ + @SerializedName("complaint_media_list") + private List complaintMediaList; + + /** + * 被投诉订单信息 + */ + @Data + public static class ComplaintOrderInfo implements Serializable { + private static final long serialVersionUID = 3244929701614280806L; + + /** + *
+     * 字段名:商户订单号
+     * 是否必填:是
+     * 描述:商户系统内部订单号
+     * 
+ */ + @SerializedName("transaction_id") + private String transactionId; + + /** + *
+     * 字段名:微信订单号
+     * 是否必填:是
+     * 描述:微信支付系统生成的订单号
+     * 
+ */ + @SerializedName("out_trade_no") + private String outTradeNo; + + /** + *
+     * 字段名:订单金额
+     * 是否必填:是
+     * 描述:订单金额,单位为分
+     * 
+ */ + @SerializedName("amount") + private Integer amount; + } + + /** + * 投诉材料 + */ + @Data + public static class ComplaintMedia implements Serializable { + private static final long serialVersionUID = 3244929701614280806L; + + /** + *
+     * 字段名:媒体文件业务类型
+     * 是否必填:是
+     * 描述:媒体文件对应的业务类型:USER_COMPLAINT_IMAGE-用户投诉图片
+     * 
+ */ + @SerializedName("media_type") + private String mediaType; + + /** + *
+     * 字段名:媒体文件请求URL
+     * 是否必填:是
+     * 描述:微信返回的媒体文件请求URL,通过该URL可以获取到对应的媒体文件(图片、视频等)
+     * 
+ */ + @SerializedName("media_url") + private String mediaUrl; + } +} \ No newline at end of file diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/complaint/WxMaComplaintNotifyUrlRequest.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/complaint/WxMaComplaintNotifyUrlRequest.java new file mode 100644 index 0000000000..6af338c974 --- /dev/null +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/complaint/WxMaComplaintNotifyUrlRequest.java @@ -0,0 +1,38 @@ +package cn.binarywang.wx.miniapp.bean.complaint; + +import cn.binarywang.wx.miniapp.json.WxMaGsonBuilder; +import com.google.gson.annotations.SerializedName; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * 小程序投诉通知回调URL请求实体 + * + * @author Binary Wang + * created on 2025-01-01 + */ +@Data +@Builder(builderMethodName = "newBuilder") +@NoArgsConstructor +@AllArgsConstructor +public class WxMaComplaintNotifyUrlRequest implements Serializable { + private static final long serialVersionUID = 3244929701614280806L; + + /** + *
+   * 字段名:通知地址
+   * 是否必填:是
+   * 描述:通知地址,仅支持https
+   * 
+ */ + @SerializedName("url") + private String url; + + public String toJson() { + return WxMaGsonBuilder.create().toJson(this); + } +} \ No newline at end of file diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/complaint/WxMaComplaintNotifyUrlResult.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/complaint/WxMaComplaintNotifyUrlResult.java new file mode 100644 index 0000000000..7771998b80 --- /dev/null +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/complaint/WxMaComplaintNotifyUrlResult.java @@ -0,0 +1,37 @@ +package cn.binarywang.wx.miniapp.bean.complaint; + +import cn.binarywang.wx.miniapp.bean.WxMaBaseResponse; +import com.google.gson.annotations.SerializedName; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 小程序投诉通知回调URL结果 + * + * @author Binary Wang + * created on 2025-01-01 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class WxMaComplaintNotifyUrlResult extends WxMaBaseResponse { + + /** + *
+   * 字段名:通知地址
+   * 是否必填:是
+   * 描述:通知地址,仅支持https
+   * 
+ */ + @SerializedName("url") + private String url; + + /** + *
+   * 字段名:签名串
+   * 是否必填:是
+   * 描述:用于验证通知消息的签名串
+   * 
+ */ + @SerializedName("signature") + private String signature; +} \ No newline at end of file diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/complaint/WxMaComplaintRequest.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/complaint/WxMaComplaintRequest.java new file mode 100644 index 0000000000..9ac45e0c12 --- /dev/null +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/complaint/WxMaComplaintRequest.java @@ -0,0 +1,70 @@ +package cn.binarywang.wx.miniapp.bean.complaint; + +import cn.binarywang.wx.miniapp.json.WxMaGsonBuilder; +import com.google.gson.annotations.SerializedName; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * 小程序交易投诉查询请求实体 + * + * @author Binary Wang + * created on 2025-01-01 + */ +@Data +@Builder(builderMethodName = "newBuilder") +@NoArgsConstructor +@AllArgsConstructor +public class WxMaComplaintRequest implements Serializable { + private static final long serialVersionUID = 3244929701614280806L; + + /** + *
+   * 字段名:开始日期
+   * 是否必填:是
+   * 描述:查询的起始时间,格式为YYYY-MM-DD,例如2021-01-01
+   * 
+ */ + @SerializedName("begin_date") + private String beginDate; + + /** + *
+   * 字段名:结束日期
+   * 是否必填:是
+   * 描述:查询的结束时间,格式为YYYY-MM-DD,例如2021-01-31
+   * 
+ */ + @SerializedName("end_date") + private String endDate; + + /** + *
+   * 字段名:分页大小
+   * 是否必填:否
+   * 描述:单次拉取条目,最大为50,不传默认为10
+   * 
+ */ + @SerializedName("limit") + @Builder.Default + private Integer limit = 10; + + /** + *
+   * 字段名:分页开始位置
+   * 是否必填:否
+   * 描述:该次请求的分页开始位置,从0开始计数,例如offset=10,表示从第11条记录开始返回,不传默认为0
+   * 
+ */ + @SerializedName("offset") + @Builder.Default + private Integer offset = 0; + + public String toJson() { + return WxMaGsonBuilder.create().toJson(this); + } +} \ No newline at end of file diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/complaint/WxMaComplaintResult.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/complaint/WxMaComplaintResult.java new file mode 100644 index 0000000000..0e4208fdc1 --- /dev/null +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/complaint/WxMaComplaintResult.java @@ -0,0 +1,156 @@ +package cn.binarywang.wx.miniapp.bean.complaint; + +import cn.binarywang.wx.miniapp.bean.WxMaBaseResponse; +import com.google.gson.annotations.SerializedName; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.io.Serializable; +import java.util.List; + +/** + * 小程序交易投诉查询结果 + * + * @author Binary Wang + * created on 2025-01-01 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class WxMaComplaintResult extends WxMaBaseResponse { + + /** + *
+   * 字段名:投诉单信息
+   * 是否必填:是
+   * 描述:查询返回的投诉单信息
+   * 
+ */ + @SerializedName("data") + private List data; + + /** + *
+   * 字段名:总数
+   * 是否必填:是
+   * 描述:总投诉单条数,用于分页展示
+   * 
+ */ + @SerializedName("total_count") + private Integer totalCount; + + /** + * 投诉单信息 + */ + @Data + public static class Complaint implements Serializable { + private static final long serialVersionUID = 3244929701614280806L; + + /** + *
+     * 字段名:投诉单号
+     * 是否必填:是
+     * 描述:投诉单对应的投诉单号
+     * 
+ */ + @SerializedName("complaint_id") + private String complaintId; + + /** + *
+     * 字段名:投诉时间
+     * 是否必填:是
+     * 描述:用户提交投诉的时间,遵循rfc3339标准格式,格式为YYYY-MM-DDTHH:mm:ss+TIMEZONE
+     * 
+ */ + @SerializedName("complaint_time") + private String complaintTime; + + /** + *
+     * 字段名:投诉详情
+     * 是否必填:是
+     * 描述:用户提交的投诉详情
+     * 
+ */ + @SerializedName("complaint_detail") + private String complaintDetail; + + /** + *
+     * 字段名:投诉状态
+     * 是否必填:是
+     * 描述:投诉单状态:WAITING_MERCHANT_RESPONSE-等待商户回复 MERCHANT_RESPONSED-商户已回复 WAITING_USER_RESPONSE-等待用户回复 USER_RESPONSED-用户已回复 COMPLAINT_COMPLETED-投诉已完成
+     * 
+ */ + @SerializedName("complaint_state") + private String complaintState; + + /** + *
+     * 字段名:投诉人openid
+     * 是否必填:是
+     * 描述:投诉人在小程序的openid
+     * 
+ */ + @SerializedName("openid") + private String openid; + + /** + *
+     * 字段名:投诉人联系方式
+     * 是否必填:否
+     * 描述:投诉人联系方式,可能为空
+     * 
+ */ + @SerializedName("phone_number") + private String phoneNumber; + + /** + *
+     * 字段名:被投诉订单信息
+     * 是否必填:是
+     * 描述:被投诉的订单信息
+     * 
+ */ + @SerializedName("complaint_order_info") + private ComplaintOrderInfo complaintOrderInfo; + } + + /** + * 被投诉订单信息 + */ + @Data + public static class ComplaintOrderInfo implements Serializable { + private static final long serialVersionUID = 3244929701614280806L; + + /** + *
+     * 字段名:商户订单号
+     * 是否必填:是
+     * 描述:商户系统内部订单号
+     * 
+ */ + @SerializedName("transaction_id") + private String transactionId; + + /** + *
+     * 字段名:微信订单号
+     * 是否必填:是
+     * 描述:微信支付系统生成的订单号
+     * 
+ */ + @SerializedName("out_trade_no") + private String outTradeNo; + + /** + *
+     * 字段名:订单金额
+     * 是否必填:是
+     * 描述:订单金额,单位为分
+     * 
+ */ + @SerializedName("amount") + private Integer amount; + } +} \ No newline at end of file diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/complaint/WxMaCompleteRequest.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/complaint/WxMaCompleteRequest.java new file mode 100644 index 0000000000..71d1066024 --- /dev/null +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/complaint/WxMaCompleteRequest.java @@ -0,0 +1,38 @@ +package cn.binarywang.wx.miniapp.bean.complaint; + +import cn.binarywang.wx.miniapp.json.WxMaGsonBuilder; +import com.google.gson.annotations.SerializedName; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * 小程序反馈处理完成请求实体 + * + * @author Binary Wang + * created on 2025-01-01 + */ +@Data +@Builder(builderMethodName = "newBuilder") +@NoArgsConstructor +@AllArgsConstructor +public class WxMaCompleteRequest implements Serializable { + private static final long serialVersionUID = 3244929701614280806L; + + /** + *
+   * 字段名:投诉单号
+   * 是否必填:是
+   * 描述:投诉单对应的投诉单号
+   * 
+ */ + @SerializedName("complaint_id") + private String complaintId; + + public String toJson() { + return WxMaGsonBuilder.create().toJson(this); + } +} \ No newline at end of file diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/complaint/WxMaNegotiationHistoryRequest.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/complaint/WxMaNegotiationHistoryRequest.java new file mode 100644 index 0000000000..a03742b6af --- /dev/null +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/complaint/WxMaNegotiationHistoryRequest.java @@ -0,0 +1,60 @@ +package cn.binarywang.wx.miniapp.bean.complaint; + +import cn.binarywang.wx.miniapp.json.WxMaGsonBuilder; +import com.google.gson.annotations.SerializedName; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * 小程序查询投诉协商历史请求实体 + * + * @author Binary Wang + * created on 2025-01-01 + */ +@Data +@Builder(builderMethodName = "newBuilder") +@NoArgsConstructor +@AllArgsConstructor +public class WxMaNegotiationHistoryRequest implements Serializable { + private static final long serialVersionUID = 3244929701614280806L; + + /** + *
+   * 字段名:投诉单号
+   * 是否必填:是
+   * 描述:投诉单对应的投诉单号
+   * 
+ */ + @SerializedName("complaint_id") + private String complaintId; + + /** + *
+   * 字段名:分页大小
+   * 是否必填:否
+   * 描述:单次拉取条目,最大为50,不传默认为10
+   * 
+ */ + @SerializedName("limit") + @Builder.Default + private Integer limit = 10; + + /** + *
+   * 字段名:分页开始位置
+   * 是否必填:否
+   * 描述:该次请求的分页开始位置,从0开始计数,例如offset=10,表示从第11条记录开始返回,不传默认为0
+   * 
+ */ + @SerializedName("offset") + @Builder.Default + private Integer offset = 0; + + public String toJson() { + return WxMaGsonBuilder.create().toJson(this); + } +} \ No newline at end of file diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/complaint/WxMaNegotiationHistoryResult.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/complaint/WxMaNegotiationHistoryResult.java new file mode 100644 index 0000000000..194daca9ff --- /dev/null +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/complaint/WxMaNegotiationHistoryResult.java @@ -0,0 +1,88 @@ +package cn.binarywang.wx.miniapp.bean.complaint; + +import cn.binarywang.wx.miniapp.bean.WxMaBaseResponse; +import com.google.gson.annotations.SerializedName; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.io.Serializable; +import java.util.List; + +/** + * 小程序查询投诉协商历史结果 + * + * @author Binary Wang + * created on 2025-01-01 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class WxMaNegotiationHistoryResult extends WxMaBaseResponse { + + /** + *
+   * 字段名:协商历史
+   * 是否必填:是
+   * 描述:协商历史记录
+   * 
+ */ + @SerializedName("data") + private List data; + + /** + *
+   * 字段名:总数
+   * 是否必填:是
+   * 描述:总协商历史条数,用于分页展示
+   * 
+ */ + @SerializedName("total_count") + private Integer totalCount; + + /** + * 协商历史 + */ + @Data + public static class NegotiationHistory implements Serializable { + private static final long serialVersionUID = 3244929701614280806L; + + /** + *
+     * 字段名:操作时间
+     * 是否必填:是
+     * 描述:操作时间,遵循rfc3339标准格式,格式为YYYY-MM-DDTHH:mm:ss+TIMEZONE
+     * 
+ */ + @SerializedName("operate_time") + private String operateTime; + + /** + *
+     * 字段名:操作类型
+     * 是否必填:是
+     * 描述:操作类型:USER_CREATE_COMPLAINT-用户提交投诉 USER_CONTINUE_COMPLAINT-用户继续投诉 MERCHANT_RESPONSE-商户回复 MERCHANT_CONFIRM_COMPLETE-商户确认完成处理
+     * 
+ */ + @SerializedName("operate_type") + private String operateType; + + /** + *
+     * 字段名:操作内容
+     * 是否必填:是
+     * 描述:具体的操作内容
+     * 
+ */ + @SerializedName("operate_details") + private String operateDetails; + + /** + *
+     * 字段名:图片凭证
+     * 是否必填:否
+     * 描述:操作过程中上传的图片凭证
+     * 
+ */ + @SerializedName("image_list") + private List imageList; + } +} \ No newline at end of file diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/complaint/WxMaResponseRequest.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/complaint/WxMaResponseRequest.java new file mode 100644 index 0000000000..b4033c4f31 --- /dev/null +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/complaint/WxMaResponseRequest.java @@ -0,0 +1,79 @@ +package cn.binarywang.wx.miniapp.bean.complaint; + +import cn.binarywang.wx.miniapp.json.WxMaGsonBuilder; +import com.google.gson.annotations.SerializedName; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; +import java.util.List; + +/** + * 小程序提交回复请求实体 + * + * @author Binary Wang + * created on 2025-01-01 + */ +@Data +@Builder(builderMethodName = "newBuilder") +@NoArgsConstructor +@AllArgsConstructor +public class WxMaResponseRequest implements Serializable { + private static final long serialVersionUID = 3244929701614280806L; + + /** + *
+   * 字段名:投诉单号
+   * 是否必填:是
+   * 描述:投诉单对应的投诉单号
+   * 
+ */ + @SerializedName("complaint_id") + private String complaintId; + + /** + *
+   * 字段名:回复内容
+   * 是否必填:是
+   * 描述:具体的回复内容,长度不超过200字符
+   * 
+ */ + @SerializedName("response_content") + private String responseContent; + + /** + *
+   * 字段名:回复图片
+   * 是否必填:否
+   * 描述:回复的图片凭证,最多可传5张图片,由图片上传接口返回
+   * 
+ */ + @SerializedName("response_images") + private List responseImages; + + /** + *
+   * 字段名:跳转链接
+   * 是否必填:否
+   * 描述:点击跳转链接
+   * 
+ */ + @SerializedName("jump_url") + private String jumpUrl; + + /** + *
+   * 字段名:跳转链接文案
+   * 是否必填:否
+   * 描述:跳转链接文案,在response_content中展示的跳转链接文案,长度不超过10个字符
+   * 
+ */ + @SerializedName("jump_url_text") + private String jumpUrlText; + + public String toJson() { + return WxMaGsonBuilder.create().toJson(this); + } +} \ No newline at end of file diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/customservice/WxMaCustomserviceResult.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/customservice/WxMaCustomserviceResult.java new file mode 100644 index 0000000000..e7a9a46de3 --- /dev/null +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/customservice/WxMaCustomserviceResult.java @@ -0,0 +1,56 @@ +package cn.binarywang.wx.miniapp.bean.customservice; + +import cn.binarywang.wx.miniapp.json.WxMaGsonBuilder; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.google.gson.annotations.SerializedName; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; +import java.util.List; + +/** + * 客服绑定结果信息,包括错误码、主体名称、企业ID和绑定时间戳。 + *

+ * 字段说明: + *

    + *
  • 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/express/request/WxMaExpressOrderCargo.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/express/request/WxMaExpressOrderCargo.java index 96817a2256..b6de21b9e6 100644 --- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/express/request/WxMaExpressOrderCargo.java +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/express/request/WxMaExpressOrderCargo.java @@ -34,7 +34,7 @@ public class WxMaExpressOrderCargo implements Serializable { * 描述: 单位是千克(kg) *
*/ - private Integer weight; + private Double weight; /** * 包裹长度 @@ -44,7 +44,7 @@ public class WxMaExpressOrderCargo implements Serializable { *
*/ @SerializedName("space_x") - private Integer spaceLength; + private Double spaceLength; /** * 包裹宽度 @@ -54,7 +54,7 @@ public class WxMaExpressOrderCargo implements Serializable { *
*/ @SerializedName("space_y") - private Integer spaceWidth; + private Double spaceWidth; /** * 包裹高度 @@ -64,7 +64,7 @@ public class WxMaExpressOrderCargo implements Serializable { *
*/ @SerializedName("space_z") - private Integer spaceHeight; + private Double spaceHeight; /** * 包裹中商品详情列表 diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/kefu/WxMaKfInfo.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/kefu/WxMaKfInfo.java new file mode 100644 index 0000000000..476056d518 --- /dev/null +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/kefu/WxMaKfInfo.java @@ -0,0 +1,79 @@ +package cn.binarywang.wx.miniapp.bean.kefu; + +import cn.binarywang.wx.miniapp.json.WxMaGsonBuilder; +import com.google.gson.annotations.SerializedName; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * 小程序客服信息. + * + * @author Binary Wang + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class WxMaKfInfo implements Serializable { + private static final long serialVersionUID = -7916302137791763175L; + + /** + * 客服账号. + */ + @SerializedName("kf_account") + private String kfAccount; + + /** + * 客服昵称. + */ + @SerializedName("kf_nick") + private String kfNick; + + /** + * 客服密码. + */ + @SerializedName("kf_id") + private String kfId; + + /** + * 客服头像. + */ + @SerializedName("kf_headimgurl") + private String kfHeadImgUrl; + + /** + * 如果客服帐号已绑定了客服人员微信号,则此处显示微信号. + */ + @SerializedName("kf_wx") + private String kfWx; + + /** + * 如果客服帐号尚未绑定微信号,但是已经发起了一个绑定邀请,则此处显示绑定邀请的微信号. + */ + @SerializedName("invite_wx") + private String inviteWx; + + /** + * 邀请的状态,有等待确认"waiting",被拒绝"rejected",过期"expired". + */ + @SerializedName("invite_expire_time") + private Long inviteExpireTime; + + /** + * 邀请的过期时间,为unix时间戳. + */ + @SerializedName("invite_status") + private String inviteStatus; + + public static WxMaKfInfo fromJson(String json) { + return WxMaGsonBuilder.create().fromJson(json, WxMaKfInfo.class); + } + + public String toJson() { + return WxMaGsonBuilder.create().toJson(this); + } +} \ No newline at end of file diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/kefu/WxMaKfList.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/kefu/WxMaKfList.java new file mode 100644 index 0000000000..8dd87a1728 --- /dev/null +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/kefu/WxMaKfList.java @@ -0,0 +1,35 @@ +package cn.binarywang.wx.miniapp.bean.kefu; + +import cn.binarywang.wx.miniapp.json.WxMaGsonBuilder; +import com.google.gson.annotations.SerializedName; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; +import java.util.List; + +/** + * 小程序客服列表. + * + * @author Binary Wang + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class WxMaKfList implements Serializable { + private static final long serialVersionUID = 6416633293297389972L; + + @SerializedName("kf_list") + private List kfList; + + public static WxMaKfList fromJson(String json) { + return WxMaGsonBuilder.create().fromJson(json, WxMaKfList.class); + } + + public String toJson() { + return WxMaGsonBuilder.create().toJson(this); + } +} \ No newline at end of file diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/kefu/WxMaKfSession.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/kefu/WxMaKfSession.java new file mode 100644 index 0000000000..60c010499c --- /dev/null +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/kefu/WxMaKfSession.java @@ -0,0 +1,43 @@ +package cn.binarywang.wx.miniapp.bean.kefu; + +import cn.binarywang.wx.miniapp.json.WxMaGsonBuilder; +import com.google.gson.annotations.SerializedName; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * 小程序客服会话. + * + * @author Binary Wang + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class WxMaKfSession implements Serializable { + private static final long serialVersionUID = -6987567952389036965L; + + /** + * 正在接待的客服,为空表示没有人在接待. + */ + @SerializedName("kf_account") + private String kfAccount; + + /** + * 会话接入的时间. + */ + @SerializedName("createtime") + private Long createTime; + + public static WxMaKfSession fromJson(String json) { + return WxMaGsonBuilder.create().fromJson(json, WxMaKfSession.class); + } + + public String toJson() { + return WxMaGsonBuilder.create().toJson(this); + } +} \ No newline at end of file diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/kefu/WxMaKfSessionList.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/kefu/WxMaKfSessionList.java new file mode 100644 index 0000000000..e172e2e4e5 --- /dev/null +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/kefu/WxMaKfSessionList.java @@ -0,0 +1,61 @@ +package cn.binarywang.wx.miniapp.bean.kefu; + +import cn.binarywang.wx.miniapp.json.WxMaGsonBuilder; +import com.google.gson.annotations.SerializedName; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; +import java.util.List; + +/** + * 小程序客服会话列表. + * + * @author Binary Wang + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class WxMaKfSessionList implements Serializable { + private static final long serialVersionUID = -1538600729426188776L; + + @SerializedName("sessionlist") + private List sessionList; + + @Data + @Builder + @NoArgsConstructor + @AllArgsConstructor + public static class SessionInfo implements Serializable { + private static final long serialVersionUID = -2077261313274513580L; + + /** + * 粉丝的openid. + */ + @SerializedName("openid") + private String openid; + + /** + * 会话创建时间,UNIX时间戳. + */ + @SerializedName("createtime") + private Long createTime; + + /** + * 粉丝的最后一条消息的时间,UNIX时间戳. + */ + @SerializedName("latest_time") + private Long latestTime; + } + + public static WxMaKfSessionList fromJson(String json) { + return WxMaGsonBuilder.create().fromJson(json, WxMaKfSessionList.class); + } + + public String toJson() { + return WxMaGsonBuilder.create().toJson(this); + } +} \ No newline at end of file diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/kefu/request/WxMaKfAccountRequest.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/kefu/request/WxMaKfAccountRequest.java new file mode 100644 index 0000000000..0ad8913639 --- /dev/null +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/kefu/request/WxMaKfAccountRequest.java @@ -0,0 +1,49 @@ +package cn.binarywang.wx.miniapp.bean.kefu.request; + +import cn.binarywang.wx.miniapp.json.WxMaGsonBuilder; +import com.google.gson.annotations.SerializedName; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * 小程序客服账号操作请求. + * + * @author Binary Wang + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class WxMaKfAccountRequest implements Serializable { + private static final long serialVersionUID = -4953504451749066635L; + + /** + * 客服账号. + */ + @SerializedName("kf_account") + private String kfAccount; + + /** + * 客服昵称. + */ + @SerializedName("kf_nick") + private String kfNick; + + /** + * 客服密码. + */ + @SerializedName("kf_pwd") + private String kfPwd; + + public String toJson() { + return WxMaGsonBuilder.create().toJson(this); + } + + public static WxMaKfAccountRequest fromJson(String json) { + return WxMaGsonBuilder.create().fromJson(json, WxMaKfAccountRequest.class); + } +} \ No newline at end of file diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/kefu/request/WxMaKfSessionRequest.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/kefu/request/WxMaKfSessionRequest.java new file mode 100644 index 0000000000..f8180f926a --- /dev/null +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/kefu/request/WxMaKfSessionRequest.java @@ -0,0 +1,43 @@ +package cn.binarywang.wx.miniapp.bean.kefu.request; + +import cn.binarywang.wx.miniapp.json.WxMaGsonBuilder; +import com.google.gson.annotations.SerializedName; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * 小程序客服会话操作请求. + * + * @author Binary Wang + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class WxMaKfSessionRequest implements Serializable { + private static final long serialVersionUID = -3278295399203344398L; + + /** + * 客服账号. + */ + @SerializedName("kf_account") + private String kfAccount; + + /** + * 用户openid. + */ + @SerializedName("openid") + private String openid; + + public String toJson() { + return WxMaGsonBuilder.create().toJson(this); + } + + public static WxMaKfSessionRequest fromJson(String json) { + return WxMaGsonBuilder.create().fromJson(json, WxMaKfSessionRequest.class); + } +} \ No newline at end of file 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 new file mode 100644 index 0000000000..b301e356e8 --- /dev/null +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/order/WxMaOrderManagementGetOrderDetailPath.java @@ -0,0 +1,22 @@ +package cn.binarywang.wx.miniapp.bean.order; + +import com.google.gson.annotations.SerializedName; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * @author xzh + * @Description + * @createTime 2025/01/16 15:27 + */ +@EqualsAndHashCode(callSuper = true) +@Data +public class WxMaOrderManagementGetOrderDetailPath extends WxMaOrderManagementResult { + private static final long serialVersionUID = -5288666524298706169L; + + /** + * 订单详情路径 + */ + @SerializedName("path") + private String path; +} diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/order/WxMaOrderManagementResult.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/order/WxMaOrderManagementResult.java new file mode 100644 index 0000000000..5a903b8980 --- /dev/null +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/order/WxMaOrderManagementResult.java @@ -0,0 +1,27 @@ +package cn.binarywang.wx.miniapp.bean.order; + +import com.google.gson.annotations.SerializedName; +import lombok.Data; + +import java.io.Serializable; + +/** + * @author xzh + * @Description + * @createTime 2025/01/16 15:27 + */ +@Data +public class WxMaOrderManagementResult implements Serializable { + private static final long serialVersionUID = 1468925151935770503L; + /** + * 错误码 + */ + @SerializedName("errcode") + private Integer errCode; + + /** + * 错误原因 + */ + @SerializedName("errmsg") + private String errMsg; +} 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/builder/BaseBuilder.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/builder/BaseBuilder.java index c353534c3f..71f49ee2d3 100644 --- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/builder/BaseBuilder.java +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/builder/BaseBuilder.java @@ -8,6 +8,7 @@ public class BaseBuilder { protected String msgType; protected String toUser; + protected String aiMsgContextMsgId; @SuppressWarnings("unchecked") public T toUser(String toUser) { @@ -15,6 +16,12 @@ public T toUser(String toUser) { return (T) this; } + @SuppressWarnings("unchecked") + public T aiMsgContextMsgId(String msgId) { + this.aiMsgContextMsgId = msgId; + return (T) this; + } + /** * 构造器方法. */ @@ -22,6 +29,9 @@ public WxMaKefuMessage build() { WxMaKefuMessage m = new WxMaKefuMessage(); m.setMsgType(this.msgType); m.setToUser(this.toUser); + if (this.aiMsgContextMsgId != null) { + m.setAiMsgContext(new WxMaKefuMessage.AiMsgContext(this.aiMsgContextMsgId)); + } return m; } } diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/config/WxMaConfig.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/config/WxMaConfig.java index ba71b931cc..8164d48346 100644 --- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/config/WxMaConfig.java +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/config/WxMaConfig.java @@ -16,9 +16,9 @@ public interface WxMaConfig { default void setUpdateAccessTokenBefore(Consumer updateAccessTokenBefore) {} /** - * Gets access token. + * 获取当前的 access_token * - * @return the access token + * @return 当前的 access_token 字符串 */ String getAccessToken(); @@ -30,26 +30,28 @@ default void setUpdateAccessTokenBefore(Consumer updateAcce // endregion /** - * Gets access token lock. + * 获取用于保护 access_token 更新的锁(线程安全用) * - * @return the access token lock + * @return access_token 的锁对象 */ Lock getAccessTokenLock(); /** - * Is access token expired boolean. + * 判断 access_token 是否已过期 * - * @return the boolean + * @return 如果已过期则返回 true,否则返回 false */ boolean isAccessTokenExpired(); - /** 强制将access token过期掉 */ + /** + * 强制将 access_token 标记为已过期 + */ void expireAccessToken(); /** * 应该是线程安全的 * - * @param accessToken 要更新的WxAccessToken对象 + * @param accessToken 要更新的 WxAccessToken 对象 */ default void updateAccessToken(WxAccessToken accessToken) { updateAccessToken(accessToken.getAccessToken(), accessToken.getExpiresIn()); @@ -58,8 +60,8 @@ default void updateAccessToken(WxAccessToken accessToken) { /** * 应该是线程安全的 * - * @param accessToken 新的accessToken值 - * @param expiresInSeconds 过期时间,以秒为单位 + * @param accessToken 新的 access_token 值 + * @param expiresInSeconds 过期时间,单位:秒 */ void updateAccessToken(String accessToken, int expiresInSeconds); @@ -75,229 +77,237 @@ default void updateAccessTokenProcessor(String accessToken, int expiresInSeconds default void updateAccessTokenBefore(WxAccessTokenEntity wxAccessTokenEntity) {} /** - * Gets jsapi ticket. + * 获取当前的 JSAPI ticket * - * @return the jsapi ticket + * @return 当前的 jsapi_ticket 字符串 */ String getJsapiTicket(); /** - * Gets jsapi ticket lock. + * 获取用于保护 jsapi_ticket 更新的锁(线程安全用) * - * @return the jsapi ticket lock + * @return jsapi_ticket 的锁对象 */ Lock getJsapiTicketLock(); /** - * Is jsapi ticket expired boolean. + * 判断 jsapi_ticket 是否已过期 * - * @return the boolean + * @return 如果已过期则返回 true,否则返回 false */ boolean isJsapiTicketExpired(); - /** 强制将jsapi ticket过期掉 */ + /** + * 强制将 jsapi_ticket 标记为已过期 + */ void expireJsapiTicket(); /** * 应该是线程安全的 * - * @param jsapiTicket 新的jsapi ticket值 - * @param expiresInSeconds 过期时间,以秒为单位 + * @param jsapiTicket 新的 jsapi_ticket 值 + * @param expiresInSeconds 过期时间,单位:秒 */ void updateJsapiTicket(String jsapiTicket, int expiresInSeconds); /** - * 卡券api_ticket. + * 获取卡券相关的 api_ticket * - * @return the card api ticket + * @return 卡券 api_ticket 字符串 */ String getCardApiTicket(); /** - * Gets card api ticket lock. + * 获取用于保护卡券 api_ticket 更新的锁(线程安全用) * - * @return the card api ticket lock + * @return 卡券 api_ticket 的锁对象 */ Lock getCardApiTicketLock(); /** - * Is card api ticket expired boolean. + * 判断卡券 api_ticket 是否已过期 * - * @return the boolean + * @return 如果已过期则返回 true,否则返回 false */ boolean isCardApiTicketExpired(); - /** 强制将卡券api ticket过期掉. */ + /** + * 强制将卡券 api_ticket 标记为已过期 + */ void expireCardApiTicket(); /** - * 应该是线程安全的. + * 应该是线程安全的 * - * @param apiTicket 新的卡券api ticket值 - * @param expiresInSeconds 过期时间,以秒为单位 + * @param apiTicket 新的卡券 api_ticket 值 + * @param expiresInSeconds 过期时间,单位:秒 */ void updateCardApiTicket(String apiTicket, int expiresInSeconds); /** - * Gets appid. + * 获取小程序的 appId * - * @return the appid + * @return 小程序的 appId */ String getAppid(); /** - * Gets secret. + * 获取小程序的 secret * - * @return the secret + * @return 小程序的 secret */ String getSecret(); /** - * Gets token. + * 获取消息校验用的 token * - * @return the token + * @return token 字符串 */ String getToken(); /** - * Gets aes key. + * 获取消息加解密使用的 AES 密钥(用于消息加密/解密) * - * @return the aes key + * @return AES 密钥字符串 */ String getAesKey(); /** - * Gets original id. + * 获取原始 ID(原始公众号/小程序 ID) * - * @return the original id + * @return 原始 ID 字符串 */ String getOriginalId(); /** - * Gets cloud env. + * 获取云开发(Cloud)环境标识 * - * @return the cloud env + * @return 云环境 ID */ String getCloudEnv(); /** - * Gets msg data format. + * 获取消息数据的格式(例如 json) * - * @return the msg data format + * @return 消息数据格式字符串 */ String getMsgDataFormat(); /** - * Gets expires time. + * 获取 access_token 或 ticket 的过期时间(时间戳) * - * @return the expires time + * @return 过期时间的毫秒时间戳 */ long getExpiresTime(); /** - * Gets http proxy host. + * 获取 HTTP 代理主机 * - * @return the http proxy host + * @return 代理主机名或 IP */ String getHttpProxyHost(); /** - * Gets http proxy port. + * 获取 HTTP 代理端口 * - * @return the http proxy port + * @return 代理端口号 */ int getHttpProxyPort(); /** - * Gets http proxy username. + * 获取 HTTP 代理用户名 * - * @return the http proxy username + * @return 代理用户名 */ String getHttpProxyUsername(); /** - * Gets http proxy password. + * 获取 HTTP 代理密码 * - * @return the http proxy password + * @return 代理密码 */ String getHttpProxyPassword(); /** - * http 请求重试间隔 + * HTTP 请求重试间隔(毫秒) * *
    *   {@link cn.binarywang.wx.miniapp.api.impl.BaseWxMaServiceImpl#setRetrySleepMillis(int)}
    * 
+ * + * @return 重试间隔,单位:毫秒 */ int getRetrySleepMillis(); /** - * http 请求最大重试次数 + * HTTP 请求最大重试次数 * *
    *   {@link cn.binarywang.wx.miniapp.api.impl.BaseWxMaServiceImpl#setMaxRetryTimes(int)}
    * 
+ * + * @return 最大重试次数 */ int getMaxRetryTimes(); /** - * http client builder + * 获取用于创建 HTTP 客户端的 ApacheHttpClientBuilder * - * @return ApacheHttpClientBuilder apache http client builder + * @return ApacheHttpClientBuilder 实例 */ ApacheHttpClientBuilder getApacheHttpClientBuilder(); /** - * 是否自动刷新token + * 是否在 token 失效时自动刷新 * - * @return the boolean + * @return 如果自动刷新则返回 true,否则返回 false */ boolean autoRefreshToken(); /** - * 设置自定义的apiHost地址 - * 具体取值,可以参考https://developers.weixin.qq.com/doc/offiaccount/Basic_Information/Interface_field_description.html + * 设置自定义的 apiHost 地址 + * 具体取值,可以参考 API 域名文档 * - * @param apiHostUrl api域名地址 + * @param apiHostUrl api 域名地址 */ void setApiHostUrl(String apiHostUrl); /** - * 获取自定义的apiHost地址,用于替换原请求中的https://api.weixin.qq.com - * 具体取值,可以参考https://developers.weixin.qq.com/doc/offiaccount/Basic_Information/Interface_field_description.html + * 获取自定义的 apiHost 地址,用于替换原请求中的 https://api.weixin.qq.com + * 具体取值,可以参考 API 域名文档 * - * @return 自定义的api域名地址 + * @return 自定义的 api 域名地址 */ String getApiHostUrl(); /** - * 获取自定义的获取accessToken地址,用于向自定义统一服务获取accessToken + * 获取自定义的获取 accessToken 地址,用于向自定义统一服务获取 accessToken * - * @return 自定义的获取accessToken地址 + * @return 自定义的获取 accessToken 地址 */ String getAccessTokenUrl(); /** - * 设置自定义的获取accessToken地址 可用于设置获取accessToken的自定义服务 + * 设置自定义的获取 accessToken 地址,可用于设置获取 accessToken 的自定义服务 * - * @param accessTokenUrl 自定义的获取accessToken地址 + * @param accessTokenUrl 自定义的获取 accessToken 地址 */ void setAccessTokenUrl(String accessTokenUrl); /** - * 服务端API签名用到的RSA私钥【pkcs8格式,会以 -----BEGIN PRIVATE KEY-----开头, 'BEGIN RSA PRIVATE - * KEY'的是pkcs1格式,需要转换(可用openssl转换)。 设置参考: - * https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/getting_started/api_signature.html + * 服务端 API 签名用到的 RSA 私钥(pkcs8 格式,会以 -----BEGIN PRIVATE KEY----- 开头, + * 'BEGIN RSA PRIVATE KEY' 的是 pkcs1 格式,需要转换(可用 openssl 转换)。设置参考: + * API 签名文档 * - * @return rsa private key string + * @return RSA 私钥字符串(pkcs8 格式) */ String getApiSignatureRsaPrivateKey(); /** - * 服务端API签名用到的AES密钥 - * https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/getting_started/api_signature.html + * 服务端 API 签名用到的 AES 密钥 + * API 签名文档 * - * @return aes key string + * @return AES 密钥字符串 */ String getApiSignatureAesKey(); @@ -307,6 +317,52 @@ default void updateAccessTokenBefore(WxAccessTokenEntity wxAccessTokenEntity) {} /** 密钥对应的序号 */ String getApiSignatureRsaPrivateKeySn(); - /** 密钥对应的小程序ID (普通小程序同 appId, 托管第三方平台的是 componentAppId) */ + /** 密钥对应的小程序 ID(普通小程序为 appId,托管第三方平台为 componentAppId) */ String getWechatMpAppid(); + + /** 微信 API 默认主机地址 */ + String DEFAULT_API_HOST_URL = "https://api.weixin.qq.com"; + /** 微信云托管使用的 HTTP 协议主机地址 */ + String CLOUD_RUN_API_HOST_URL = "http://api.weixin.qq.com"; + + /** + * 是否使用微信云托管内网模式 + * 当部署在微信云托管环境时,api.weixin.qq.com 会被解析为内网地址,此时需要使用 HTTP 协议访问 + * 开启此配置后,SDK 会自动将 https://api.weixin.qq.com 替换为 http://api.weixin.qq.com + * + * @see 微信云托管内网调用微信接口 + * @return 是否使用微信云托管模式 + */ + default boolean isUseWxCloudRun() { + return false; + } + + /** + * 设置是否使用微信云托管内网模式 + * 当部署在微信云托管环境时,api.weixin.qq.com 会被解析为内网地址,此时需要使用 HTTP 协议访问 + * 开启此配置后,SDK 会自动将 https://api.weixin.qq.com 替换为 http://api.weixin.qq.com + * + * @see 微信云托管内网调用微信接口 + * @param useWxCloudRun 是否使用微信云托管模式 + */ + default void setUseWxCloudRun(boolean useWxCloudRun) { + // 默认空实现 + } + + /** + * 根据配置获取实际应使用的 API 主机地址 + * 优先级:自定义 apiHostUrl > 微信云托管模式 > 默认 HTTPS 地址 + * + * @return 实际应使用的 API 主机地址 + */ + default String getEffectiveApiHostUrl() { + String apiHostUrl = getApiHostUrl(); + if (apiHostUrl != null && !apiHostUrl.isEmpty()) { + return apiHostUrl; + } + if (isUseWxCloudRun()) { + return CLOUD_RUN_API_HOST_URL; + } + return DEFAULT_API_HOST_URL; + } } diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/config/impl/WxMaDefaultConfigImpl.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/config/impl/WxMaDefaultConfigImpl.java index ab82d6209e..07dfaefcc9 100644 --- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/config/impl/WxMaDefaultConfigImpl.java +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/config/impl/WxMaDefaultConfigImpl.java @@ -69,6 +69,10 @@ public class WxMaDefaultConfigImpl implements WxMaConfig { private String apiHostUrl; private String accessTokenUrl; + /** 是否使用微信云托管模式(使用 HTTP 协议访问内网地址) */ + @Getter(AccessLevel.NONE) + private boolean useWxCloudRun = false; + /** 自定义配置token的消费者 */ @Setter private Consumer updateAccessTokenBefore; @@ -388,6 +392,16 @@ public void setAccessTokenUrl(String accessTokenUrl) { this.accessTokenUrl = accessTokenUrl; } + @Override + public boolean isUseWxCloudRun() { + return this.useWxCloudRun; + } + + @Override + public void setUseWxCloudRun(boolean useWxCloudRun) { + this.useWxCloudRun = useWxCloudRun; + } + @Override public String getAppid() { return appid; 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 5908385790..45e1219659 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 @@ -364,6 +364,8 @@ public interface User { String SET_USER_STORAGE = "https://api.weixin.qq.com/wxa/set_user_storage?appid=%s&signature=%s&openid=%s&sig_method=%s"; String GET_PHONE_NUMBER_URL = "https://api.weixin.qq.com/wxa/business/getuserphonenumber"; + /** 多端登录验证接口 */ + String CODE_2_VERIFY_INFO_URL = "https://api.weixin.qq.com/wxa/sec/checkcode2verifyinfo"; } public interface Ocr { @@ -626,7 +628,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"; @@ -751,7 +753,7 @@ public interface OrderShipping { *
*/ String UPLOAD_COMBINED_SHIPPING_INFO = - "https://api.weixin.qq.com/wxa/sec/order/upload_combined_shipping_info"; + "https://api.weixin.qq.com/wxa/sec/order/upload_combined_shipping_info"; /** * 查询订单发货状态. @@ -779,7 +781,7 @@ public interface OrderShipping { *
*/ String NOTIFY_CONFIRM_RECEIVE = - "https://api.weixin.qq.com/wxa/sec/order/notify_confirm_receive"; + "https://api.weixin.qq.com/wxa/sec/order/notify_confirm_receive"; /** * 消息跳转路径设置接口. @@ -809,6 +811,35 @@ public interface OrderShipping { } + /** + * 小程序订单管理 + * + *
+   * 文档地址: https://developers.weixin.qq.com/miniprogram/dev/platform-capabilities/business-capabilities/order_center/order_center.html
+   * 
+ */ + public interface OrderManagement { + + /** + * 配置订单详情路径. + * + *
+     * 文档地址:https://developers.weixin.qq.com/miniprogram/dev/platform-capabilities/business-capabilities/order_center/order_center.html
+     * 
+ */ + String UPDATE_ORDER_DETAIL_PATH = "https://api.weixin.qq.com/wxa/sec/order/update_order_detail_path"; + + /** + * 查询订单详情路径. + * + *
+     * 文档地址:https://developers.weixin.qq.com/miniprogram/dev/platform-capabilities/business-capabilities/order_center/order_center.html
+     * 
+ */ + String GET_ORDER_DETAIL_PATH = "https://api.weixin.qq.com/wxa/sec/order/get_order_detail_path"; + + } + public interface Vod { String LIST_MEDIA_URL = "https://api.weixin.qq.com/wxa/sec/vod/listmedia"; String GET_MEDIA_URL = "https://api.weixin.qq.com/wxa/sec/vod/getmedia"; @@ -856,6 +887,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"; } /** @@ -919,4 +966,34 @@ public interface Intracity { String GET_CITY = "https://api.weixin.qq.com/cgi-bin/express/intracity/getcity"; } + + /** + * 小程序交易投诉接口 + * + *
+   * 文档地址:https://developers.weixin.qq.com/miniprogram/dev/platform-capabilities/business-capabilities/guarantee/complaint.html
+   * 
+ */ + public interface Complaint { + /** 查询投诉单列表 */ + String QUERY_COMPLAINTS_URL = "https://api.weixin.qq.com/cgi-bin/miniapp/complaint/list"; + /** 查询投诉单详情 */ + String GET_COMPLAINT_URL = "https://api.weixin.qq.com/cgi-bin/miniapp/complaint/detail"; + /** 查询投诉协商历史 */ + String QUERY_NEGOTIATION_HISTORY_URL = "https://api.weixin.qq.com/cgi-bin/miniapp/complaint/negotiation/history"; + /** 创建投诉通知回调地址 */ + String ADD_COMPLAINT_NOTIFY_URL = "https://api.weixin.qq.com/cgi-bin/miniapp/complaint/notify/add"; + /** 查询投诉通知回调地址 */ + String GET_COMPLAINT_NOTIFY_URL = "https://api.weixin.qq.com/cgi-bin/miniapp/complaint/notify/get"; + /** 更新投诉通知回调地址 */ + String UPDATE_COMPLAINT_NOTIFY_URL = "https://api.weixin.qq.com/cgi-bin/miniapp/complaint/notify/update"; + /** 删除投诉通知回调地址 */ + String DELETE_COMPLAINT_NOTIFY_URL = "https://api.weixin.qq.com/cgi-bin/miniapp/complaint/notify/delete"; + /** 提交回复 */ + String SUBMIT_RESPONSE_URL = "https://api.weixin.qq.com/cgi-bin/miniapp/complaint/response"; + /** 反馈处理完成 */ + String COMPLETE_COMPLAINT_URL = "https://api.weixin.qq.com/cgi-bin/miniapp/complaint/complete"; + /** 上传反馈图片 */ + String UPLOAD_RESPONSE_IMAGE_URL = "https://api.weixin.qq.com/cgi-bin/miniapp/complaint/upload"; + } } 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 aClass) { + return aClass == File.class || aClass == ApacheHttpClientBuilder.class; + } + }); } public static Gson create() { diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/json/adaptor/WxMaCodeVersionDistributionGsonAdapter.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/json/adaptor/WxMaCodeVersionDistributionGsonAdapter.java index 018be6b046..746d261170 100644 --- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/json/adaptor/WxMaCodeVersionDistributionGsonAdapter.java +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/json/adaptor/WxMaCodeVersionDistributionGsonAdapter.java @@ -33,7 +33,7 @@ public WxMaCodeVersionDistribution deserialize(JsonElement json, Type type, Json 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/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/WxMaJsonOutMessage.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/message/WxMaJsonOutMessage.java new file mode 100644 index 0000000000..3f9bbe200c --- /dev/null +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/message/WxMaJsonOutMessage.java @@ -0,0 +1,67 @@ +package cn.binarywang.wx.miniapp.message; + +import cn.binarywang.wx.miniapp.config.WxMaConfig; +import cn.binarywang.wx.miniapp.json.WxMaGsonBuilder; +import cn.binarywang.wx.miniapp.util.crypt.WxMaCryptUtils; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.Accessors; + +import java.io.Serializable; + +/** + * 微信小程序输出给微信服务器的JSON格式消息. + * + * @author Binary Wang + */ +@Data +@Accessors(chain = true) +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class WxMaJsonOutMessage implements WxMaOutMessage { + private static final long serialVersionUID = 4241135225946919154L; + + protected String toUserName; + protected String fromUserName; + protected Long createTime; + protected String msgType; + + /** + * 转换成JSON格式. + */ + @Override + public String toJson() { + return WxMaGsonBuilder.create().toJson(this); + } + + /** + * 转换成XML格式(对于JSON消息类型,返回JSON格式). + */ + @Override + public String toXml() { + // JSON消息类型默认返回JSON格式 + return toJson(); + } + + /** + * 转换成加密的JSON格式. + */ + @Override + public String toEncryptedJson(WxMaConfig config) { + String plainJson = toJson(); + WxMaCryptUtils pc = new WxMaCryptUtils(config); + return pc.encrypt(plainJson); + } + + /** + * 转换成加密的XML格式(对于JSON消息类型,返回加密的JSON格式). + */ + @Override + public String toEncryptedXml(WxMaConfig config) { + // JSON消息类型默认返回加密的JSON格式 + return toEncryptedJson(config); + } +} \ No newline at end of file diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/message/WxMaMessageHandler.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/message/WxMaMessageHandler.java index 9fdd956934..c222692e8f 100644 --- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/message/WxMaMessageHandler.java +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/message/WxMaMessageHandler.java @@ -20,10 +20,10 @@ public interface WxMaMessageHandler { * @param context 上下文 * @param service 服务类 * @param sessionManager session管理器 - * @return 输出消息 + * @return 输出消息,可以是XML格式或JSON格式 * @throws WxErrorException 异常 */ - WxMaXmlOutMessage handle(WxMaMessage message, Map context, - WxMaService service, WxSessionManager sessionManager) throws WxErrorException; + WxMaOutMessage handle(WxMaMessage message, Map context, + WxMaService service, WxSessionManager sessionManager) throws WxErrorException; } 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..b46003d98b 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; @@ -108,7 +107,7 @@ public WxMaMessageRouterRule rule() { /** * 处理微信消息. */ - public WxMaXmlOutMessage route(final WxMaMessage wxMessage, final Map context) { + public WxMaOutMessage route(final WxMaMessage wxMessage, final Map context) { if (isMsgDuplicated(wxMessage)) { // 如果是重复消息,那么就不做处理 return null; @@ -125,12 +124,12 @@ public WxMaXmlOutMessage route(final WxMaMessage wxMessage, final Map> futures = new ArrayList<>(); - WxMaXmlOutMessage result = null; + WxMaOutMessage result = null; for (final WxMaMessageRouterRule rule : matchRules) { // 返回最后一个非异步的rule的执行结果 if (rule.isAsync()) { @@ -152,7 +151,7 @@ public WxMaXmlOutMessage route(final WxMaMessage wxMessage, final Map 0) { + if (!futures.isEmpty()) { this.executorService.submit(() -> { for (Future future : futures) { try { @@ -169,7 +168,7 @@ public WxMaXmlOutMessage route(final WxMaMessage wxMessage, final Map(2)); } diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/message/WxMaMessageRouterRule.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/message/WxMaMessageRouterRule.java index 99181e0434..ebff3fb50b 100644 --- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/message/WxMaMessageRouterRule.java +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/message/WxMaMessageRouterRule.java @@ -201,7 +201,7 @@ protected boolean test(WxMaMessage wxMessage) { /** * 处理微信推送过来的消息. */ - protected WxMaXmlOutMessage service(WxMaMessage wxMessage, + protected WxMaOutMessage service(WxMaMessage wxMessage, Map context, WxMaService wxMaService, WxSessionManager sessionManager, @@ -210,7 +210,7 @@ protected WxMaXmlOutMessage service(WxMaMessage wxMessage, context = new HashMap<>(16); } - WxMaXmlOutMessage outMessage = null; + WxMaOutMessage outMessage = null; try { // 如果拦截器不通过 for (WxMaMessageInterceptor interceptor : this.interceptors) { diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/message/WxMaOutMessage.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/message/WxMaOutMessage.java new file mode 100644 index 0000000000..595db304c5 --- /dev/null +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/message/WxMaOutMessage.java @@ -0,0 +1,43 @@ +package cn.binarywang.wx.miniapp.message; + +import cn.binarywang.wx.miniapp.config.WxMaConfig; + +import java.io.Serializable; + +/** + * 微信小程序输出消息的通用接口,支持XML和JSON两种格式. + * + * @author Binary Wang + */ +public interface WxMaOutMessage extends Serializable { + + /** + * 转换成XML格式. + * + * @return XML格式的消息 + */ + String toXml(); + + /** + * 转换成JSON格式. + * + * @return JSON格式的消息 + */ + String toJson(); + + /** + * 转换成加密的XML格式. + * + * @param config 配置对象 + * @return 加密后的XML格式消息 + */ + String toEncryptedXml(WxMaConfig config); + + /** + * 转换成加密的JSON格式. + * + * @param config 配置对象 + * @return 加密后的JSON格式消息 + */ + String toEncryptedJson(WxMaConfig config); +} diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/message/WxMaXmlOutMessage.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/message/WxMaXmlOutMessage.java index a6c2b828ae..b66563a95f 100644 --- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/message/WxMaXmlOutMessage.java +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/message/WxMaXmlOutMessage.java @@ -26,7 +26,7 @@ @Builder @AllArgsConstructor @NoArgsConstructor -public class WxMaXmlOutMessage implements Serializable { +public class WxMaXmlOutMessage implements WxMaOutMessage { private static final long serialVersionUID = 4241135225946919153L; @XStreamAlias("ToUserName") @@ -45,16 +45,36 @@ public class WxMaXmlOutMessage implements Serializable { protected String msgType; @SuppressWarnings("unchecked") + @Override public String toXml() { return XStreamTransformer.toXml((Class) this.getClass(), this); } + /** + * 转换成JSON格式(对于XML消息类型,返回XML格式). + */ + @Override + public String toJson() { + // XML消息类型默认返回XML格式 + return toXml(); + } + /** * 转换成加密的xml格式. */ + @Override public String toEncryptedXml(WxMaConfig config) { String plainXml = toXml(); WxMaCryptUtils pc = new WxMaCryptUtils(config); return pc.encrypt(plainXml); } + + /** + * 转换成加密的JSON格式(对于XML消息类型,返回加密的XML格式). + */ + @Override + public String toEncryptedJson(WxMaConfig config) { + // XML消息类型默认返回加密的XML格式 + return toEncryptedXml(config); + } } 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/WxMaExpressServiceImplTest.java b/weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/api/impl/WxMaExpressServiceImplTest.java index bf6e23797e..5cb96c2ab2 100644 --- a/weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/api/impl/WxMaExpressServiceImplTest.java +++ b/weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/api/impl/WxMaExpressServiceImplTest.java @@ -116,10 +116,10 @@ public void testAddOrder() throws WxErrorException { goodsCount ++; } cargo.setCount(detailList.size()); - cargo.setWeight(5); - cargo.setSpaceHeight(10); - cargo.setSpaceLength(10); - cargo.setSpaceWidth(10); + cargo.setWeight(1.2); + cargo.setSpaceHeight(10.0); + cargo.setSpaceLength(20.0); + cargo.setSpaceWidth(15.0); cargo.setDetailList(detailList); diff --git a/weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/api/impl/WxMaKefuServiceImplTest.java b/weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/api/impl/WxMaKefuServiceImplTest.java new file mode 100644 index 0000000000..456aca93f2 --- /dev/null +++ b/weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/api/impl/WxMaKefuServiceImplTest.java @@ -0,0 +1,60 @@ +package cn.binarywang.wx.miniapp.api.impl; + +import cn.binarywang.wx.miniapp.api.WxMaKefuService; +import cn.binarywang.wx.miniapp.api.WxMaService; +import cn.binarywang.wx.miniapp.bean.kefu.WxMaKfList; +import cn.binarywang.wx.miniapp.bean.kefu.request.WxMaKfAccountRequest; +import me.chanjar.weixin.common.error.WxErrorException; +import org.testng.Assert; +import org.testng.annotations.Test; + +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + * 小程序客服管理服务测试. + * + * @author Binary Wang + */ +public class WxMaKefuServiceImplTest { + + @Test + public void testKfList() throws WxErrorException { + WxMaService service = mock(WxMaService.class); + when(service.get(anyString(), any())).thenReturn("{\"kf_list\":[]}"); + + WxMaKefuService kefuService = new WxMaKefuServiceImpl(service); + WxMaKfList result = kefuService.kfList(); + + Assert.assertNotNull(result); + Assert.assertNotNull(result.getKfList()); + Assert.assertEquals(result.getKfList().size(), 0); + } + + @Test + public void testKfAccountAdd() throws WxErrorException { + WxMaService service = mock(WxMaService.class); + when(service.post(anyString(), anyString())).thenReturn("{\"errcode\":0}"); + + WxMaKefuService kefuService = new WxMaKefuServiceImpl(service); + WxMaKfAccountRequest request = WxMaKfAccountRequest.builder() + .kfAccount("test@kfaccount") + .kfNick("测试客服") + .kfPwd("password") + .build(); + + boolean result = kefuService.kfAccountAdd(request); + Assert.assertTrue(result); + } + + @Test + public void testKfSessionCreate() throws WxErrorException { + WxMaService service = mock(WxMaService.class); + when(service.post(anyString(), anyString())).thenReturn("{\"errcode\":0}"); + + WxMaKefuService kefuService = new WxMaKefuServiceImpl(service); + boolean result = kefuService.kfSessionCreate("test_openid", "test@kfaccount"); + Assert.assertTrue(result); + } +} \ No newline at end of file diff --git a/weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/api/impl/WxMaShareServiceImplTest.java b/weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/api/impl/WxMaShareServiceImplTest.java index dcf3726e33..f6d041ff35 100644 --- a/weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/api/impl/WxMaShareServiceImplTest.java +++ b/weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/api/impl/WxMaShareServiceImplTest.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.WxMaGroupEnterInfo; import cn.binarywang.wx.miniapp.bean.WxMaShareInfo; import cn.binarywang.wx.miniapp.test.ApiTestModule; import com.google.inject.Inject; @@ -37,4 +38,16 @@ public void testGetShareInfo() { assertNotNull(shareInfo); System.out.println(shareInfo.toString()); } + + /** + * TODO 测试数据有问题,需要替换为正确的数据 + */ + @Test + public void testGetGroupEnterInfo() { + WxMaGroupEnterInfo groupEnterInfo = this.wxService.getShareService().getGroupEnterInfo("tiihtNczf5v6AKRyjwEUhQ==", + "CiyLU1Aw2KjvrjMdj8YKliAjtP4gsMZMQmRzooG2xrDcvSnxIMXFufNstNGTyaGS9uT5geRa0W4oTOb1WT7fJlAC+oNPdbB+3hVbJSRgv+4lGOETKUQz6OYStslQ142dNCuabNPGBzlooOmB231qMM85d2/fV6ChevvXvQP8Hkue1poOFtnEtpyxVLW1zAo6/1Xx1COxFvrc2d7UL/lmHInNlxuacJXwu0fjpXfz/YqYzBIBzD6WUfTIF9GRHpOn/Hz7saL8xz+W//FRAUid1OksQaQx4CMs8LOddcQhULW4ucetDf96JcR3g0gfRK4PC7E/r7Z6xNrXd2UIeorGj5Ef7b1pJAYB6Y5anaHqZ9J6nKEBvB4DnNLIVWSgARns/8wR2SiRS7MNACwTyrGvt9ts8p12PKFdlqYTopNHR1Vf7XjfhQlVsAJdNiKdYmYVoKlaRv85IfVunYzO0IKXsyl7JCUjCpoG20f0a04COwfneQAGGwd5oa+T8yO5hzuyDb/XcxxmK01EpqOyuxINew==", + "r7BXXKkLb8qrSNn05n0qiA=="); + assertNotNull(groupEnterInfo); + System.out.println(groupEnterInfo.toString()); + } } 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-miniapp/src/test/java/cn/binarywang/wx/miniapp/bean/WxMaKefuMessageTest.java b/weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/bean/WxMaKefuMessageTest.java index 6486c3237f..c855b2747a 100644 --- a/weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/bean/WxMaKefuMessageTest.java +++ b/weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/bean/WxMaKefuMessageTest.java @@ -66,4 +66,14 @@ public void testURLEscaped() { "\"link\":{\"title\":\"title\",\"description\":\"description\",\"url\":\"https://mp.weixin.qq.com/s?__biz=MzI0MDA2OTY5NQ==\",\"thumb_url\":\"thumbUrl\"}}"); } + public void testTextBuilderWithAiMsgContext() { + WxMaKefuMessage reply = WxMaKefuMessage.newTextBuilder() + .toUser("OPENID") + .content("回复内容") + .aiMsgContextMsgId("MSG_ID_123") + .build(); + assertThat(reply.toJson()) + .isEqualTo("{\"touser\":\"OPENID\",\"msgtype\":\"text\",\"text\":{\"content\":\"回复内容\"},\"aimsgcontext\":{\"msgid\":\"MSG_ID_123\"}}"); + } + } diff --git a/weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/demo/WxMaDemoServer.java b/weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/demo/WxMaDemoServer.java index 7784cf3a1d..a1a796c715 100644 --- a/weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/demo/WxMaDemoServer.java +++ b/weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/demo/WxMaDemoServer.java @@ -8,6 +8,7 @@ import cn.binarywang.wx.miniapp.constant.WxMaConstants; import cn.binarywang.wx.miniapp.message.WxMaMessageHandler; import cn.binarywang.wx.miniapp.message.WxMaMessageRouter; +import cn.binarywang.wx.miniapp.message.WxMaOutMessage; import cn.binarywang.wx.miniapp.message.WxMaXmlOutMessage; import cn.binarywang.wx.miniapp.test.TestConfig; import me.chanjar.weixin.common.api.WxConsts; @@ -32,8 +33,8 @@ public class WxMaDemoServer { private static final WxMaMessageHandler logHandler = new WxMaMessageHandler() { @Override - public WxMaXmlOutMessage handle(WxMaMessage wxMessage, Map context, - WxMaService service, WxSessionManager sessionManager) throws WxErrorException { + public WxMaOutMessage handle(WxMaMessage wxMessage, Map context, + WxMaService service, WxSessionManager sessionManager) throws WxErrorException { System.out.println("收到消息:" + wxMessage.toString()); service.getMsgService().sendKefuMsg(WxMaKefuMessage.newTextBuilder().content("收到信息为:" + wxMessage.toJson()) .toUser(wxMessage.getFromUser()).build()); @@ -43,8 +44,8 @@ public WxMaXmlOutMessage handle(WxMaMessage wxMessage, Map conte private static final WxMaMessageHandler textHandler = new WxMaMessageHandler() { @Override - public WxMaXmlOutMessage handle(WxMaMessage wxMessage, Map context, - WxMaService service, WxSessionManager sessionManager) + public WxMaOutMessage handle(WxMaMessage wxMessage, Map context, + WxMaService service, WxSessionManager sessionManager) throws WxErrorException { service.getMsgService().sendKefuMsg(WxMaKefuMessage.newTextBuilder().content("回复文本消息") .toUser(wxMessage.getFromUser()).build()); @@ -55,8 +56,8 @@ public WxMaXmlOutMessage handle(WxMaMessage wxMessage, Map conte private static final WxMaMessageHandler picHandler = new WxMaMessageHandler() { @Override - public WxMaXmlOutMessage handle(WxMaMessage wxMessage, Map context, - WxMaService service, WxSessionManager sessionManager) throws WxErrorException { + public WxMaOutMessage handle(WxMaMessage wxMessage, Map context, + WxMaService service, WxSessionManager sessionManager) throws WxErrorException { try { WxMediaUploadResult uploadResult = service.getMediaService() .uploadMedia(WxMaConstants.MediaType.IMAGE, "png", @@ -76,8 +77,8 @@ public WxMaXmlOutMessage handle(WxMaMessage wxMessage, Map conte private static final WxMaMessageHandler qrcodeHandler = new WxMaMessageHandler() { @Override - public WxMaXmlOutMessage handle(WxMaMessage wxMessage, Map context, - WxMaService service, WxSessionManager sessionManager) throws WxErrorException { + public WxMaOutMessage handle(WxMaMessage wxMessage, Map context, + WxMaService service, WxSessionManager sessionManager) throws WxErrorException { try { final File file = service.getQrcodeService().createQrcode("123", 430); WxMediaUploadResult uploadResult = service.getMediaService().uploadMedia(WxMaConstants.MediaType.IMAGE, file); @@ -96,7 +97,7 @@ public WxMaXmlOutMessage handle(WxMaMessage wxMessage, Map conte private static final WxMaMessageHandler customerServiceMessageHandler = new WxMaMessageHandler() { @Override - public WxMaXmlOutMessage handle(WxMaMessage message, Map context, WxMaService service, WxSessionManager sessionManager) { + public WxMaOutMessage handle(WxMaMessage message, Map context, WxMaService service, WxSessionManager sessionManager) { return new WxMaXmlOutMessage() .setMsgType(WxConsts.XmlMsgType.TRANSFER_CUSTOMER_SERVICE) .setFromUserName(message.getToUser()) diff --git a/weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/demo/WxMaKefuServiceDemo.java b/weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/demo/WxMaKefuServiceDemo.java new file mode 100644 index 0000000000..1d62fd9be9 --- /dev/null +++ b/weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/demo/WxMaKefuServiceDemo.java @@ -0,0 +1,84 @@ +package cn.binarywang.wx.miniapp.demo; + +import cn.binarywang.wx.miniapp.api.WxMaService; +import cn.binarywang.wx.miniapp.bean.kefu.WxMaKfList; +import cn.binarywang.wx.miniapp.bean.kefu.WxMaKfSession; +import cn.binarywang.wx.miniapp.bean.kefu.WxMaKfSessionList; +import cn.binarywang.wx.miniapp.bean.kefu.request.WxMaKfAccountRequest; +import me.chanjar.weixin.common.error.WxErrorException; + +/** + * 小程序客服管理功能使用示例. + * + * @author Binary Wang + */ +public class WxMaKefuServiceDemo { + + private final WxMaService wxMaService; + + public WxMaKefuServiceDemo(WxMaService wxMaService) { + this.wxMaService = wxMaService; + } + + /** + * 演示客服账号管理功能 + */ + public void demonstrateCustomerServiceManagement() throws WxErrorException { + // 1. 获取客服列表 + WxMaKfList kfList = wxMaService.getKefuService().kfList(); + System.out.println("当前客服数量: " + kfList.getKfList().size()); + + // 2. 添加新客服账号 + WxMaKfAccountRequest addRequest = WxMaKfAccountRequest.builder() + .kfAccount("service001@example") + .kfNick("客服001") + .kfPwd("password123") + .build(); + + boolean addResult = wxMaService.getKefuService().kfAccountAdd(addRequest); + System.out.println("添加客服账号结果: " + addResult); + + // 3. 更新客服账号信息 + WxMaKfAccountRequest updateRequest = WxMaKfAccountRequest.builder() + .kfAccount("service001@example") + .kfNick("高级客服001") + .build(); + + boolean updateResult = wxMaService.getKefuService().kfAccountUpdate(updateRequest); + System.out.println("更新客服账号结果: " + updateResult); + } + + /** + * 演示客服会话管理功能 + */ + public void demonstrateSessionManagement() throws WxErrorException { + String userOpenid = "user_openid_example"; + String kfAccount = "service001@example"; + + // 1. 创建客服会话 + boolean createResult = wxMaService.getKefuService().kfSessionCreate(userOpenid, kfAccount); + System.out.println("创建会话结果: " + createResult); + + // 2. 获取用户会话状态 + WxMaKfSession session = wxMaService.getKefuService().kfSessionGet(userOpenid); + System.out.println("用户当前会话客服: " + session.getKfAccount()); + + // 3. 获取客服的会话列表 + WxMaKfSessionList sessionList = wxMaService.getKefuService().kfSessionList(kfAccount); + System.out.println("客服当前会话数量: " + sessionList.getSessionList().size()); + + // 4. 关闭客服会话 + boolean closeResult = wxMaService.getKefuService().kfSessionClose(userOpenid, kfAccount); + System.out.println("关闭会话结果: " + closeResult); + } + + /** + * 演示客服账号删除功能 + */ + public void demonstrateAccountDeletion() throws WxErrorException { + String kfAccount = "service001@example"; + + boolean deleteResult = wxMaService.getKefuService().kfAccountDel(kfAccount); + System.out.println("删除客服账号结果: " + deleteResult); + } +} \ No newline at end of file diff --git a/weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/demo/WxMaPortalServlet.java b/weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/demo/WxMaPortalServlet.java index c209082d45..cf004510cd 100644 --- a/weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/demo/WxMaPortalServlet.java +++ b/weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/demo/WxMaPortalServlet.java @@ -5,7 +5,7 @@ import cn.binarywang.wx.miniapp.config.WxMaConfig; import cn.binarywang.wx.miniapp.constant.WxMaConstants; import cn.binarywang.wx.miniapp.message.WxMaMessageRouter; -import cn.binarywang.wx.miniapp.message.WxMaXmlOutMessage; +import cn.binarywang.wx.miniapp.message.WxMaOutMessage; import lombok.AllArgsConstructor; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils; @@ -62,9 +62,13 @@ protected void service(HttpServletRequest request, HttpServletResponse response) inMessage = WxMaMessage.fromXml(request.getInputStream()); } - final WxMaXmlOutMessage outMessage = this.messageRouter.route(inMessage); + final WxMaOutMessage outMessage = this.messageRouter.route(inMessage); if (outMessage != null) { - response.getWriter().write(outMessage.toXml()); + if (isJson) { + response.getWriter().write(outMessage.toJson()); + } else { + response.getWriter().write(outMessage.toXml()); + } return; } @@ -82,9 +86,13 @@ protected void service(HttpServletRequest request, HttpServletResponse response) inMessage = WxMaMessage.fromEncryptedXml(request.getInputStream(), this.config, timestamp, nonce, msgSignature); } - final WxMaXmlOutMessage outMessage = this.messageRouter.route(inMessage); + final WxMaOutMessage outMessage = this.messageRouter.route(inMessage); if (outMessage != null) { - response.getWriter().write(outMessage.toEncryptedXml(this.config)); + if (isJson) { + response.getWriter().write(outMessage.toEncryptedJson(this.config)); + } else { + response.getWriter().write(outMessage.toEncryptedXml(this.config)); + } return; } response.getWriter().write("success"); diff --git a/weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/message/WxMaJsonOutMessageTest.java b/weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/message/WxMaJsonOutMessageTest.java new file mode 100644 index 0000000000..09f3beaf22 --- /dev/null +++ b/weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/message/WxMaJsonOutMessageTest.java @@ -0,0 +1,56 @@ +package cn.binarywang.wx.miniapp.message; + +import me.chanjar.weixin.common.api.WxConsts; +import org.testng.annotations.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class WxMaJsonOutMessageTest { + + @Test + public void testToJson() { + WxMaJsonOutMessage message = WxMaJsonOutMessage.builder() + .fromUserName("test_from_user") + .toUserName("test_to_user") + .msgType(WxConsts.XmlMsgType.TRANSFER_CUSTOMER_SERVICE) + .createTime(System.currentTimeMillis() / 1000) + .build(); + + String jsonResult = message.toJson(); + assertThat(jsonResult).isNotEmpty(); + assertThat(jsonResult).contains("test_from_user"); + assertThat(jsonResult).contains("test_to_user"); + assertThat(jsonResult).contains(WxConsts.XmlMsgType.TRANSFER_CUSTOMER_SERVICE); + + System.out.println("JSON Output:"); + System.out.println(jsonResult); + } + + @Test + public void testEmptyMessage() { + WxMaJsonOutMessage message = new WxMaJsonOutMessage(); + String jsonResult = message.toJson(); + assertThat(jsonResult).isNotEmpty(); + System.out.println("Empty message JSON:"); + System.out.println(jsonResult); + } + + @Test + public void testImplementsInterface() { + WxMaJsonOutMessage message = WxMaJsonOutMessage.builder() + .fromUserName("test_from_user") + .toUserName("test_to_user") + .msgType(WxConsts.XmlMsgType.TEXT) + .createTime(System.currentTimeMillis() / 1000) + .build(); + + // Test that it implements WxMaOutMessage interface + WxMaOutMessage outMessage = message; + assertThat(outMessage).isNotNull(); + + // Test both toJson and toXml methods (for JSON messages, both return JSON format) + assertThat(outMessage.toJson()).isNotEmpty(); + assertThat(outMessage.toXml()).isNotEmpty(); + assertThat(outMessage.toJson()).isEqualTo(outMessage.toXml()); + } +} \ No newline at end of file diff --git a/weixin-java-mp/pom.xml b/weixin-java-mp/pom.xml index c8c3f298c8..2b2e8e4210 100644 --- a/weixin-java-mp/pom.xml +++ b/weixin-java-mp/pom.xml @@ -7,7 +7,7 @@ com.github.binarywang wx-java - 4.7.0 + 4.7.9.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/WxMpAiOpenService.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpAiOpenService.java index 07bc1e52e1..ad995bba2d 100644 --- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpAiOpenService.java +++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpAiOpenService.java @@ -6,74 +6,112 @@ import me.chanjar.weixin.mp.enums.AiLangType; /** - *
- * 微信AI开放接口(语音识别,微信翻译).
- * https://mp.weixin.qq.com/wiki?t=resource/res_main&id=21516712282KzWVE
- *  Created by BinaryWang on 2018/6/9.
- * 
+ * 微信AI开放接口(语音识别,微信翻译) + *

+ * 提供微信AI相关的功能,包括语音识别、微信翻译等。 + * 支持上传语音文件进行语音识别,以及文本翻译功能。 + *

+ *

+ * 详情请见:微信AI开放接口 + *

+ * Created by BinaryWang on 2018/6/9. * * @author Binary Wang */ public interface WxMpAiOpenService { - /** - *
-   * 提交语音.
-   * http请求方式: POST
-   * http://api.weixin.qq.com/cgi-bin/media/voice/addvoicetorecofortext?access_token=ACCESS_TOKEN&format=&voice_id=xxxxxx&lang=zh_CN
-   * 
- * - * @param voiceId 语音唯一标识 - * @param lang 语言,zh_CN 或 en_US,默认中文 - * @param voiceFile 语音文件 - * @throws WxErrorException the wx error exception - */ - void uploadVoice(String voiceId, AiLangType lang, File voiceFile) throws WxErrorException; + /** + *
+     * 提交语音
+     * 
+ * + * @param voiceId 语音唯一标识 + * @param lang 语言,zh_CN 或 en_US,默认中文 + * @param voiceFile 语音文件 + * @throws WxErrorException 微信API调用异常,可能包括: + *
    + *
  • 40001 - 获取access_token时AppSecret错误,或者access_token无效
  • + *
  • 40002 - 请确保grant_type字段值为client_credential
  • + *
  • 40003 - appid对应公众号请开发者使用绑定的公众号测试
  • + *
  • 40004 - appid不正确
  • + *
  • 40006 - access_token超时
  • + *
  • 48001 - api功能未授权
  • + *
  • 45009 - 调用接口的QPS超限
  • + *
  • 其他业务错误码
  • + *
+ * @see 提交语音接口 + */ + void uploadVoice(String voiceId, AiLangType lang, File voiceFile) throws WxErrorException; - /** - *
-   * 获取语音识别结果.
-   * 接口调用请求说明
-   *
-   * http://api.weixin.qq.com/cgi-bin/media/voice/queryrecoresultfortext?access_token=ACCESS_TOKEN&voice_id=xxxxxx&lang=zh_CN
-   * 请注意,添加完文件之后10s内调用这个接口
-   *
-   * 
- * - * @param voiceId 语音唯一标识 - * @param lang 语言,zh_CN 或 en_US,默认中文 - * @return the string - * @throws WxErrorException the wx error exception - */ - String queryRecognitionResult(String voiceId, AiLangType lang) throws WxErrorException; + /** + *
+     * 获取语音识别结果
+     * 请注意,添加完文件之后10s内调用这个接口
+     * 
+ * + * @param voiceId 语音唯一标识 + * @param lang 语言,zh_CN 或 en_US,默认中文 + * @return 语音识别结果文本 + * @throws WxErrorException 微信API调用异常,可能包括: + *
    + *
  • 40001 - 获取access_token时AppSecret错误,或者access_token无效
  • + *
  • 40002 - 请确保grant_type字段值为client_credential
  • + *
  • 40003 - appid对应公众号请开发者使用绑定的公众号测试
  • + *
  • 40004 - appid不正确
  • + *
  • 40006 - access_token超时
  • + *
  • 48001 - api功能未授权
  • + *
  • 45009 - 调用接口的QPS超限
  • + *
  • 其他业务错误码
  • + *
+ * @see 获取语音识别结果接口 + */ + String queryRecognitionResult(String voiceId, AiLangType lang) throws WxErrorException; - /** - * 识别指定语音文件内容. - * 此方法揉合了前两两个方法:uploadVoice 和 queryRecognitionResult - * - * @param voiceId 语音唯一标识 - * @param lang 语言,zh_CN 或 en_US,默认中文 - * @param voiceFile 语音文件 - * @return the string - * @throws WxErrorException the wx error exception - */ - String recogniseVoice(String voiceId, AiLangType lang, File voiceFile) throws WxErrorException; + /** + *
+     * 识别指定语音文件内容
+     * 此方法揉合了前两两个方法:uploadVoice 和 queryRecognitionResult
+     * 
+ * + * @param voiceId 语音唯一标识 + * @param lang 语言,zh_CN 或 en_US,默认中文 + * @param voiceFile 语音文件 + * @return 语音识别结果文本 + * @throws WxErrorException 微信API调用异常,可能包括: + *
    + *
  • 40001 - 获取access_token时AppSecret错误,或者access_token无效
  • + *
  • 40002 - 请确保grant_type字段值为client_credential
  • + *
  • 40003 - appid对应公众号请开发者使用绑定的公众号测试
  • + *
  • 40004 - appid不正确
  • + *
  • 40006 - access_token超时
  • + *
  • 48001 - api功能未授权
  • + *
  • 45009 - 调用接口的QPS超限
  • + *
  • 其他业务错误码
  • + *
+ */ + String recogniseVoice(String voiceId, AiLangType lang, File voiceFile) throws WxErrorException; - /** - *
-   * 微信翻译.
-   * 接口调用请求说明
-   *
-   * http请求方式: POST
-   * http://api.weixin.qq.com/cgi-bin/media/voice/translatecontent?access_token=ACCESS_TOKEN&lfrom=xxx<o=xxx
-   *
-   * 
- * - * @param langFrom 源语言,zh_CN 或 en_US - * @param langTo 目标语言,zh_CN 或 en_US - * @param content 要翻译的文本内容 - * @return the string - * @throws WxErrorException the wx error exception - */ - String translate(AiLangType langFrom, AiLangType langTo, String content) throws WxErrorException; + /** + *
+     * 微信翻译
+     * 
+ * + * @param langFrom 源语言,zh_CN 或 en_US + * @param langTo 目标语言,zh_CN 或 en_US + * @param content 要翻译的文本内容 + * @return 翻译结果文本 + * @throws WxErrorException 微信API调用异常,可能包括: + *
    + *
  • 40001 - 获取access_token时AppSecret错误,或者access_token无效
  • + *
  • 40002 - 请确保grant_type字段值为client_credential
  • + *
  • 40003 - appid对应公众号请开发者使用绑定的公众号测试
  • + *
  • 40004 - appid不正确
  • + *
  • 40006 - access_token超时
  • + *
  • 48001 - api功能未授权
  • + *
  • 45009 - 调用接口的QPS超限
  • + *
  • 其他业务错误码
  • + *
+ * @see 微信翻译接口 + */ + String translate(AiLangType langFrom, AiLangType langTo, String content) throws WxErrorException; } diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpCardService.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpCardService.java index 08c040e144..188e4be78b 100644 --- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpCardService.java +++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpCardService.java @@ -7,298 +7,611 @@ import java.util.List; /** - * 卡券相关接口. + * 卡券相关接口 + *

+ * 提供微信卡券的创建、查询、核销、管理等功能。 + * 支持卡券API签名生成、卡券Code解码、卡券核销、库存管理等功能。 + *

+ *

+ * 详情请见:卡券开发文档 + *

* * @author YuJian(mgcnrx11 @ hotmail.com) on 01/11/2016 * @author yuanqixun 2018-08-29 */ public interface WxMpCardService { /** - * 得到WxMpService. + *
+     * 获取WxMpService实例
+     * 
* - * @return WxMpService wx mp service + * @return WxMpService实例 */ WxMpService getWxMpService(); /** - * 获得卡券api_ticket,不强制刷新卡券api_ticket. + *
+     * 获得卡券api_ticket,不强制刷新卡券api_ticket
+     * 
* - * @return 卡券api_ticket card api ticket - * @throws WxErrorException 异常 - * @see #getCardApiTicket(boolean) #getCardApiTicket(boolean)#getCardApiTicket(boolean) + * @return 卡券api_ticket + * @throws WxErrorException 微信API调用异常,可能包括: + *
    + *
  • 40001 - 获取access_token时AppSecret错误,或者access_token无效
  • + *
  • 40002 - 请确保grant_type字段值为client_credential
  • + *
  • 40003 - appid对应公众号请开发者使用绑定的公众号测试
  • + *
  • 40004 - appid不正确
  • + *
  • 40006 - access_token超时
  • + *
  • 48001 - api功能未授权
  • + *
  • 45009 - 调用接口的QPS超限
  • + *
  • 其他业务错误码
  • + *
+ * @see #getCardApiTicket(boolean) */ String getCardApiTicket() throws WxErrorException; /** *
-     * 获得卡券api_ticket.
+     * 获得卡券api_ticket
      * 获得时会检查卡券apiToken是否过期,如果过期了,那么就刷新一下,否则就什么都不干
-     *
-     * 详情请见:http://mp.weixin.qq.com/wiki/7/aaa137b55fb2e0456bf8dd9148dd613f.html#.E9.99.84.E5.BD.954-.E5.8D.A1.E5.88.B8.E6.89.A9.E5.B1.95.E5.AD.97.E6.AE.B5.E5.8F.8A.E7.AD.BE.E5.90.8D.E7.94.9F.E6.88.90.E7.AE.97.E6.B3.95
      * 
* - * @param forceRefresh 强制刷新 - * @return 卡券api_ticket card api ticket - * @throws WxErrorException 异常 + * @param forceRefresh 强制刷新,如果为true则强制刷新api_ticket + * @return 卡券api_ticket + * @throws WxErrorException 微信API调用异常,可能包括: + *
    + *
  • 40001 - 获取access_token时AppSecret错误,或者access_token无效
  • + *
  • 40002 - 请确保grant_type字段值为client_credential
  • + *
  • 40003 - appid对应公众号请开发者使用绑定的公众号测试
  • + *
  • 40004 - appid不正确
  • + *
  • 40006 - access_token超时
  • + *
  • 48001 - api功能未授权
  • + *
  • 45009 - 调用接口的QPS超限
  • + *
  • 其他业务错误码
  • + *
+ * @see 卡券签名生成算法 */ String getCardApiTicket(boolean forceRefresh) throws WxErrorException; /** *
-     * 创建调用卡券api时所需要的签名.
-     *
-     * 详情请见:http://mp.weixin.qq.com/wiki/7/aaa137b55fb2e0456bf8dd9148dd613f.html#.E9.99.84.E5.BD
-     * .954-.E5.8D.A1.E5.88.B8.E6.89.A9.E5.B1.95.E5.AD.97.E6.AE.B5.E5.8F.8A.E7.AD.BE.E5.90.8D.E7.94
-     * .9F.E6.88.90.E7.AE.97.E6.B3.95
+     * 创建调用卡券api时所需要的签名
      * 
* - * @param optionalSignParam 参与签名的参数数组。可以为下列字段:app_id, card_id, card_type, code, openid, location_id
注意:当做wx.chooseCard调用时,必须传入app_id参与签名,否则会造成签名失败导致拉取卡券列表为空 - * @return 卡券Api签名对象 wx card api signature - * @throws WxErrorException 异常 + * @param optionalSignParam 参与签名的参数数组。可以为下列字段:app_id, card_id, card_type, code, openid, location_id + * 注意:当做wx.chooseCard调用时,必须传入app_id参与签名,否则会造成签名失败导致拉取卡券列表为空 + * @return 卡券Api签名对象 + * @throws WxErrorException 微信API调用异常,可能包括: + *
    + *
  • 40001 - 获取access_token时AppSecret错误,或者access_token无效
  • + *
  • 40002 - 请确保grant_type字段值为client_credential
  • + *
  • 40003 - appid对应公众号请开发者使用绑定的公众号测试
  • + *
  • 40004 - appid不正确
  • + *
  • 40006 - access_token超时
  • + *
  • 48001 - api功能未授权
  • + *
  • 45009 - 调用接口的QPS超限
  • + *
  • 其他业务错误码
  • + *
+ * @see 卡券签名生成算法 */ WxCardApiSignature createCardApiSignature(String... optionalSignParam) throws WxErrorException; /** - * 卡券Code解码. + *
+     * 卡券Code解码
+     * 
* * @param encryptCode 加密Code,通过JSSDK的chooseCard接口获得 - * @return 解密后的Code string - * @throws WxErrorException 异常 + * @return 解密后的Code + * @throws WxErrorException 微信API调用异常,可能包括: + *
    + *
  • 40001 - 获取access_token时AppSecret错误,或者access_token无效
  • + *
  • 40002 - 请确保grant_type字段值为client_credential
  • + *
  • 40003 - appid对应公众号请开发者使用绑定的公众号测试
  • + *
  • 40004 - appid不正确
  • + *
  • 40006 - access_token超时
  • + *
  • 48001 - api功能未授权
  • + *
  • 45009 - 调用接口的QPS超限
  • + *
  • 其他业务错误码
  • + *
*/ String decryptCardCode(String encryptCode) throws WxErrorException; /** - * 卡券Code查询. - * 文档地址: https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1451025272&anchor=1 + *
+     * 卡券Code查询
+     * 
* * @param cardId 卡券ID代表一类卡券 * @param code 单张卡券的唯一标准 * @param checkConsume 是否校验code核销状态,填入true和false时的code异常状态返回数据不同 - * @return WxMpCardResult对象 wx mp card result - * @throws WxErrorException 异常 + * @return WxMpCardResult对象,包含卡券查询结果信息 + * @throws WxErrorException 微信API调用异常,可能包括: + *
    + *
  • 40001 - 获取access_token时AppSecret错误,或者access_token无效
  • + *
  • 40002 - 请确保grant_type字段值为client_credential
  • + *
  • 40003 - appid对应公众号请开发者使用绑定的公众号测试
  • + *
  • 40004 - appid不正确
  • + *
  • 40006 - access_token超时
  • + *
  • 48001 - api功能未授权
  • + *
  • 45009 - 调用接口的QPS超限
  • + *
  • 其他业务错误码
  • + *
+ * @see 卡券Code查询接口 */ WxMpCardResult queryCardCode(String cardId, String code, boolean checkConsume) throws WxErrorException; /** + *
      * 卡券Code核销。核销失败会抛出异常
+     * 
* * @param code 单张卡券的唯一标准 - * @return 调用返回的JSON字符串 。可用 com.google.gson.JsonParser#parse 等方法直接取JSON串中的errcode等信息。 - * @throws WxErrorException 异常 + * @return 调用返回的JSON字符串,可用 com.google.gson.JsonParser#parse 等方法直接取JSON串中的errcode等信息 + * @throws WxErrorException 微信API调用异常,可能包括: + *
    + *
  • 40001 - 获取access_token时AppSecret错误,或者access_token无效
  • + *
  • 40002 - 请确保grant_type字段值为client_credential
  • + *
  • 40003 - appid对应公众号请开发者使用绑定的公众号测试
  • + *
  • 40004 - appid不正确
  • + *
  • 40006 - access_token超时
  • + *
  • 48001 - api功能未授权
  • + *
  • 45009 - 调用接口的QPS超限
  • + *
  • 其他业务错误码
  • + *
*/ String consumeCardCode(String code) throws WxErrorException; /** - * 卡券Code核销。核销失败会抛出异常. + *
+     * 卡券Code核销。核销失败会抛出异常
+     * 
* * @param code 单张卡券的唯一标准 * @param cardId 当自定义Code卡券时需要传入card_id - * @return 调用返回的JSON字符串 。可用 com.google.gson.JsonParser#parse 等方法直接取JSON串中的errcode等信息。 - * @throws WxErrorException 异常 + * @return 调用返回的JSON字符串,可用 com.google.gson.JsonParser#parse 等方法直接取JSON串中的errcode等信息 + * @throws WxErrorException 微信API调用异常,可能包括: + *
    + *
  • 40001 - 获取access_token时AppSecret错误,或者access_token无效
  • + *
  • 40002 - 请确保grant_type字段值为client_credential
  • + *
  • 40003 - appid对应公众号请开发者使用绑定的公众号测试
  • + *
  • 40004 - appid不正确
  • + *
  • 40006 - access_token超时
  • + *
  • 48001 - api功能未授权
  • + *
  • 45009 - 调用接口的QPS超限
  • + *
  • 其他业务错误码
  • + *
*/ String consumeCardCode(String code, String cardId) throws WxErrorException; /** - * 卡券Mark接口. + *
+     * 卡券Mark接口
      * 开发者在帮助消费者核销卡券之前,必须帮助先将此code(卡券串码)与一个openid绑定(即mark住),
      * 才能进一步调用核销接口,否则报错。
+     * 
* * @param code 卡券的code码 * @param cardId 卡券的ID * @param openId 用券用户的openid * @param isMark 是否要mark(占用)这个code,填写true或者false,表示占用或解除占用 - * @throws WxErrorException 异常 + * @throws WxErrorException 微信API调用异常,可能包括: + *
    + *
  • 40001 - 获取access_token时AppSecret错误,或者access_token无效
  • + *
  • 40002 - 请确保grant_type字段值为client_credential
  • + *
  • 40003 - appid对应公众号请开发者使用绑定的公众号测试
  • + *
  • 40004 - appid不正确
  • + *
  • 40006 - access_token超时
  • + *
  • 48001 - api功能未授权
  • + *
  • 45009 - 调用接口的QPS超限
  • + *
  • 其他业务错误码
  • + *
*/ void markCardCode(String code, String cardId, String openId, boolean isMark) throws WxErrorException; /** - * 查看卡券详情接口. - * 详见 https://mp.weixin.qq.com/wiki/14/8dd77aeaee85f922db5f8aa6386d385e.html#.E6.9F.A5.E7.9C.8B.E5.8D.A1.E5.88.B8.E8.AF.A6.E6.83.85 + *
+     * 查看卡券详情接口
+     * 
* * @param cardId 卡券的ID - * @return 返回的卡券详情JSON字符串
[注] 由于返回的JSON格式过于复杂,难以定义其对应格式的Bean并且难以维护,因此只返回String格式的JSON串。
可由 com.google.gson.JsonParser#parse 等方法直接取JSON串中的某个字段。 - * @throws WxErrorException 异常 + * @return 返回的卡券详情JSON字符串 + * [注] 由于返回的JSON格式过于复杂,难以定义其对应格式的Bean并且难以维护,因此只返回String格式的JSON串。 + * 可由 com.google.gson.JsonParser#parse 等方法直接取JSON串中的某个字段。 + * @throws WxErrorException 微信API调用异常,可能包括: + *
    + *
  • 40001 - 获取access_token时AppSecret错误,或者access_token无效
  • + *
  • 40002 - 请确保grant_type字段值为client_credential
  • + *
  • 40003 - appid对应公众号请开发者使用绑定的公众号测试
  • + *
  • 40004 - appid不正确
  • + *
  • 40006 - access_token超时
  • + *
  • 48001 - api功能未授权
  • + *
  • 45009 - 调用接口的QPS超限
  • + *
  • 其他业务错误码
  • + *
+ * @see 查看卡券详情 */ String getCardDetail(String cardId) throws WxErrorException; /** - * 添加测试白名单. + *
+     * 添加测试白名单
+     * 
* * @param openid 用户的openid - * @return string string - * @throws WxErrorException 异常 + * @return 操作结果字符串 + * @throws WxErrorException 微信API调用异常,可能包括: + *
    + *
  • 40001 - 获取access_token时AppSecret错误,或者access_token无效
  • + *
  • 40002 - 请确保grant_type字段值为client_credential
  • + *
  • 40003 - appid对应公众号请开发者使用绑定的公众号测试
  • + *
  • 40004 - appid不正确
  • + *
  • 40006 - access_token超时
  • + *
  • 48001 - api功能未授权
  • + *
  • 45009 - 调用接口的QPS超限
  • + *
  • 其他业务错误码
  • + *
*/ String addTestWhiteList(String openid) throws WxErrorException; /** - * 创建卡券. + *
+     * 创建卡券
+     * 
* - * @param cardCreateMessage 请求 - * @return result wx mp card create result - * @throws WxErrorException 异常 + * @param cardCreateRequest 卡券创建请求对象 + * @return 卡券创建结果对象 + * @throws WxErrorException 微信API调用异常,可能包括: + *
    + *
  • 40001 - 获取access_token时AppSecret错误,或者access_token无效
  • + *
  • 40002 - 请确保grant_type字段值为client_credential
  • + *
  • 40003 - appid对应公众号请开发者使用绑定的公众号测试
  • + *
  • 40004 - appid不正确
  • + *
  • 40006 - access_token超时
  • + *
  • 48001 - api功能未授权
  • + *
  • 45009 - 调用接口的QPS超限
  • + *
  • 其他业务错误码
  • + *
*/ WxMpCardCreateResult createCard(WxMpCardCreateRequest cardCreateMessage) throws WxErrorException; /** - * 创建卡券二维码. + *
+     * 创建卡券二维码
+     * 
* * @param cardId 卡券编号 * @param outerStr 二维码标识 - * @return WxMpCardQrcodeCreateResult wx mp card qrcode create result - * @throws WxErrorException 异常 + * @return 卡券二维码创建结果对象 + * @throws WxErrorException 微信API调用异常,可能包括: + *
    + *
  • 40001 - 获取access_token时AppSecret错误,或者access_token无效
  • + *
  • 40002 - 请确保grant_type字段值为client_credential
  • + *
  • 40003 - appid对应公众号请开发者使用绑定的公众号测试
  • + *
  • 40004 - appid不正确
  • + *
  • 40006 - access_token超时
  • + *
  • 48001 - api功能未授权
  • + *
  • 45009 - 调用接口的QPS超限
  • + *
  • 其他业务错误码
  • + *
*/ WxMpCardQrcodeCreateResult createQrcodeCard(String cardId, String outerStr) throws WxErrorException; /** - * 创建卡券二维码. + *
+     * 创建卡券二维码
+     * 
* * @param cardId 卡券编号 - * @param outerStr 二维码标识 + * @param outerStr 用户首次领卡时,会通过 领取事件推送 给商户; 对于会员卡的二维码,用户每次扫码打开会员卡后点击任何url,会将该值拼入url中,方便开发者定位扫码来源 * @param expiresIn 指定二维码的有效时间,范围是60 ~ 1800秒。不填默认为365天有效 - * @return WxMpCardQrcodeCreateResult wx mp card qrcode create result - * @throws WxErrorException 异常 + * @return 卡券二维码创建结果对象 + * @throws WxErrorException 微信API调用异常,可能包括: + *
    + *
  • 40001 - 获取access_token时AppSecret错误,或者access_token无效
  • + *
  • 40002 - 请确保grant_type字段值为client_credential
  • + *
  • 40003 - appid对应公众号请开发者使用绑定的公众号测试
  • + *
  • 40004 - appid不正确
  • + *
  • 40006 - access_token超时
  • + *
  • 48001 - api功能未授权
  • + *
  • 45009 - 调用接口的QPS超限
  • + *
  • 其他业务错误码
  • + *
*/ WxMpCardQrcodeCreateResult createQrcodeCard(String cardId, String outerStr, int expiresIn) throws WxErrorException; /** - * 创建卡券二维码. + *
+     * 创建卡券二维码
+     * 
* * @param cardId 卡券编号 * @param outerStr 用户首次领卡时,会通过 领取事件推送 给商户; 对于会员卡的二维码,用户每次扫码打开会员卡后点击任何url,会将该值拼入url中,方便开发者定位扫码来源 * @param expiresIn 指定二维码的有效时间,范围是60 ~ 1800秒。不填默认为365天有效 - * @param openid 指定领取者的openid,只有该用户能领取。bind_openid字段为true的卡券必须填写,非指定openid不必填写。 - * @param code 卡券Code码,use_custom_code字段为true的卡券必须填写,非自定义code和导入code模式的卡券不必填写。 - * @param isUniqueCode 指定下发二维码,生成的二维码随机分配一个code,领取后不可再次扫描。填写true或false。默认false,注意填写该字段时,卡券须通过审核且库存不为0。 - * @return WxMpCardQrcodeCreateResult wx mp card qrcode create result - * @throws WxErrorException 异常 + * @param openid 指定领取者的openid,只有该用户能领取。bind_openid字段为true的卡券必须填写,非指定openid不必填写 + * @param code 卡券Code码,use_custom_code字段为true的卡券必须填写,非自定义code和导入code模式的卡券不必填写 + * @param isUniqueCode 指定下发二维码,生成的二维码随机分配一个code,领取后不可再次扫描。填写true或false。默认false,注意填写该字段时,卡券须通过审核且库存不为0 + * @return 卡券二维码创建结果对象 + * @throws WxErrorException 微信API调用异常,可能包括: + *
    + *
  • 40001 - 获取access_token时AppSecret错误,或者access_token无效
  • + *
  • 40002 - 请确保grant_type字段值为client_credential
  • + *
  • 40003 - appid对应公众号请开发者使用绑定的公众号测试
  • + *
  • 40004 - appid不正确
  • + *
  • 40006 - access_token超时
  • + *
  • 48001 - api功能未授权
  • + *
  • 45009 - 调用接口的QPS超限
  • + *
  • 其他业务错误码
  • + *
*/ WxMpCardQrcodeCreateResult createQrcodeCard(String cardId, String outerStr, int expiresIn, String openid, String code, boolean isUniqueCode) throws WxErrorException; /** - * 创建卡券货架. + *
+     * 创建卡券货架
+     * 
* * @param createRequest 货架创建参数 - * @return WxMpCardLandingPageCreateResult wx mp card landing page create result - * @throws WxErrorException 异常 + * @return 货架创建结果对象 + * @throws WxErrorException 微信API调用异常,可能包括: + *
    + *
  • 40001 - 获取access_token时AppSecret错误,或者access_token无效
  • + *
  • 40002 - 请确保grant_type字段值为client_credential
  • + *
  • 40003 - appid对应公众号请开发者使用绑定的公众号测试
  • + *
  • 40004 - appid不正确
  • + *
  • 40006 - access_token超时
  • + *
  • 48001 - api功能未授权
  • + *
  • 45009 - 调用接口的QPS超限
  • + *
  • 其他业务错误码
  • + *
*/ WxMpCardLandingPageCreateResult createLandingPage(WxMpCardLandingPageCreateRequest createRequest) throws WxErrorException; /** - * 将用户的卡券设置为失效状态. - * 详见:https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1451025272&anchor=9 + *
+     * 将用户的卡券设置为失效状态
+     * 
* * @param cardId 卡券编号 * @param code 用户会员卡号 * @param reason 设置为失效的原因 - * @return result string - * @throws WxErrorException 异常 + * @return 操作结果字符串 + * @throws WxErrorException 微信API调用异常,可能包括: + *
    + *
  • 40001 - 获取access_token时AppSecret错误,或者access_token无效
  • + *
  • 40002 - 请确保grant_type字段值为client_credential
  • + *
  • 40003 - appid对应公众号请开发者使用绑定的公众号测试
  • + *
  • 40004 - appid不正确
  • + *
  • 40006 - access_token超时
  • + *
  • 48001 - api功能未授权
  • + *
  • 45009 - 调用接口的QPS超限
  • + *
  • 其他业务错误码
  • + *
+ * @see 设置卡券失效 */ String unavailableCardCode(String cardId, String code, String reason) throws WxErrorException; /** - * 删除卡券接口. + *
+     * 删除卡券接口
+     * 
* * @param cardId 卡券id - * @return 删除结果 wx mp card delete result - * @throws WxErrorException 异常 + * @return 删除结果对象 + * @throws WxErrorException 微信API调用异常,可能包括: + *
    + *
  • 40001 - 获取access_token时AppSecret错误,或者access_token无效
  • + *
  • 40002 - 请确保grant_type字段值为client_credential
  • + *
  • 40003 - appid对应公众号请开发者使用绑定的公众号测试
  • + *
  • 40004 - appid不正确
  • + *
  • 40006 - access_token超时
  • + *
  • 48001 - api功能未授权
  • + *
  • 45009 - 调用接口的QPS超限
  • + *
  • 其他业务错误码
  • + *
*/ WxMpCardDeleteResult deleteCard(String cardId) throws WxErrorException; /** + *
      * 导入自定义code(仅对自定义code商户)
+     * 
* * @param cardId 卡券id - * @param codeList 需导入微信卡券后台的自定义code,上限为100个。 - * @return the wx mp card code deposit result - * @throws WxErrorException the wx error exception + * @param codeList 需导入微信卡券后台的自定义code,上限为100个 + * @return 导入结果对象 + * @throws WxErrorException 微信API调用异常,可能包括: + *
    + *
  • 40001 - 获取access_token时AppSecret错误,或者access_token无效
  • + *
  • 40002 - 请确保grant_type字段值为client_credential
  • + *
  • 40003 - appid对应公众号请开发者使用绑定的公众号测试
  • + *
  • 40004 - appid不正确
  • + *
  • 40006 - access_token超时
  • + *
  • 48001 - api功能未授权
  • + *
  • 45009 - 调用接口的QPS超限
  • + *
  • 其他业务错误码
  • + *
*/ WxMpCardCodeDepositResult cardCodeDeposit(String cardId, List codeList) throws WxErrorException; /** + *
      * 查询导入code数目接口
+     * 
* * @param cardId 卡券id - * @return the wx mp card code deposit count result - * @throws WxErrorException the wx error exception + * @return 查询结果对象 + * @throws WxErrorException 微信API调用异常,可能包括: + *
    + *
  • 40001 - 获取access_token时AppSecret错误,或者access_token无效
  • + *
  • 40002 - 请确保grant_type字段值为client_credential
  • + *
  • 40003 - appid对应公众号请开发者使用绑定的公众号测试
  • + *
  • 40004 - appid不正确
  • + *
  • 40006 - access_token超时
  • + *
  • 48001 - api功能未授权
  • + *
  • 45009 - 调用接口的QPS超限
  • + *
  • 其他业务错误码
  • + *
*/ WxMpCardCodeDepositCountResult cardCodeDepositCount(String cardId) throws WxErrorException; /** + *
      * 核查code接口
+     * 
* * @param cardId 卡券id * @param codeList 已经微信卡券后台的自定义code,上限为100个 - * @return the wx mp card code checkcode result - * @throws WxErrorException the wx error exception + * @return 核查结果对象 + * @throws WxErrorException 微信API调用异常,可能包括: + *
    + *
  • 40001 - 获取access_token时AppSecret错误,或者access_token无效
  • + *
  • 40002 - 请确保grant_type字段值为client_credential
  • + *
  • 40003 - appid对应公众号请开发者使用绑定的公众号测试
  • + *
  • 40004 - appid不正确
  • + *
  • 40006 - access_token超时
  • + *
  • 48001 - api功能未授权
  • + *
  • 45009 - 调用接口的QPS超限
  • + *
  • 其他业务错误码
  • + *
*/ WxMpCardCodeCheckcodeResult cardCodeCheckcode(String cardId, List codeList) throws WxErrorException; /** + *
      * 图文消息群发卡券获取内嵌html
+     * 
* * @param cardId 卡券id - * @return the wx mp card mpnews gethtml result - * @throws WxErrorException the wx error exception + * @return HTML获取结果对象 + * @throws WxErrorException 微信API调用异常,可能包括: + *
    + *
  • 40001 - 获取access_token时AppSecret错误,或者access_token无效
  • + *
  • 40002 - 请确保grant_type字段值为client_credential
  • + *
  • 40003 - appid对应公众号请开发者使用绑定的公众号测试
  • + *
  • 40004 - appid不正确
  • + *
  • 40006 - access_token超时
  • + *
  • 48001 - api功能未授权
  • + *
  • 45009 - 调用接口的QPS超限
  • + *
  • 其他业务错误码
  • + *
*/ WxMpCardMpnewsGethtmlResult cardMpnewsGethtml(String cardId) throws WxErrorException; - /** + *
      * 修改库存接口
-     * https://developers.weixin.qq.com/doc/offiaccount/Cards_and_Offer/Managing_Coupons_Vouchers_and_Cards.html#5
+     * 
* * @param cardId 卡券ID * @param changeValue 库存变更值,负值为减少库存 - * @throws WxErrorException the wx error exception + * @throws WxErrorException 微信API调用异常,可能包括: + *
    + *
  • 40001 - 获取access_token时AppSecret错误,或者access_token无效
  • + *
  • 40002 - 请确保grant_type字段值为client_credential
  • + *
  • 40003 - appid对应公众号请开发者使用绑定的公众号测试
  • + *
  • 40004 - appid不正确
  • + *
  • 40006 - access_token超时
  • + *
  • 48001 - api功能未授权
  • + *
  • 45009 - 调用接口的QPS超限
  • + *
  • 其他业务错误码
  • + *
+ * @see 修改库存接口 */ void cardModifyStock(String cardId, Integer changeValue) throws WxErrorException; - /** + *
      * 更改Code接口
-     * https://developers.weixin.qq.com/doc/offiaccount/Cards_and_Offer/Managing_Coupons_Vouchers_and_Cards.html#6
+     * 
* * @param cardId 卡券ID * @param oldCode 需变更的Code码 * @param newCode 变更后的有效Code码 - * @throws WxErrorException the wx error exception + * @throws WxErrorException 微信API调用异常,可能包括: + *
    + *
  • 40001 - 获取access_token时AppSecret错误,或者access_token无效
  • + *
  • 40002 - 请确保grant_type字段值为client_credential
  • + *
  • 40003 - appid对应公众号请开发者使用绑定的公众号测试
  • + *
  • 40004 - appid不正确
  • + *
  • 40006 - access_token超时
  • + *
  • 48001 - api功能未授权
  • + *
  • 45009 - 调用接口的QPS超限
  • + *
  • 其他业务错误码
  • + *
+ * @see 更改Code接口 */ void cardCodeUpdate(String cardId, String oldCode, String newCode) throws WxErrorException; /** + *
      * 设置买单接口
-     * https://developers.weixin.qq.com/doc/offiaccount/Cards_and_Offer/Create_a_Coupon_Voucher_or_Card.html#12
+     * 
* * @param cardId 卡券ID * @param isOpen 是否开启买单功能,填true/false - * @throws WxErrorException the wx error exception + * @throws WxErrorException 微信API调用异常,可能包括: + *
    + *
  • 40001 - 获取access_token时AppSecret错误,或者access_token无效
  • + *
  • 40002 - 请确保grant_type字段值为client_credential
  • + *
  • 40003 - appid对应公众号请开发者使用绑定的公众号测试
  • + *
  • 40004 - appid不正确
  • + *
  • 40006 - access_token超时
  • + *
  • 48001 - api功能未授权
  • + *
  • 45009 - 调用接口的QPS超限
  • + *
  • 其他业务错误码
  • + *
+ * @see 设置买单接口 */ void cardPaycellSet(String cardId, Boolean isOpen) throws WxErrorException; /** + *
      * 设置自助核销
-     * https://developers.weixin.qq.com/doc/offiaccount/Cards_and_Offer/Create_a_Coupon_Voucher_or_Card.html#14
+     * 
* * @param cardId 卡券ID * @param isOpen 是否开启自助核销功能 * @param needVerifyCod 用户核销时是否需要输入验证码, 填true/false, 默认为false * @param needRemarkAmount 用户核销时是否需要备注核销金额, 填true/false, 默认为false - * @throws WxErrorException the wx error exception + * @throws WxErrorException 微信API调用异常,可能包括: + *
    + *
  • 40001 - 获取access_token时AppSecret错误,或者access_token无效
  • + *
  • 40002 - 请确保grant_type字段值为client_credential
  • + *
  • 40003 - appid对应公众号请开发者使用绑定的公众号测试
  • + *
  • 40004 - appid不正确
  • + *
  • 40006 - access_token超时
  • + *
  • 48001 - api功能未授权
  • + *
  • 45009 - 调用接口的QPS超限
  • + *
  • 其他业务错误码
  • + *
+ * @see 设置自助核销 */ void cardSelfConsumeCellSet(String cardId, Boolean isOpen, Boolean needVerifyCod, Boolean needRemarkAmount) throws WxErrorException; /** + *
      * 获取用户已领取卡券接口
-     * https://developers.weixin.qq.com/doc/offiaccount/Cards_and_Offer/Managing_Coupons_Vouchers_and_Cards.html#1
+     * 
* * @param openId 需要查询的用户openid * @param cardId 卡券ID。不填写时默认查询当前appid下的卡券 - * @return user card list - * @throws WxErrorException the wx error exception + * @return 用户卡券列表结果对象 + * @throws WxErrorException 微信API调用异常,可能包括: + *
    + *
  • 40001 - 获取access_token时AppSecret错误,或者access_token无效
  • + *
  • 40002 - 请确保grant_type字段值为client_credential
  • + *
  • 40003 - appid对应公众号请开发者使用绑定的公众号测试
  • + *
  • 40004 - appid不正确
  • + *
  • 40006 - access_token超时
  • + *
  • 48001 - api功能未授权
  • + *
  • 45009 - 调用接口的QPS超限
  • + *
  • 其他业务错误码
  • + *
+ * @see 获取用户已领取卡券接口 */ WxUserCardListResult getUserCardList(String openId, String cardId) throws WxErrorException; - } diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpKefuService.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpKefuService.java index bceb80448d..234a7160e4 100644 --- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpKefuService.java +++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpKefuService.java @@ -29,9 +29,9 @@ public interface WxMpKefuService { * 接口url格式:https://api.weixin.qq.com/cgi-bin/message/custom/send?access_token=ACCESS_TOKEN * * - * @param message the message - * @return the boolean - * @throws WxErrorException 异常 + * @param message 客服消息对象,包含消息类型、内容、接收者等信息 + * @return 发送是否成功,true表示成功,false表示失败 + * @throws WxErrorException 微信API调用异常 */ boolean sendKefuMessage(WxMpKefuMessage message) throws WxErrorException; @@ -42,9 +42,9 @@ public interface WxMpKefuService { * 接口url格式:https://api.weixin.qq.com/cgi-bin/message/custom/send?access_token=ACCESS_TOKEN * * - * @param message the message - * @return the response - * @throws WxErrorException 异常 + * @param message 客服消息对象,包含消息类型、内容、接收者等信息 + * @return 微信API响应结果,JSON格式字符串 + * @throws WxErrorException 微信API调用异常 */ String sendKefuMessageWithResponse(WxMpKefuMessage message) throws WxErrorException; @@ -57,8 +57,8 @@ public interface WxMpKefuService { * 接口url格式:https://api.weixin.qq.com/cgi-bin/customservice/getkflist?access_token=ACCESS_TOKEN * * - * @return the wx mp kf list - * @throws WxErrorException 异常 + * @return 客服基本信息列表,包含客服账号、昵称、头像等信息 + * @throws WxErrorException 微信API调用异常 */ WxMpKfList kfList() throws WxErrorException; @@ -69,8 +69,8 @@ public interface WxMpKefuService { * 接口url格式:https://api.weixin.qq.com/cgi-bin/customservice/getonlinekflist?access_token=ACCESS_TOKEN * * - * @return the wx mp kf online list - * @throws WxErrorException 异常 + * @return 在线客服接待信息列表,包含在线客服账号、接待状态等信息 + * @throws WxErrorException 微信API调用异常 */ WxMpKfOnlineList kfOnlineList() throws WxErrorException; @@ -81,9 +81,9 @@ public interface WxMpKefuService { * 接口url格式:https://api.weixin.qq.com/customservice/kfaccount/add?access_token=ACCESS_TOKEN * * - * @param request the request - * @return the boolean - * @throws WxErrorException 异常 + * @param request 客服账号请求对象,包含客服账号、昵称等信息 + * @return 添加是否成功,true表示成功,false表示失败 + * @throws WxErrorException 微信API调用异常 */ boolean kfAccountAdd(WxMpKfAccountRequest request) throws WxErrorException; @@ -94,22 +94,22 @@ public interface WxMpKefuService { * 接口url格式:https://api.weixin.qq.com/customservice/kfaccount/update?access_token=ACCESS_TOKEN * * - * @param request the request - * @return the boolean - * @throws WxErrorException the wx error exception + * @param request 客服账号请求对象,包含客服账号、昵称等信息 + * @return 更新是否成功,true表示成功,false表示失败 + * @throws WxErrorException 微信API调用异常 */ boolean kfAccountUpdate(WxMpKfAccountRequest request) throws WxErrorException; /** *
-     * 设置客服信息(即更新客服信息)
+     * 邀请绑定客服账号
      * 详情请见:客服管理
      * 接口url格式:https://api.weixin.qq.com/customservice/kfaccount/inviteworker?access_token=ACCESS_TOKEN
      * 
* - * @param request the request - * @return the boolean - * @throws WxErrorException 异常 + * @param request 客服账号请求对象,包含客服账号、邀请者微信号等信息 + * @return 邀请是否成功,true表示成功,false表示失败 + * @throws WxErrorException 微信API调用异常 */ boolean kfAccountInviteWorker(WxMpKfAccountRequest request) throws WxErrorException; @@ -120,10 +120,10 @@ public interface WxMpKefuService { * 接口url格式:https://api.weixin.qq.com/customservice/kfaccount/uploadheadimg?access_token=ACCESS_TOKEN&kf_account=KFACCOUNT * * - * @param kfAccount the kf account - * @param imgFile the img file - * @return the boolean - * @throws WxErrorException 异常 + * @param kfAccount 客服账号,格式为:账号前缀@微信号 + * @param imgFile 头像图片文件,支持JPG、PNG格式,大小不超过2MB + * @return 上传是否成功,true表示成功,false表示失败 + * @throws WxErrorException 微信API调用异常 */ boolean kfAccountUploadHeadImg(String kfAccount, File imgFile) throws WxErrorException; @@ -134,9 +134,9 @@ public interface WxMpKefuService { * 接口url格式:https://api.weixin.qq.com/customservice/kfaccount/del?access_token=ACCESS_TOKEN&kf_account=KFACCOUNT * * - * @param kfAccount the kf account - * @return the boolean - * @throws WxErrorException 异常 + * @param kfAccount 客服账号,格式为:账号前缀@微信号 + * @return 删除是否成功,true表示成功,false表示失败 + * @throws WxErrorException 微信API调用异常 */ boolean kfAccountDel(String kfAccount) throws WxErrorException; @@ -150,10 +150,10 @@ public interface WxMpKefuService { * 接口url格式: https://api.weixin.qq.com/customservice/kfsession/create?access_token=ACCESS_TOKEN * * - * @param openid the openid - * @param kfAccount the kf account - * @return the boolean - * @throws WxErrorException 异常 + * @param openid 用户的openid,标识具体的用户 + * @param kfAccount 客服账号,格式为:账号前缀@微信号 + * @return 创建是否成功,true表示成功,false表示失败 + * @throws WxErrorException 微信API调用异常 */ boolean kfSessionCreate(String openid, String kfAccount) throws WxErrorException; @@ -165,10 +165,10 @@ public interface WxMpKefuService { * 接口url格式: https://api.weixin.qq.com/customservice/kfsession/close?access_token=ACCESS_TOKEN * * - * @param openid the openid - * @param kfAccount the kf account - * @return the boolean - * @throws WxErrorException 异常 + * @param openid 用户的openid,标识具体的用户 + * @param kfAccount 客服账号,格式为:账号前缀@微信号 + * @return 关闭是否成功,true表示成功,false表示失败 + * @throws WxErrorException 微信API调用异常 */ boolean kfSessionClose(String openid, String kfAccount) throws WxErrorException; @@ -180,9 +180,9 @@ public interface WxMpKefuService { * 接口url格式: https://api.weixin.qq.com/customservice/kfsession/getsession?access_token=ACCESS_TOKEN&openid=OPENID * * - * @param openid the openid - * @return the wx mp kf session get result - * @throws WxErrorException 异常 + * @param openid 用户的openid,标识具体的用户 + * @return 客户会话状态信息,包含客服账号、会话状态等 + * @throws WxErrorException 微信API调用异常 */ WxMpKfSessionGetResult kfSessionGet(String openid) throws WxErrorException; @@ -194,9 +194,9 @@ public interface WxMpKefuService { * 接口url格式: https://api.weixin.qq.com/customservice/kfsession/getsessionlist?access_token=ACCESS_TOKEN&kf_account=KFACCOUNT * * - * @param kfAccount the kf account - * @return the wx mp kf session list - * @throws WxErrorException 异常 + * @param kfAccount 客服账号,格式为:账号前缀@微信号 + * @return 客服会话列表,包含正在接待的会话信息 + * @throws WxErrorException 微信API调用异常 */ WxMpKfSessionList kfSessionList(String kfAccount) throws WxErrorException; @@ -208,8 +208,8 @@ public interface WxMpKefuService { * 接口url格式: https://api.weixin.qq.com/customservice/kfsession/getwaitcase?access_token=ACCESS_TOKEN * * - * @return the wx mp kf session wait case list - * @throws WxErrorException 异常 + * @return 未接入会话列表,包含等待接入的会话信息 + * @throws WxErrorException 微信API调用异常 */ WxMpKfSessionWaitCaseList kfSessionGetWaitCase() throws WxErrorException; @@ -223,12 +223,12 @@ public interface WxMpKefuService { * 接口url格式: https://api.weixin.qq.com/customservice/msgrecord/getmsglist?access_token=ACCESS_TOKEN * * - * @param startTime 起始时间 - * @param endTime 结束时间 - * @param msgId 消息id顺序从小到大,从1开始 - * @param number 每次获取条数,最多10000条 - * @return 聊天记录对象 wx mp kf msg list - * @throws WxErrorException 异常 + * @param startTime 起始时间,用于筛选聊天记录的时间范围 + * @param endTime 结束时间,用于筛选聊天记录的时间范围 + * @param msgId 消息id顺序从小到大,从1开始,用于分页获取 + * @param number 每次获取条数,最多10000条,用于分页控制 + * @return 聊天记录对象,包含客服和用户的聊天消息列表 + * @throws WxErrorException 微信API调用异常 */ WxMpKfMsgList kfMsgList(Date startTime, Date endTime, Long msgId, Integer number) throws WxErrorException; @@ -240,17 +240,17 @@ public interface WxMpKefuService { * 接口url格式: https://api.weixin.qq.com/customservice/msgrecord/getmsglist?access_token=ACCESS_TOKEN * * - * @param startTime 起始时间 - * @param endTime 结束时间 - * @return 聊天记录对象 wx mp kf msg list - * @throws WxErrorException 异常 + * @param startTime 起始时间,用于筛选聊天记录的时间范围 + * @param endTime 结束时间,用于筛选聊天记录的时间范围 + * @return 聊天记录对象,包含客服和用户的聊天消息列表 + * @throws WxErrorException 微信API调用异常 */ WxMpKfMsgList kfMsgList(Date startTime, Date endTime) throws WxErrorException; /** *
      * 客服输入状态
-     * 开发者可通过调用“客服输入状态”接口,返回客服当前输入状态给用户。
+     * 开发者可通过调用"客服输入状态"接口,返回客服当前输入状态给用户。
      * 此接口需要客服消息接口权限。
      * 如果不满足发送客服消息的触发条件,则无法下发输入状态。
      * 下发输入状态,需要客服之前30秒内跟用户有过消息交互。
@@ -261,10 +261,10 @@ public interface WxMpKefuService {
      * 接口url格式:https://api.weixin.qq.com/cgi-bin/message/custom/typing?access_token=ACCESS_TOKEN
      * 
* - * @param openid 用户id - * @param command "Typing":对用户下发“正在输入"状态 "CancelTyping":取消对用户的”正在输入"状态 - * @return the boolean - * @throws WxErrorException 异常 + * @param openid 用户的openid,标识具体的用户 + * @param command 输入状态命令,可选值:"Typing":对用户下发"正在输入"状态;"CancelTyping":取消对用户的"正在输入"状态 + * @return 发送是否成功,true表示成功,false表示失败 + * @throws WxErrorException 微信API调用异常 */ boolean sendKfTypingState(String openid, String command) throws WxErrorException; } diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpMassMessageService.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpMassMessageService.java index 823c2c6343..5ad3098c2a 100644 --- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpMassMessageService.java +++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpMassMessageService.java @@ -8,138 +8,234 @@ import me.chanjar.weixin.mp.bean.result.WxMpMassUploadResult; /** - *
- * 群发消息服务类.
+ * 群发消息服务类
+ * 

+ * 提供微信公众号群发消息的功能,包括图文消息、视频消息的群发, + * 支持按分组群发、按openid列表群发、消息预览、群发状态查询等功能。 + *

+ *

+ * 详情请见:群发消息开发文档 + *

* Created by Binary Wang on 2017-8-16. - *
* * @author Binary Wang */ public interface WxMpMassMessageService { /** *
-     * 上传群发用的图文消息,上传后才能群发图文消息.
-     *
-     * 详情请见: http://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140549&token=&lang=zh_CN
+     * 上传群发用的图文消息,上传后才能群发图文消息
      * 
* - * @param news the news - * @return the wx mp mass upload result - * @throws WxErrorException the wx error exception - * @see #massGroupMessageSend(WxMpMassTagMessage) #massGroupMessageSend(WxMpMassTagMessage) - * @see #massOpenIdsMessageSend(WxMpMassOpenIdsMessage) #massOpenIdsMessageSend(WxMpMassOpenIdsMessage) + * @param news 图文消息对象 + * @return 上传结果对象,包含media_id等信息 + * @throws WxErrorException 微信API调用异常,可能包括: + *
    + *
  • 40001 - 获取access_token时AppSecret错误,或者access_token无效
  • + *
  • 40002 - 请确保grant_type字段值为client_credential
  • + *
  • 40003 - appid对应公众号请开发者使用绑定的公众号测试
  • + *
  • 40004 - appid不正确
  • + *
  • 40006 - access_token超时
  • + *
  • 48001 - api功能未授权
  • + *
  • 45009 - 调用接口的QPS超限
  • + *
  • 其他业务错误码
  • + *
+ * @see #massGroupMessageSend(WxMpMassTagMessage) + * @see #massOpenIdsMessageSend(WxMpMassOpenIdsMessage) + * @see 上传群发用的图文消息 */ WxMpMassUploadResult massNewsUpload(WxMpMassNews news) throws WxErrorException; /** *
-     * 上传群发用的视频,上传后才能群发视频消息.
-     * 详情请见: http://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140549&token=&lang=zh_CN
+     * 上传群发用的视频,上传后才能群发视频消息
      * 
* - * @param video the video - * @return the wx mp mass upload result - * @throws WxErrorException the wx error exception - * @see #massGroupMessageSend(WxMpMassTagMessage) #massGroupMessageSend(WxMpMassTagMessage) - * @see #massOpenIdsMessageSend(WxMpMassOpenIdsMessage) #massOpenIdsMessageSend(WxMpMassOpenIdsMessage) + * @param video 视频消息对象 + * @return 上传结果对象,包含media_id等信息 + * @throws WxErrorException 微信API调用异常,可能包括: + *
    + *
  • 40001 - 获取access_token时AppSecret错误,或者access_token无效
  • + *
  • 40002 - 请确保grant_type字段值为client_credential
  • + *
  • 40003 - appid对应公众号请开发者使用绑定的公众号测试
  • + *
  • 40004 - appid不正确
  • + *
  • 40006 - access_token超时
  • + *
  • 48001 - api功能未授权
  • + *
  • 45009 - 调用接口的QPS超限
  • + *
  • 其他业务错误码
  • + *
+ * @see #massGroupMessageSend(WxMpMassTagMessage) + * @see #massOpenIdsMessageSend(WxMpMassOpenIdsMessage) + * @see 上传群发用的视频 */ WxMpMassUploadResult massVideoUpload(WxMpMassVideo video) throws WxErrorException; /** *
-     * 分组群发消息.
+     * 分组群发消息
      * 如果发送图文消息,必须先使用 {@link #massNewsUpload(WxMpMassNews)} 获得media_id,然后再发送
      * 如果发送视频消息,必须先使用 {@link #massVideoUpload(WxMpMassVideo)} 获得media_id,然后再发送
-     * 详情请见: http://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140549&token=&lang=zh_CN
      * 
* - * @param message the message - * @return the wx mp mass send result - * @throws WxErrorException the wx error exception + * @param message 分组群发消息对象 + * @return 群发结果对象,包含msg_id等信息 + * @throws WxErrorException 微信API调用异常,可能包括: + *
    + *
  • 40001 - 获取access_token时AppSecret错误,或者access_token无效
  • + *
  • 40002 - 请确保grant_type字段值为client_credential
  • + *
  • 40003 - appid对应公众号请开发者使用绑定的公众号测试
  • + *
  • 40004 - appid不正确
  • + *
  • 40006 - access_token超时
  • + *
  • 48001 - api功能未授权
  • + *
  • 45009 - 调用接口的QPS超限
  • + *
  • 其他业务错误码
  • + *
+ * @see 分组群发消息 */ WxMpMassSendResult massGroupMessageSend(WxMpMassTagMessage message) throws WxErrorException; /** *
-     * 按openId列表群发消息.
+     * 按openId列表群发消息
      * 如果发送图文消息,必须先使用 {@link #massNewsUpload(WxMpMassNews)} 获得media_id,然后再发送
      * 如果发送视频消息,必须先使用 {@link #massVideoUpload(WxMpMassVideo)} 获得media_id,然后再发送
-     * 详情请见: http://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140549&token=&lang=zh_CN
      * 
* - * @param message the message - * @return the wx mp mass send result - * @throws WxErrorException the wx error exception + * @param message 按openid列表群发消息对象 + * @return 群发结果对象,包含msg_id等信息 + * @throws WxErrorException 微信API调用异常,可能包括: + *
    + *
  • 40001 - 获取access_token时AppSecret错误,或者access_token无效
  • + *
  • 40002 - 请确保grant_type字段值为client_credential
  • + *
  • 40003 - appid对应公众号请开发者使用绑定的公众号测试
  • + *
  • 40004 - appid不正确
  • + *
  • 40006 - access_token超时
  • + *
  • 48001 - api功能未授权
  • + *
  • 45009 - 调用接口的QPS超限
  • + *
  • 其他业务错误码
  • + *
+ * @see 按openId列表群发消息 */ WxMpMassSendResult massOpenIdsMessageSend(WxMpMassOpenIdsMessage message) throws WxErrorException; /** *
-     * 群发消息预览接口.
+     * 群发消息预览接口
      * 开发者可通过该接口发送消息给指定用户,在手机端查看消息的样式和排版。为了满足第三方平台开发者的需求,
      * 在保留对openID预览能力的同时,增加了对指定微信号发送预览的能力,但该能力每日调用次数有限制(100次),请勿滥用。
-     * 接口调用请求说明
-     *  http请求方式: POST
-     *  https://api.weixin.qq.com/cgi-bin/message/mass/preview?access_token=ACCESS_TOKEN
-     * 详情请见:http://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140549&token=&lang=zh_CN
      * 
* - * @param wxMpMassPreviewMessage the wx mp mass preview message - * @return wxMpMassSendResult wx mp mass send result - * @throws WxErrorException the wx error exception + * @param wxMpMassPreviewMessage 预览消息对象 + * @return 群发结果对象,包含msg_id等信息 + * @throws WxErrorException 微信API调用异常,可能包括: + *
    + *
  • 40001 - 获取access_token时AppSecret错误,或者access_token无效
  • + *
  • 40002 - 请确保grant_type字段值为client_credential
  • + *
  • 40003 - appid对应公众号请开发者使用绑定的公众号测试
  • + *
  • 40004 - appid不正确
  • + *
  • 40006 - access_token超时
  • + *
  • 48001 - api功能未授权
  • + *
  • 45009 - 调用接口的QPS超限
  • + *
  • 其他业务错误码
  • + *
+ * @see 群发消息预览接口 */ WxMpMassSendResult massMessagePreview(WxMpMassPreviewMessage wxMpMassPreviewMessage) throws WxErrorException; /** *
-     * 删除群发.
+     * 删除群发
      * 群发之后,随时可以通过该接口删除群发。
      * 请注意:
      * 1、只有已经发送成功的消息才能删除
      * 2、删除消息是将消息的图文详情页失效,已经收到的用户,还是能在其本地看到消息卡片。
      * 3、删除群发消息只能删除图文消息和视频消息,其他类型的消息一经发送,无法删除。
      * 4、如果多次群发发送的是一个图文消息,那么删除其中一次群发,就会删除掉这个图文消息也,导致所有群发都失效
-     * 接口调用请求说明:
-     *  http请求方式: POST
-     *  https://api.weixin.qq.com/cgi-bin/message/mass/delete?access_token=ACCESS_TOKEN
-     * 详情请见:https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1481187827_i0l21
      * 
* * @param msgId 发送出去的消息ID * @param articleIndex 要删除的文章在图文消息中的位置,第一篇编号为1,该字段不填或填0会删除全部文章 - * @throws WxErrorException the wx error exception + * @throws WxErrorException 微信API调用异常,可能包括: + *
    + *
  • 40001 - 获取access_token时AppSecret错误,或者access_token无效
  • + *
  • 40002 - 请确保grant_type字段值为client_credential
  • + *
  • 40003 - appid对应公众号请开发者使用绑定的公众号测试
  • + *
  • 40004 - appid不正确
  • + *
  • 40006 - access_token超时
  • + *
  • 48001 - api功能未授权
  • + *
  • 45009 - 调用接口的QPS超限
  • + *
  • 其他业务错误码
  • + *
+ * @see 删除群发 */ void delete(Long msgId, Integer articleIndex) throws WxErrorException; - /** + *
      * 获取群发速度
-     * https://developers.weixin.qq.com/doc/offiaccount/Message_Management/Batch_Sends_and_Originality_Checks.html#9
+     * 
* - * @return the wx mp mass speed get result - * @throws WxErrorException the wx error exception + * @return 群发速度获取结果对象 + * @throws WxErrorException 微信API调用异常,可能包括: + *
    + *
  • 40001 - 获取access_token时AppSecret错误,或者access_token无效
  • + *
  • 40002 - 请确保grant_type字段值为client_credential
  • + *
  • 40003 - appid对应公众号请开发者使用绑定的公众号测试
  • + *
  • 40004 - appid不正确
  • + *
  • 40006 - access_token超时
  • + *
  • 48001 - api功能未授权
  • + *
  • 45009 - 调用接口的QPS超限
  • + *
  • 其他业务错误码
  • + *
+ * @see 获取群发速度 */ WxMpMassSpeedGetResult messageMassSpeedGet() throws WxErrorException; - /** + *
      * 设置群发速度
-     * https://developers.weixin.qq.com/doc/offiaccount/Message_Management/Batch_Sends_and_Originality_Checks.html#9
+     * 
* - * @param speed 群发速度的级别,是一个0到4的整数,数字越大表示群发速度越慢。 speed realspeed 0 80w/分钟 1 60w/分钟 2 45w/分钟 3 30w/分钟 4 10w/分钟 - * @throws WxErrorException the wx error exception + * @param speed 群发速度的级别,是一个0到4的整数,数字越大表示群发速度越慢。 + * speed realspeed + * 0 80w/分钟 + * 1 60w/分钟 + * 2 45w/分钟 + * 3 30w/分钟 + * 4 10w/分钟 + * @throws WxErrorException 微信API调用异常,可能包括: + *
    + *
  • 40001 - 获取access_token时AppSecret错误,或者access_token无效
  • + *
  • 40002 - 请确保grant_type字段值为client_credential
  • + *
  • 40003 - appid对应公众号请开发者使用绑定的公众号测试
  • + *
  • 40004 - appid不正确
  • + *
  • 40006 - access_token超时
  • + *
  • 48001 - api功能未授权
  • + *
  • 45009 - 调用接口的QPS超限
  • + *
  • 其他业务错误码
  • + *
+ * @see 设置群发速度 */ void messageMassSpeedSet(Integer speed) throws WxErrorException; - /** + *
      * 查询群发消息发送状态【订阅号与服务号认证后均可用】
-     * https://developers.weixin.qq.com/doc/offiaccount/Message_Management/Batch_Sends_and_Originality_Checks.html#%E6%9F%A5%E8%AF%A2%E7%BE%A4%E5%8F%91%E6%B6%88%E6%81%AF%E5%8F%91%E9%80%81%E7%8A%B6%E6%80%81%E3%80%90%E8%AE%A2%E9%98%85%E5%8F%B7%E4%B8%8E%E6%9C%8D%E5%8A%A1%E5%8F%B7%E8%AE%A4%E8%AF%81%E5%90%8E%E5%9D%87%E5%8F%AF%E7%94%A8%E3%80%91
+     * 
* * @param msgId 群发消息后返回的消息id - * @return 消息发送后的状态 ,SEND_SUCCESS表示发送成功,SENDING表示发送中,SEND_FAIL表示发送失败,DELETE表示已删除 - * @throws WxErrorException the wx error exception + * @return 消息发送后的状态,SEND_SUCCESS表示发送成功,SENDING表示发送中,SEND_FAIL表示发送失败,DELETE表示已删除 + * @throws WxErrorException 微信API调用异常,可能包括: + *
    + *
  • 40001 - 获取access_token时AppSecret错误,或者access_token无效
  • + *
  • 40002 - 请确保grant_type字段值为client_credential
  • + *
  • 40003 - appid对应公众号请开发者使用绑定的公众号测试
  • + *
  • 40004 - appid不正确
  • + *
  • 40006 - access_token超时
  • + *
  • 48001 - api功能未授权
  • + *
  • 45009 - 调用接口的QPS超限
  • + *
  • 其他业务错误码
  • + *
+ * @see 查询群发消息发送状态 */ WxMpMassGetResult messageMassGet(Long msgId) throws WxErrorException; - } 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..3c7a525b0f 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 @@ -17,7 +17,6 @@ /** *
- * Created by Binary Wang on 2016/7/21.
  * 素材管理的相关接口,包括媒体管理的接口,
  * 即以https://api.weixin.qq.com/cgi-bin/material
  * 和 https://api.weixin.qq.com/cgi-bin/media开头的接口
@@ -36,7 +35,7 @@ public interface WxMpMaterialService {
      *  2、media_id是可复用的。
      *  3、素材的格式大小等要求与公众平台官网一致。具体是,图片大小不超过2M,支持png/jpeg/jpg/gif格式,语音大小不超过5M,长度不超过60秒,支持mp3/amr格式
      *  4、需使用https调用本接口。
-     *  本接口即为原“上传多媒体文件”接口。
+     *  本接口即为原"上传多媒体文件"接口。
      *  注意事项:
      *    上传的临时多媒体文件有格式和大小限制,如下:
      *    图片(image): 2M,支持PNG\JPEG\JPG\GIF格式
@@ -49,17 +48,17 @@ public interface WxMpMaterialService {
      * 
* * @param mediaType 媒体类型, 请看{@link me.chanjar.weixin.common.api.WxConsts} - * @param file 文件对象 - * @return the wx media upload result - * @throws WxErrorException the wx error exception - * @see #mediaUpload(String, String, InputStream) #mediaUpload(String, String, InputStream)#mediaUpload(String, String, InputStream) + * @param file 文件对象,需要上传的临时素材文件 + * @return 上传结果,包含media_id等信息 + * @throws WxErrorException 微信API调用异常 + * @see #mediaUpload(String, String, InputStream) 使用输入流上传临时素材 */ WxMediaUploadResult mediaUpload(String mediaType, File file) throws WxErrorException; /** *
      * 新增临时素材
-     * 本接口即为原“上传多媒体文件”接口。
+     * 本接口即为原"上传多媒体文件"接口。
      *
      * 详情请见: 新增临时素材
      * 接口url格式:https://api.weixin.qq.com/cgi-bin/media/upload?access_token=ACCESS_TOKEN&type=TYPE
@@ -67,10 +66,10 @@ public interface WxMpMaterialService {
      *
      * @param mediaType   媒体类型, 请看{@link me.chanjar.weixin.common.api.WxConsts}
      * @param fileType    文件类型,请看{@link me.chanjar.weixin.common.api.WxConsts}
-     * @param inputStream 输入流
-     * @return the wx media upload result
-     * @throws WxErrorException the wx error exception
-     * @see #mediaUpload(java.lang.String, java.io.File) #mediaUpload(java.lang.String, java.io.File)#mediaUpload(java.lang.String, java.io.File)
+     * @param inputStream 输入流,包含要上传的临时素材内容
+     * @return 上传结果,包含media_id等信息
+     * @throws WxErrorException 微信API调用异常
+     * @see #mediaUpload(String, File) 使用文件对象上传临时素材
      */
     WxMediaUploadResult mediaUpload(String mediaType, String fileType, InputStream inputStream) throws WxErrorException;
 
@@ -78,15 +77,15 @@ public interface WxMpMaterialService {
      * 
      * 获取临时素材
      * 公众号可以使用本接口获取临时素材(即下载临时的多媒体文件)。请注意,视频文件不支持https下载,调用该接口需http协议。
-     * 本接口即为原“下载多媒体文件”接口。
+     * 本接口即为原"下载多媒体文件"接口。
      * 根据微信文档,视频文件下载不了,会返回null
      * 详情请见: 获取临时素材
      * 接口url格式:https://api.weixin.qq.com/cgi-bin/media/get?access_token=ACCESS_TOKEN&media_id=MEDIA_ID
      * 
* - * @param mediaId 媒体文件Id - * @return 保存到本地的临时文件 file - * @throws WxErrorException the wx error exception + * @param mediaId 媒体文件Id,通过上传临时素材接口获取 + * @return 保存到本地的临时文件,如果下载失败则返回null + * @throws WxErrorException 微信API调用异常 */ File mediaDownload(String mediaId) throws WxErrorException; @@ -100,9 +99,9 @@ public interface WxMpMaterialService { * 接口url格式:https://api.weixin.qq.com/cgi-bin/media/get/jssdk?access_token=ACCESS_TOKEN&media_id=MEDIA_ID *
* - * @param mediaId 媒体文件Id - * @return 保存到本地的临时文件 file - * @throws WxErrorException the wx error exception + * @param mediaId 媒体文件Id,通过JSSDK上传语音素材获取 + * @return 保存到本地的临时文件,高清语音素材 + * @throws WxErrorException 微信API调用异常 */ File jssdkMediaDownload(String mediaId) throws WxErrorException; @@ -114,9 +113,9 @@ public interface WxMpMaterialService { * 接口url格式:https://api.weixin.qq.com/cgi-bin/media/uploadimg?access_token=ACCESS_TOKEN * * - * @param file 上传的文件对象 - * @return WxMediaImgUploadResult 返回图片url - * @throws WxErrorException the wx error exception + * @param file 上传的文件对象,图片素材,支持jpg/png格式,大小不超过1MB + * @return 图片上传结果,包含图片URL,可用于图文消息中 + * @throws WxErrorException 微信API调用异常 */ WxMediaImgUploadResult mediaImgUpload(File file) throws WxErrorException; @@ -141,35 +140,11 @@ public interface WxMpMaterialService { * * @param mediaType 媒体类型, 请看{@link me.chanjar.weixin.common.api.WxConsts} * @param material 上传的素材, 请看{@link WxMpMaterial} - * @return the wx mp material upload result - * @throws WxErrorException the wx error exception + * @return 上传结果,包含media_id等信息 + * @throws WxErrorException 微信API调用异常 */ 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; - /** *
      * 获取声音或者图片永久素材
@@ -179,8 +154,8 @@ public interface WxMpMaterialService {
      * 
* * @param mediaId 永久素材的id - * @return the input stream - * @throws WxErrorException the wx error exception + * @return 素材内容的输入流,可用于读取图片或语音文件 + * @throws WxErrorException 微信API调用异常 */ InputStream materialImageOrVoiceDownload(String mediaId) throws WxErrorException; @@ -193,8 +168,8 @@ public interface WxMpMaterialService { * * * @param mediaId 永久素材的id - * @return the wx mp material video info result - * @throws WxErrorException the wx error exception + * @return 视频素材信息,包含标题、描述和下载地址 + * @throws WxErrorException 微信API调用异常 */ WxMpMaterialVideoInfoResult materialVideoInfo(String mediaId) throws WxErrorException; @@ -207,27 +182,11 @@ public interface WxMpMaterialService { * * * @param mediaId 永久素材的id - * @return the wx mp material news - * @throws WxErrorException the wx error exception + * @return 图文素材信息,包含文章列表和标题等 + * @throws WxErrorException 微信API调用异常 */ 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; - /** *
      * 删除永久素材
@@ -241,8 +200,8 @@ public interface WxMpMaterialService {
      * 
* * @param mediaId 永久素材的id - * @return the boolean - * @throws WxErrorException the wx error exception + * @return 删除是否成功,true表示成功,false表示失败 + * @throws WxErrorException 微信API调用异常 */ boolean materialDelete(String mediaId) throws WxErrorException; @@ -259,8 +218,8 @@ public interface WxMpMaterialService { * 接口url格式:https://api.weixin.qq.com/cgi-bin/material/get_materialcount?access_token=ACCESS_TOKEN * * - * @return the wx mp material count result - * @throws WxErrorException the wx error exception + * @return 素材统计结果,包含各类素材的数量 + * @throws WxErrorException 微信API调用异常 */ WxMpMaterialCountResult materialCount() throws WxErrorException; @@ -272,10 +231,10 @@ public interface WxMpMaterialService { * 接口url格式:https://api.weixin.qq.com/cgi-bin/material/batchget_material?access_token=ACCESS_TOKEN * * - * @param offset 从全部素材的该偏移位置开始返回,0表示从第一个素材 返回 + * @param offset 从全部素材的该偏移位置开始返回,0表示从第一个素材返回 * @param count 返回素材的数量,取值在1到20之间 - * @return the wx mp material news batch get result - * @throws WxErrorException the wx error exception + * @return 图文素材列表,包含文章列表和标题等信息 + * @throws WxErrorException 微信API调用异常 */ WxMpMaterialNewsBatchGetResult materialNewsBatchGet(int offset, int count) throws WxErrorException; @@ -288,10 +247,10 @@ public interface WxMpMaterialService { * * * @param type 媒体类型, 请看{@link me.chanjar.weixin.common.api.WxConsts} - * @param offset 从全部素材的该偏移位置开始返回,0表示从第一个素材 返回 + * @param offset 从全部素材的该偏移位置开始返回,0表示从第一个素材返回 * @param count 返回素材的数量,取值在1到20之间 - * @return the wx mp material file batch get result - * @throws WxErrorException the wx error exception + * @return 其他媒体素材列表,包含图片、语音、视频等素材信息 + * @throws WxErrorException 微信API调用异常 */ WxMpMaterialFileBatchGetResult materialFileBatchGet(String type, int offset, int count) 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/WxMpMenuService.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpMenuService.java index 3e78893005..ad1813ee85 100644 --- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpMenuService.java +++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpMenuService.java @@ -19,9 +19,9 @@ public interface WxMpMenuService { * 详情请见:https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1455782296&token=&lang=zh_CN * * - * @param menu the menu - * @return 如果是个性化菜单 ,则返回menuid,否则返回null - * @throws WxErrorException the wx error exception + * @param menu 菜单对象,包含菜单结构和配置信息 + * @return 如果是个性化菜单,则返回menuid,否则返回null + * @throws WxErrorException 微信API调用异常 */ String menuCreate(WxMenu menu) throws WxErrorException; @@ -33,9 +33,9 @@ public interface WxMpMenuService { * 详情请见:https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1455782296&token=&lang=zh_CN * * - * @param json the json - * @return 如果是个性化菜单 ,则返回menuid,否则返回null - * @throws WxErrorException the wx error exception + * @param json 菜单配置的JSON字符串,包含菜单结构和配置信息 + * @return 如果是个性化菜单,则返回menuid,否则返回null + * @throws WxErrorException 微信API调用异常 */ String menuCreate(String json) throws WxErrorException; @@ -45,7 +45,7 @@ public interface WxMpMenuService { * 详情请见: https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421141015&token=&lang=zh_CN * * - * @throws WxErrorException the wx error exception + * @throws WxErrorException 微信API调用异常 */ void menuDelete() throws WxErrorException; @@ -55,8 +55,8 @@ public interface WxMpMenuService { * 详情请见: https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1455782296&token=&lang=zh_CN * * - * @param menuId 个性化菜单的menuid - * @throws WxErrorException the wx error exception + * @param menuId 个性化菜单的menuid,通过创建个性化菜单时返回 + * @throws WxErrorException 微信API调用异常 */ void menuDelete(String menuId) throws WxErrorException; @@ -66,8 +66,8 @@ public interface WxMpMenuService { * 详情请见: https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421141014&token=&lang=zh_CN * * - * @return the wx mp menu - * @throws WxErrorException the wx error exception + * @return 当前公众号的自定义菜单配置 + * @throws WxErrorException 微信API调用异常 */ WxMpMenu menuGet() throws WxErrorException; @@ -77,9 +77,9 @@ public interface WxMpMenuService { * 详情请见: http://mp.weixin.qq.com/wiki/0/c48ccd12b69ae023159b4bfaa7c39c20.html * * - * @param userid 可以是粉丝的OpenID,也可以是粉丝的微信号。 - * @return the wx menu - * @throws WxErrorException the wx error exception + * @param userid 可以是粉丝的OpenID,也可以是粉丝的微信号 + * @return 匹配到的菜单配置 + * @throws WxErrorException 微信API调用异常 */ WxMenu menuTryMatch(String userid) throws WxErrorException; @@ -98,8 +98,8 @@ public interface WxMpMenuService { * https://api.weixin.qq.com/cgi-bin/get_current_selfmenu_info?access_token=ACCESS_TOKEN * * - * @return the self menu info - * @throws WxErrorException the wx error exception + * @return 自定义菜单配置信息,包含菜单结构和配置详情 + * @throws WxErrorException 微信API调用异常 */ WxMpGetSelfMenuInfoResult getSelfMenuInfo() throws WxErrorException; } 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..df923512c9 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 @@ -22,8 +22,8 @@ public interface WxMpQrcodeService { * * @param sceneId 场景值ID,临时二维码时为32位非0整型 * @param expireSeconds 该二维码有效时间,以秒为单位。 最大不超过2592000(即30天),此字段如果不填,则默认有效期为30秒。 - * @return the wx mp qr code ticket - * @throws WxErrorException the wx error exception + * @return 二维码ticket,可用于获取二维码图片 + * @throws WxErrorException 微信API调用异常 */ WxMpQrCodeTicket qrCodeCreateTmpTicket(int sceneId, Integer expireSeconds) throws WxErrorException; @@ -36,8 +36,8 @@ public interface WxMpQrcodeService { * * @param sceneStr 场景值ID(字符串形式的ID),字符串类型,长度限制为1到64 * @param expireSeconds 该二维码有效时间,以秒为单位。 最大不超过2592000(即30天),此字段如果不填,则默认有效期为30秒。 - * @return the wx mp qr code ticket - * @throws WxErrorException the wx error exception + * @return 二维码ticket,可用于获取二维码图片 + * @throws WxErrorException 微信API调用异常 */ WxMpQrCodeTicket qrCodeCreateTmpTicket(String sceneStr, Integer expireSeconds) throws WxErrorException; @@ -48,8 +48,8 @@ public interface WxMpQrcodeService { * * * @param sceneId 场景值ID,最大值为100000(目前参数只支持1--100000) - * @return the wx mp qr code ticket - * @throws WxErrorException the wx error exception + * @return 二维码ticket,可用于获取二维码图片 + * @throws WxErrorException 微信API调用异常 */ WxMpQrCodeTicket qrCodeCreateLastTicket(int sceneId) throws WxErrorException; @@ -60,8 +60,8 @@ public interface WxMpQrcodeService { * * * @param sceneStr 参数。字符串类型长度现在为1到64 - * @return the wx mp qr code ticket - * @throws WxErrorException the wx error exception + * @return 二维码ticket,可用于获取二维码图片 + * @throws WxErrorException 微信API调用异常 */ WxMpQrCodeTicket qrCodeCreateLastTicket(String sceneStr) throws WxErrorException; @@ -71,9 +71,9 @@ public interface WxMpQrcodeService { * 详情请见: 生成带参数的二维码 * * - * @param ticket 二维码ticket - * @return the file - * @throws WxErrorException the wx error exception + * @param ticket 二维码ticket,通过创建二维码接口获取 + * @return 二维码图片文件,jpg格式 + * @throws WxErrorException 微信API调用异常 */ File qrCodePicture(WxMpQrCodeTicket ticket) throws WxErrorException; @@ -85,11 +85,12 @@ public interface WxMpQrcodeService { * * @param ticket 二维码ticket * @param needShortUrl 是否需要压缩的二维码地址 - * @return the string - * @throws WxErrorException the wx error exception + * @return 二维码图片的URL地址 + * @throws WxErrorException 微信API调用异常 + * @deprecated 请使用 {@link #qrCodePictureUrl(String)} 方法 */ @Deprecated - String qrCodePictureUrl(String ticket, boolean needShortUrl) throws WxErrorException; + String qrCodePictureUrl(String ticket, boolean needShortUrl) throws WxErrorException; /** *
@@ -97,9 +98,9 @@ public interface WxMpQrcodeService {
      * 详情请见: 生成带参数的二维码
      * 
* - * @param ticket 二维码ticket - * @return the string - * @throws WxErrorException the wx error exception + * @param ticket 二维码ticket,通过创建二维码接口获取 + * @return 二维码图片的URL地址 + * @throws WxErrorException 微信API调用异常 */ String qrCodePictureUrl(String ticket) 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..468dced138 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 @@ -37,7 +37,7 @@ public interface WxMpService extends WxService { * @param longData 需要转换的长信息,不超过4KB * @param expireSeconds 短key有效期(单位秒),最大值为2592000(即30天),默认为2592000(30天) * @return shortKey 短key,15字节,base62编码(0-9/a-z/A-Z) - * @throws WxErrorException . + * @throws WxErrorException 微信API调用异常 */ String genShorten(String longData, Integer expireSeconds) throws WxErrorException; @@ -47,9 +47,9 @@ public interface WxMpService extends WxService { * 详情请见: https://developers.weixin.qq.com/doc/offiaccount/Account_Management/KEY_Shortener.html * * - * @param shortKey 短key - * @return WxMpShortKeyResult 解析结果 - * @throws WxErrorException . + * @param shortKey 短key,15字节,base62编码(0-9/a-z/A-Z) + * @return WxMpShortKeyResult 解析结果,包含原始长信息 + * @throws WxErrorException 微信API调用异常 */ WxMpShortKeyResult fetchShorten(String shortKey) throws WxErrorException; @@ -59,19 +59,19 @@ public interface WxMpService extends WxService { * 详情请见: http://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421135319&token=&lang=zh_CN * * - * @param timestamp 时间戳 - * @param nonce 随机串 - * @param signature 签名 - * @return 是否验证通过 boolean + * @param timestamp 时间戳,字符串格式 + * @param nonce 随机串,字符串格式 + * @param signature 签名,字符串格式 + * @return 是否验证通过,true表示验证通过,false表示验证失败 */ boolean checkSignature(String timestamp, String nonce, String signature); /** * 获取access_token, 不强制刷新access_token. * - * @return token access token - * @throws WxErrorException . - * @see #getAccessToken(boolean) #getAccessToken(boolean)#getAccessToken(boolean)#getAccessToken(boolean) + * @return token access token,字符串格式 + * @throws WxErrorException 微信API调用异常 + * @see #getAccessToken(boolean) 获取access_token,可选择是否强制刷新 */ String getAccessToken() throws WxErrorException; @@ -87,21 +87,21 @@ public interface WxMpService extends WxService { * 详情请见: http://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140183&token=&lang=zh_CN * * - * @param forceRefresh 是否强制刷新 - * @return token access token - * @throws WxErrorException . + * @param forceRefresh 是否强制刷新,true表示强制刷新,false表示使用缓存 + * @return token access token,字符串格式 + * @throws WxErrorException 微信API调用异常 */ String getAccessToken(boolean forceRefresh) throws WxErrorException; /** * 获得ticket,不强制刷新ticket. * - * @param type ticket 类型 - * @return ticket ticket - * @throws WxErrorException . - * @see #getTicket(TicketType, boolean) #getTicket(TicketType, boolean)#getTicket(TicketType, boolean)#getTicket(TicketType, boolean) + * @param ticketType ticket 类型,通过TicketType枚举指定 + * @return ticket ticket,字符串格式 + * @throws WxErrorException 微信API调用异常 + * @see #getTicket(TicketType, boolean) 获得ticket,可选择是否强制刷新 */ - String getTicket(TicketType type) throws WxErrorException; + String getTicket(TicketType ticketType) throws WxErrorException; /** *
@@ -109,19 +109,19 @@ public interface WxMpService extends WxService {
    * 获得时会检查 Token是否过期,如果过期了,那么就刷新一下,否则就什么都不干
    * 
* - * @param type ticket类型 - * @param forceRefresh 强制刷新 - * @return ticket ticket - * @throws WxErrorException . + * @param ticketType ticket类型,通过TicketType枚举指定 + * @param forceRefresh 强制刷新,true表示强制刷新,false表示使用缓存 + * @return ticket ticket,字符串格式 + * @throws WxErrorException 微信API调用异常 */ - String getTicket(TicketType type, boolean forceRefresh) throws WxErrorException; + String getTicket(TicketType ticketType, boolean forceRefresh) throws WxErrorException; /** * 获得jsapi_ticket,不强制刷新jsapi_ticket. * - * @return jsapi ticket - * @throws WxErrorException . - * @see #getJsapiTicket(boolean) #getJsapiTicket(boolean)#getJsapiTicket(boolean)#getJsapiTicket(boolean) + * @return jsapi ticket,字符串格式 + * @throws WxErrorException 微信API调用异常 + * @see #getJsapiTicket(boolean) 获得jsapi_ticket,可选择是否强制刷新 */ String getJsapiTicket() throws WxErrorException; @@ -133,9 +133,9 @@ public interface WxMpService extends WxService { * 详情请见:http://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421141115&token=&lang=zh_CN * * - * @param forceRefresh 强制刷新 - * @return jsapi ticket - * @throws WxErrorException . + * @param forceRefresh 强制刷新,true表示强制刷新,false表示使用缓存 + * @return jsapi ticket,字符串格式 + * @throws WxErrorException 微信API调用异常 */ String getJsapiTicket(boolean forceRefresh) throws WxErrorException; @@ -146,9 +146,9 @@ public interface WxMpService extends WxService { * 详情请见:http://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421141115&token=&lang=zh_CN * * - * @param url 地址 - * @return 生成的签名对象 wx jsapi signature - * @throws WxErrorException . + * @param url 当前网页的URL,不包括#及其后面部分 + * @return 生成的签名对象,包含签名、时间戳、随机串等信息 + * @throws WxErrorException 微信API调用异常 */ WxJsapiSignature createJsapiSignature(String url) throws WxErrorException; @@ -158,9 +158,10 @@ public interface WxMpService extends WxService { * 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=长链接转短链接接口 * * - * @param longUrl 长url - * @return 生成的短地址 string - * @throws WxErrorException . + * @param longUrl 长url,需要转换的原始URL + * @return 生成的短地址,字符串格式 + * @throws WxErrorException 微信API调用异常 + * @deprecated 请使用 {@link #genShorten(String, Integer)} 方法 */ @Deprecated String shortUrl(String longUrl) throws WxErrorException; @@ -171,9 +172,9 @@ public interface WxMpService extends WxService { * 详情请见:http://mp.weixin.qq.com/wiki/index.php?title=语义理解 * * - * @param semanticQuery 查询条件 - * @return 查询结果 wx mp semantic query result - * @throws WxErrorException . + * @param semanticQuery 查询条件,包含查询内容、类型等信息 + * @return 查询结果,包含语义理解的结果和建议回复 + * @throws WxErrorException 微信API调用异常 */ WxMpSemanticQueryResult semanticQuery(WxMpSemanticQuery semanticQuery) throws WxErrorException; @@ -187,7 +188,7 @@ public interface WxMpService extends WxService { * @param redirectUri 用户授权完成后的重定向链接,无需urlencode, 方法内会进行encode * @param scope 应用授权作用域,拥有多个作用域用逗号(,)分隔,网页应用目前仅填写snsapi_login即可 * @param state 非必填,用于保持请求和回调的状态,授权请求后原样带回给第三方。该参数可用于防止csrf攻击(跨站请求伪造攻击),建议第三方带上该参数,可设置为简单的随机数加session进行校验 - * @return url string + * @return url 构造好的授权登录URL,字符串格式 */ String buildQrConnectUrl(String redirectUri, String scope, String state); @@ -197,8 +198,8 @@ public interface WxMpService extends WxService { * http://mp.weixin.qq.com/wiki/0/2ad4b6bfd29f30f71d39616c2a0fcedc.html * * - * @return 微信服务器ip地址数组 string [ ] - * @throws WxErrorException . + * @return 微信服务器ip地址数组,包含所有微信服务器IP地址 + * @throws WxErrorException 微信API调用异常 */ String[] getCallbackIP() throws WxErrorException; @@ -209,10 +210,10 @@ public interface WxMpService extends WxService { * 为了帮助开发者排查回调连接失败的问题,提供这个网络检测的API。它可以对开发者URL做域名解析,然后对所有IP进行一次ping操作,得到丢包率和耗时。 * * - * @param action 执行的检测动作 - * @param operator 指定平台从某个运营商进行检测 - * @return 检测结果 wx net check result - * @throws WxErrorException . + * @param action 执行的检测动作,可选值:all(全部检测)、dns(仅域名解析)、ping(仅网络连通性检测) + * @param operator 指定平台从某个运营商进行检测,可选值:CHINANET(中国电信)、UNICOM(中国联通)、CAP(中国联通)、CUCC(中国联通) + * @return 检测结果,包含丢包率和耗时等信息 + * @throws WxErrorException 微信API调用异常 */ WxNetCheckResult netCheck(String action, String operator) throws WxErrorException; @@ -232,8 +233,8 @@ public interface WxMpService extends WxService { * https://api.weixin.qq.com/cgi-bin/get_current_autoreply_info?access_token=ACCESS_TOKEN * * - * @return 公众号的自动回复规则 current auto reply info - * @throws WxErrorException . + * @return 公众号的自动回复规则,包含关注后自动回复、消息自动回复、关键词自动回复等信息 + * @throws WxErrorException 微信API调用异常 */ WxMpCurrentAutoReplyInfo getCurrentAutoReplyInfo() throws WxErrorException; @@ -245,8 +246,8 @@ public interface WxMpService extends WxService { * * * - * @param appid 公众号的APPID - * @throws WxErrorException the wx error exception + * @param appid 公众号的APPID,需要清零调用的公众号的appid + * @throws WxErrorException 微信API调用异常 */ void clearQuota(String appid) throws WxErrorException; @@ -257,53 +258,53 @@ public interface WxMpService extends WxService { * 可以参考,{@link MediaUploadRequestExecutor}的实现方法 * * - * @param the type parameter - * @param the type parameter - * @param executor 执行器 - * @param url 接口地址 - * @param data 参数数据 - * @return 结果 t - * @throws WxErrorException 异常 + * @param 返回值类型 + * @param 参数类型 + * @param executor 执行器,用于处理请求和响应 + * @param url 接口地址,字符串格式 + * @param data 参数数据,根据API不同可能是不同类型 + * @return 结果,根据API不同可能是不同类型 + * @throws WxErrorException 微信API调用异常 */ T execute(RequestExecutor executor, String url, E data) throws WxErrorException; /** * 当本Service没有实现某个API的时候,可以用这个,针对所有微信API中的GET请求. * - * @param url 请求接口地址 - * @param queryParam 参数 - * @return 接口响应字符串 string - * @throws WxErrorException 异常 + * @param url 请求接口地址,通过WxMpApiUrl枚举指定 + * @param queryParam 参数,字符串格式,通常是URL查询参数 + * @return 接口响应字符串,JSON格式 + * @throws WxErrorException 微信API调用异常 */ String get(WxMpApiUrl url, String queryParam) throws WxErrorException; /** * 当本Service没有实现某个API的时候,可以用这个,针对所有微信API中的POST请求. * - * @param url 请求接口地址 - * @param postData 请求参数json值 - * @return 接口响应字符串 string - * @throws WxErrorException 异常 + * @param url 请求接口地址,通过WxMpApiUrl枚举指定 + * @param postData 请求参数json值,字符串格式 + * @return 接口响应字符串,JSON格式 + * @throws WxErrorException 微信API调用异常 */ String post(WxMpApiUrl url, String postData) throws WxErrorException; /** * 当本Service没有实现某个API的时候,可以用这个,针对所有微信API中的POST请求. * - * @param url 请求接口地址 - * @param obj 请求参数 - * @return 接口响应字符串 string - * @throws WxErrorException 异常 + * @param url 请求接口地址,通过WxMpApiUrl枚举指定 + * @param obj 请求参数,对象格式,会被序列化为JSON + * @return 接口响应字符串,JSON格式 + * @throws WxErrorException 微信API调用异常 */ String post(WxMpApiUrl url, Object obj) throws WxErrorException; /** * 当本Service没有实现某个API的时候,可以用这个,针对所有微信API中的POST请求. * - * @param url 请求接口地址 - * @param jsonObject 请求参数json对象 - * @return 接口响应字符串 string - * @throws WxErrorException 异常 + * @param url 请求接口地址,通过WxMpApiUrl枚举指定 + * @param jsonObject 请求参数json对象,JSON格式 + * @return 接口响应字符串,JSON格式 + * @throws WxErrorException 微信API调用异常 */ String post(WxMpApiUrl url, JsonObject jsonObject) throws WxErrorException; @@ -314,20 +315,20 @@ public interface WxMpService extends WxService { * 可以参考,{@link MediaUploadRequestExecutor}的实现方法 * * - * @param the type parameter - * @param the type parameter - * @param executor 执行器 - * @param url 接口地址 - * @param data 参数数据 - * @return 结果 t - * @throws WxErrorException 异常 + * @param 返回值类型 + * @param 参数类型 + * @param executor 执行器,用于处理请求和响应 + * @param url 接口地址,通过WxMpApiUrl枚举指定 + * @param data 参数数据,根据API不同可能是不同类型 + * @return 结果,根据API不同可能是不同类型 + * @throws WxErrorException 微信API调用异常 */ T execute(RequestExecutor executor, WxMpApiUrl url, E data) throws WxErrorException; /** * 设置当微信系统响应系统繁忙时,要等待多少 retrySleepMillis(ms) * 2^(重试次数 - 1) 再发起重试. * - * @param retrySleepMillis 默认:1000ms + * @param retrySleepMillis 重试等待时间,单位毫秒,默认1000ms */ void setRetrySleepMillis(int retrySleepMillis); @@ -337,36 +338,36 @@ public interface WxMpService extends WxService { * 默认:5次 * * - * @param maxRetryTimes 最大重试次数 + * @param maxRetryTimes 最大重试次数,默认5次 */ void setMaxRetryTimes(int maxRetryTimes); /** * 获取WxMpConfigStorage 对象. * - * @return WxMpConfigStorage wx mp config storage + * @return WxMpConfigStorage 微信公众号配置存储对象 */ WxMpConfigStorage getWxMpConfigStorage(); /** * 设置 {@link WxMpConfigStorage} 的实现. 兼容老版本 * - * @param wxConfigProvider . + * @param wxConfigProvider 微信公众号配置存储对象 */ void setWxMpConfigStorage(WxMpConfigStorage wxConfigProvider); /** * Map里 加入新的 {@link WxMpConfigStorage},适用于动态添加新的微信公众号配置. * - * @param mpId 公众号id - * @param configStorage 新的微信配置 + * @param mpId 公众号id,用于标识不同的公众号 + * @param configStorage 新的微信配置,微信公众号配置存储对象 */ void addConfigStorage(String mpId, WxMpConfigStorage configStorage); /** * 从 Map中 移除 {@link String mpId} 所对应的 {@link WxMpConfigStorage},适用于动态移除微信公众号配置. * - * @param mpId 对应公众号的标识 + * @param mpId 对应公众号的标识,用于标识不同的公众号 */ void removeConfigStorage(String mpId); @@ -374,14 +375,14 @@ public interface WxMpService extends WxService { * 注入多个 {@link WxMpConfigStorage} 的实现. 并为每个 {@link WxMpConfigStorage} 赋予不同的 {@link String mpId} 值 * 随机采用一个{@link String mpId}进行Http初始化操作 * - * @param configStorages WxMpConfigStorage map + * @param configStorages WxMpConfigStorage map,公众号id到配置存储对象的映射 */ void setMultiConfigStorages(Map configStorages); /** * 注入多个 {@link WxMpConfigStorage} 的实现. 并为每个 {@link WxMpConfigStorage} 赋予不同的 {@link String label} 值 * - * @param configStorages WxMpConfigStorage map + * @param configStorages WxMpConfigStorage map,公众号id到配置存储对象的映射 * @param defaultMpId 设置一个{@link WxMpConfigStorage} 所对应的{@link String mpId}进行Http初始化 */ void setMultiConfigStorages(Map configStorages, String defaultMpId); @@ -389,132 +390,146 @@ public interface WxMpService extends WxService { /** * 进行相应的公众号切换. * - * @param mpId 公众号标识 - * @return 切换是否成功 boolean + * @param mpId 公众号标识,用于标识不同的公众号 + * @return 切换是否成功,true表示成功,false表示失败 */ boolean switchover(String mpId); + /** + * 进行相应的公众号切换,支持自定义配置获取函数. + * + * @param mpId 公众号标识,用于标识不同的公众号 + * @param func 自定义配置获取函数,当配置不存在时使用 + * @return 切换是否成功,true表示成功,false表示失败 + */ boolean switchover(String mpId, Function func); /** * 进行相应的公众号切换. * - * @param mpId 公众号标识 + * @param mpId 公众号标识,用于标识不同的公众号 * @return 切换成功 ,则返回当前对象,方便链式调用,否则抛出异常 */ WxMpService switchoverTo(String mpId); + /** + * 进行相应的公众号切换,支持自定义配置获取函数. + * + * @param mpId 公众号标识,用于标识不同的公众号 + * @param func 自定义配置获取函数,当配置不存在时使用 + * @return 切换成功 ,则返回当前对象,方便链式调用,否则抛出异常 + */ WxMpService switchoverTo(String mpId, Function func); /** * 返回客服接口方法实现类,以方便调用其各个接口. * - * @return WxMpKefuService kefu service + * @return WxMpKefuService 客服服务接口 */ WxMpKefuService getKefuService(); /** * 返回素材相关接口方法的实现类对象,以方便调用其各个接口. * - * @return WxMpMaterialService material service + * @return WxMpMaterialService 素材服务接口 */ WxMpMaterialService getMaterialService(); /** * 返回菜单相关接口方法的实现类对象,以方便调用其各个接口. * - * @return WxMpMenuService menu service + * @return WxMpMenuService 菜单服务接口 */ WxMpMenuService getMenuService(); /** * 返回用户相关接口方法的实现类对象,以方便调用其各个接口. * - * @return WxMpUserService user service + * @return WxMpUserService 用户服务接口 */ WxMpUserService getUserService(); /** * 返回用户标签相关接口方法的实现类对象,以方便调用其各个接口. * - * @return WxMpUserTagService user tag service + * @return WxMpUserTagService 用户标签服务接口 */ WxMpUserTagService getUserTagService(); /** * 返回二维码相关接口方法的实现类对象,以方便调用其各个接口. * - * @return WxMpQrcodeService qrcode service + * @return WxMpQrcodeService 二维码服务接口 */ WxMpQrcodeService getQrcodeService(); /** * 返回卡券相关接口方法的实现类对象,以方便调用其各个接口. * - * @return WxMpCardService card service + * @return WxMpCardService 卡券服务接口 */ WxMpCardService getCardService(); /** * 返回数据分析统计相关接口方法的实现类对象,以方便调用其各个接口. * - * @return WxMpDataCubeService data cube service + * @return WxMpDataCubeService 数据分析服务接口 */ WxMpDataCubeService getDataCubeService(); /** * 返回用户黑名单管理相关接口方法的实现类对象,以方便调用其各个接口. * - * @return WxMpUserBlacklistService black list service + * @return WxMpUserBlacklistService 用户黑名单服务接口 */ WxMpUserBlacklistService getBlackListService(); /** * 返回门店管理相关接口方法的实现类对象,以方便调用其各个接口. * - * @return WxMpStoreService store service + * @return WxMpStoreService 门店服务接口 */ WxMpStoreService getStoreService(); /** * 返回模板消息相关接口方法的实现类对象,以方便调用其各个接口. * - * @return WxMpTemplateMsgService template msg service + * @return WxMpTemplateMsgService 模板消息服务接口 */ WxMpTemplateMsgService getTemplateMsgService(); /** * 返回一次性订阅消息相关接口方法的实现类对象,以方便调用其各个接口. * - * @return WxMpSubscribeMsgService subscribe msg service + * @return WxMpSubscribeMsgService 订阅消息服务接口 */ WxMpSubscribeMsgService getSubscribeMsgService(); /** * 返回硬件平台相关接口方法的实现类对象,以方便调用其各个接口. * - * @return WxMpDeviceService device service + * @return WxMpDeviceService 硬件平台服务接口 */ WxMpDeviceService getDeviceService(); /** * 返回摇一摇周边相关接口方法的实现类对象,以方便调用其各个接口. * - * @return WxMpShakeService shake service + * @return WxMpShakeService 摇一摇周边服务接口 */ WxMpShakeService getShakeService(); /** * 返回会员卡相关接口方法的实现类对象,以方便调用其各个接口. * - * @return WxMpMemberCardService member card service + * @return WxMpMemberCardService 会员卡服务接口 */ WxMpMemberCardService getMemberCardService(); /** * 返回营销相关接口方法的实现类对象,以方便调用其各个接口. * - * @return WxMpMarketingService marketing service + * @return WxMpMarketingService 营销服务接口 */ WxMpMarketingService getMarketingService(); @@ -526,329 +541,329 @@ public interface WxMpService extends WxService { /** * 获取RequestHttp对象. * - * @return RequestHttp对象 request http + * @return RequestHttp对象 HTTP请求处理对象 */ - RequestHttp getRequestHttp(); + RequestHttp getRequestHttp(); /** * 返回群发消息相关接口方法的实现类对象,以方便调用其各个接口. * - * @return WxMpMassMessageService mass message service + * @return WxMpMassMessageService 群发消息服务接口 */ WxMpMassMessageService getMassMessageService(); /** * 返回AI开放接口方法的实现类对象,以方便调用其各个接口. * - * @return WxMpAiOpenService ai open service + * @return WxMpAiOpenService AI开放服务接口 */ WxMpAiOpenService getAiOpenService(); /** * 返回WIFI接口方法的实现类对象,以方便调用其各个接口. * - * @return WxMpWifiService wifi service + * @return WxMpWifiService WIFI服务接口 */ WxMpWifiService getWifiService(); /** - * 返回WIFI接口方法的实现类对象,以方便调用其各个接口. + * 返回OCR接口方法的实现类对象,以方便调用其各个接口. * - * @return WxMpWifiService ocr service + * @return WxOcrService OCR服务接口 */ WxOcrService getOcrService(); /** * 返回图像处理接口的实现类对象,以方便调用其各个接口. * - * @return WxImgProcService img proc service + * @return WxImgProcService 图像处理服务接口 */ WxImgProcService getImgProcService(); /** * 返回电子发票报销方相关接口 * - * @return WxMpReimburseInvoiceService reimburse invoice service + * @return WxMpReimburseInvoiceService 电子发票报销方服务接口 */ WxMpReimburseInvoiceService getReimburseInvoiceService(); /** * 返回草稿箱相关接口 * - * @return WxMpDraftService draft service + * @return WxMpDraftService 草稿箱服务接口 */ WxMpDraftService getDraftService(); /** * 返回发布能力接口 * - * @return WxMpFreePublishService free publish service + * @return WxMpFreePublishService 发布能力服务接口 */ WxMpFreePublishService getFreePublishService(); /** - * . + * 设置电子发票报销方服务接口 * - * @param reimburseInvoiceService . + * @param reimburseInvoiceService 电子发票报销方服务接口 */ void setReimburseInvoiceService(WxMpReimburseInvoiceService reimburseInvoiceService); /** - * . + * 设置客服服务接口 * - * @param kefuService . + * @param kefuService 客服服务接口 */ void setKefuService(WxMpKefuService kefuService); /** - * . + * 设置素材服务接口 * - * @param materialService . + * @param materialService 素材服务接口 */ void setMaterialService(WxMpMaterialService materialService); /** - * . + * 设置菜单服务接口 * - * @param menuService . + * @param menuService 菜单服务接口 */ void setMenuService(WxMpMenuService menuService); /** - * . + * 设置用户服务接口 * - * @param userService . + * @param userService 用户服务接口 */ void setUserService(WxMpUserService userService); /** - * . + * 设置用户标签服务接口 * - * @param userTagService . + * @param userTagService 用户标签服务接口 */ void setUserTagService(WxMpUserTagService userTagService); /** - * . + * 设置二维码服务接口 * - * @param qrcodeService . + * @param qrcodeService 二维码服务接口 */ void setQrcodeService(WxMpQrcodeService qrcodeService); /** - * . + * 设置卡券服务接口 * - * @param cardService . + * @param cardService 卡券服务接口 */ void setCardService(WxMpCardService cardService); /** - * . + * 设置门店服务接口 * - * @param storeService . + * @param storeService 门店服务接口 */ void setStoreService(WxMpStoreService storeService); /** - * . + * 设置数据分析服务接口 * - * @param dataCubeService . + * @param dataCubeService 数据分析服务接口 */ void setDataCubeService(WxMpDataCubeService dataCubeService); /** - * . + * 设置用户黑名单服务接口 * - * @param blackListService . + * @param blackListService 用户黑名单服务接口 */ void setBlackListService(WxMpUserBlacklistService blackListService); /** - * . + * 设置模板消息服务接口 * - * @param templateMsgService . + * @param templateMsgService 模板消息服务接口 */ void setTemplateMsgService(WxMpTemplateMsgService templateMsgService); /** - * . + * 设置硬件平台服务接口 * - * @param deviceService . + * @param deviceService 硬件平台服务接口 */ void setDeviceService(WxMpDeviceService deviceService); /** - * . + * 设置摇一摇周边服务接口 * - * @param shakeService . + * @param shakeService 摇一摇周边服务接口 */ void setShakeService(WxMpShakeService shakeService); /** - * . + * 设置会员卡服务接口 * - * @param memberCardService . + * @param memberCardService 会员卡服务接口 */ void setMemberCardService(WxMpMemberCardService memberCardService); /** - * . + * 设置群发消息服务接口 * - * @param massMessageService . + * @param massMessageService 群发消息服务接口 */ void setMassMessageService(WxMpMassMessageService massMessageService); /** - * . + * 设置AI开放服务接口 * - * @param aiOpenService . + * @param aiOpenService AI开放服务接口 */ void setAiOpenService(WxMpAiOpenService aiOpenService); /** - * . + * 设置营销服务接口 * - * @param marketingService . + * @param marketingService 营销服务接口 */ void setMarketingService(WxMpMarketingService marketingService); /** - * . + * 设置OCR服务接口 * - * @param ocrService . + * @param ocrService OCR服务接口 */ void setOcrService(WxOcrService ocrService); /** - * . + * 设置图像处理服务接口 * - * @param imgProcService . + * @param imgProcService 图像处理服务接口 */ void setImgProcService(WxImgProcService imgProcService); /** * 返回评论数据管理接口方法的实现类对象,以方便调用其各个接口. * - * @return WxMpWifiService comment service + * @return WxMpCommentService 评论数据管理服务接口 */ WxMpCommentService getCommentService(); /** - * . + * 设置评论数据管理服务接口 * - * @param commentService . + * @param commentService 评论数据管理服务接口 */ void setCommentService(WxMpCommentService commentService); /** - * Gets oauth2 service. + * 获取OAuth2服务接口 * - * @return the oauth2 service + * @return WxOAuth2Service OAuth2服务接口 */ WxOAuth2Service getOAuth2Service(); /** - * Sets oauth2Service. + * 设置OAuth2服务接口 * - * @param oAuth2Service the o auth 2 service + * @param oAuth2Service OAuth2服务接口 */ void setOAuth2Service(WxOAuth2Service oAuth2Service); /** - * Gets guide service. + * 获取微信导购服务接口 * - * @return the guide service + * @return WxMpGuideService 微信导购服务接口 */ WxMpGuideService getGuideService(); /** - * Sets guide service. + * 设置微信导购服务接口 * - * @param guideService the guide service + * @param guideService 微信导购服务接口 */ void setGuideService(WxMpGuideService guideService); /** - * Gets guideBuyer service. + * 获取微信导购买家服务接口 * - * @return the guideBuyer service + * @return WxMpGuideBuyerService 微信导购买家服务接口 */ WxMpGuideBuyerService getGuideBuyerService(); /** - * Sets guideBuyer service. + * 设置微信导购买家服务接口 * - * @param guideBuyerService the guideBuyer service + * @param guideBuyerService 微信导购买家服务接口 */ void setGuideBuyerService(WxMpGuideBuyerService guideBuyerService); /** - * Gets guideTag service. + * 获取微信导购标签服务接口 * - * @return the guide service + * @return WxMpGuideTagService 微信导购标签服务接口 */ WxMpGuideTagService getGuideTagService(); /** - * Sets guideTag service. + * 设置微信导购标签服务接口 * - * @param guideTagService the guideTag service + * @param guideTagService 微信导购标签服务接口 */ void setGuideTagService(WxMpGuideTagService guideTagService); /** - * Gets guideMaterial service. + * 获取微信导购素材服务接口 * - * @return the guideMaterial service + * @return WxMpGuideMaterialService 微信导购素材服务接口 */ WxMpGuideMaterialService getGuideMaterialService(); /** - * Sets guideMaterial service. + * 设置微信导购素材服务接口 * - * @param guideMaterialService the guideMaterial service + * @param guideMaterialService 微信导购素材服务接口 */ void setGuideMaterialService(WxMpGuideMaterialService guideMaterialService); /** - * Gets guideMassedJob service. + * 获取微信导购批量任务服务接口 * - * @return the guideMassedJob service + * @return WxMpGuideMassedJobService 微信导购批量任务服务接口 */ WxMpGuideMassedJobService getGuideMassedJobService(); /** - * Sets guide service. + * 设置微信导购批量任务服务接口 * - * @param guideMassedJobService the guide service + * @param guideMassedJobService 微信导购批量任务服务接口 */ void setGuideMassedJobService(WxMpGuideMassedJobService guideMassedJobService); /** - * Gets merchant invoice service. + * 获取微信商户发票服务接口 * - * @return the merchant invoice service + * @return WxMpMerchantInvoiceService 微信商户发票服务接口 */ WxMpMerchantInvoiceService getMerchantInvoiceService(); /** - * Sets merchant invoice service. + * 设置微信商户发票服务接口 * - * @param merchantInvoiceService the merchant invoice service + * @param merchantInvoiceService 微信商户发票服务接口 */ void setMerchantInvoiceService(WxMpMerchantInvoiceService merchantInvoiceService); /** - * Sets draft service. + * 设置草稿箱服务接口 * - * @param draftService the draft service + * @param draftService 草稿箱服务接口 */ void setDraftService(WxMpDraftService draftService); /** - * Sets free publish service. + * 设置发布能力服务接口 * - * @param freePublishService the free publish service + * @param freePublishService 发布能力服务接口 */ void setFreePublishService(WxMpFreePublishService freePublishService); } 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/WxMpTemplateMsgService.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpTemplateMsgService.java
index 5605c93651..d84737909e 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpTemplateMsgService.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpTemplateMsgService.java
@@ -8,105 +8,178 @@
 import java.util.List;
 
 /**
- * 
  * 模板消息接口
- * http://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1433751277&token=&lang=zh_CN
+ * 

+ * 提供微信模板消息的发送、行业设置、模板管理等功能。 + * 模板消息用于在用户触发特定事件后,向用户发送重要的服务通知。 + *

+ *

+ * 详情请见:模板消息开发文档 + *

* Created by Binary Wang on 2016-10-14. + * * @author miller.lin - * @author Binary Wang
+ * @author Binary Wang */ public interface WxMpTemplateMsgService { - /** - *
-   * 设置所属行业
-   * 官方文档中暂未告知响应内容
-   * 详情请见:http://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1433751277&token=&lang=zh_CN
-   * 
- * - * @param wxMpIndustry 行业信息 - * @return 是否成功 industry - * @throws WxErrorException . - */ - boolean setIndustry(WxMpTemplateIndustry wxMpIndustry) throws WxErrorException; + /** + *
+     * 设置所属行业
+     * 官方文档中暂未告知响应内容
+     * 
+ * + * @param wxMpIndustry 行业信息 + * @return 是否成功设置行业 + * @throws WxErrorException 微信API调用异常,可能包括: + *
    + *
  • 40001 - 获取access_token时AppSecret错误,或者access_token无效
  • + *
  • 40002 - 请确保grant_type字段值为client_credential
  • + *
  • 40003 - appid对应公众号请开发者使用绑定的公众号测试
  • + *
  • 40004 - appid不正确
  • + *
  • 40006 - access_token超时
  • + *
  • 48001 - api功能未授权
  • + *
  • 45009 - 调用接口的QPS超限
  • + *
  • 其他业务错误码
  • + *
+ * @see 设置所属行业 + */ + boolean setIndustry(WxMpTemplateIndustry wxMpIndustry) throws WxErrorException; - /*** - *
-   * 获取设置的行业信息
-   * 详情请见:http://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1433751277&token=&lang=zh_CN
-   * 
- * - * @return wxMpIndustry industry - * @throws WxErrorException . - */ - WxMpTemplateIndustry getIndustry() throws WxErrorException; + /** + *
+     * 获取设置的行业信息
+     * 
+ * + * @return 行业信息对象 + * @throws WxErrorException 微信API调用异常,可能包括: + *
    + *
  • 40001 - 获取access_token时AppSecret错误,或者access_token无效
  • + *
  • 40002 - 请确保grant_type字段值为client_credential
  • + *
  • 40003 - appid对应公众号请开发者使用绑定的公众号测试
  • + *
  • 40004 - appid不正确
  • + *
  • 40006 - access_token超时
  • + *
  • 48001 - api功能未授权
  • + *
  • 45009 - 调用接口的QPS超限
  • + *
  • 其他业务错误码
  • + *
+ * @see 获取设置的行业信息 + */ + WxMpTemplateIndustry getIndustry() throws WxErrorException; - /** - *
-   * 发送模板消息
-   * 详情请见: http://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1433751277&token=&lang=zh_CN
-   * 
- * - * @param templateMessage 模板消息 - * @return 消息Id string - * @throws WxErrorException . - */ - String sendTemplateMsg(WxMpTemplateMessage templateMessage) throws WxErrorException; + /** + *
+     * 发送模板消息
+     * 
+ * + * @param templateMessage 模板消息对象 + * @return 消息ID,可用于查询模板消息发送状态 + * @throws WxErrorException 微信API调用异常,可能包括: + *
    + *
  • 40001 - 获取access_token时AppSecret错误,或者access_token无效
  • + *
  • 40002 - 请确保grant_type字段值为client_credential
  • + *
  • 40003 - appid对应公众号请开发者使用绑定的公众号测试
  • + *
  • 40004 - appid不正确
  • + *
  • 40006 - access_token超时
  • + *
  • 48001 - api功能未授权
  • + *
  • 45009 - 调用接口的QPS超限
  • + *
  • 其他业务错误码
  • + *
+ * @see 发送模板消息 + */ + String sendTemplateMsg(WxMpTemplateMessage templateMessage) throws WxErrorException; - /** - *
-   * 获得模板ID
-   * 从行业模板库选择模板到账号后台,获得模板ID的过程可在MP中完成
-   * 详情请见: http://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1433751277&token=&lang=zh_CN
-   * 接口地址格式:https://api.weixin.qq.com/cgi-bin/template/api_add_template?access_token=ACCESS_TOKEN
-   * 
- * - * @param shortTemplateId 模板库中模板的编号,有“TM**”和“OPENTMTM**”等形式 - * @return templateId 模板Id - * @throws WxErrorException . - * @deprecated 请使用 addTemplate(java.lang.String, java.util.List) - */ - @Deprecated - String addTemplate(String shortTemplateId) throws WxErrorException; + /** + *
+     * 获得模板ID
+     * 从行业模板库选择模板到账号后台,获得模板ID的过程可在MP中完成
+     * 
+ * + * @param shortTemplateId 模板库中模板的编号,有"TM**"和"OPENTMTM**"等形式 + * @return 模板ID + * @throws WxErrorException 微信API调用异常,可能包括: + *
    + *
  • 40001 - 获取access_token时AppSecret错误,或者access_token无效
  • + *
  • 40002 - 请确保grant_type字段值为client_credential
  • + *
  • 40003 - appid对应公众号请开发者使用绑定的公众号测试
  • + *
  • 40004 - appid不正确
  • + *
  • 40006 - access_token超时
  • + *
  • 48001 - api功能未授权
  • + *
  • 45009 - 调用接口的QPS超限
  • + *
  • 其他业务错误码
  • + *
+ * @deprecated 请使用 {@link #addTemplate(String, List)} + * @see 获得模板ID + */ + @Deprecated + String addTemplate(String shortTemplateId) throws WxErrorException; - /** - *
-   * 获得模板ID
-   * 从类目模板库选择模板到账号后台,获得模板ID的过程可在MP中完成
-   * 详情请见: http://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1433751277&token=&lang=zh_CN
-   * 接口地址格式:https://api.weixin.qq.com/cgi-bin/template/api_add_template?access_token=ACCESS_TOKEN
-   * 
- * - * @param shortTemplateId 模板库中模板的编号,有“TM**”和“OPENTMTM**”等形式,对于类目模板,为纯数字ID - * @param keywordNameList 选用的类目模板的关键词,按顺序传入,如果为空,或者关键词不在模板库中,会返回40246错误码 - * @return templateId 模板Id - * @throws WxErrorException . - */ - String addTemplate(String shortTemplateId, List keywordNameList) throws WxErrorException; + /** + *
+     * 获得模板ID
+     * 从类目模板库选择模板到账号后台,获得模板ID的过程可在MP中完成
+     * 
+ * + * @param shortTemplateId 模板库中模板的编号,有"TM**"和"OPENTMTM**"等形式,对于类目模板,为纯数字ID + * @param keywordNameList 选用的类目模板的关键词,按顺序传入,如果为空,或者关键词不在模板库中,会返回40246错误码 + * @return 模板ID + * @throws WxErrorException 微信API调用异常,可能包括: + *
    + *
  • 40001 - 获取access_token时AppSecret错误,或者access_token无效
  • + *
  • 40002 - 请确保grant_type字段值为client_credential
  • + *
  • 40003 - appid对应公众号请开发者使用绑定的公众号测试
  • + *
  • 40004 - appid不正确
  • + *
  • 40006 - access_token超时
  • + *
  • 48001 - api功能未授权
  • + *
  • 45009 - 调用接口的QPS超限
  • + *
  • 40246 - 关键词不在模板库中
  • + *
  • 其他业务错误码
  • + *
+ * @see 获得模板ID + */ + String addTemplate(String shortTemplateId, List keywordNameList) throws WxErrorException; - /** - *
-   * 获取模板列表
-   * 获取已添加至账号下所有模板列表,可在MP中查看模板列表信息,为方便第三方开发者,提供通过接口调用的方式来获取账号下所有模板信息
-   * 详情请见: http://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1433751277&token=&lang=zh_CN
-   * 接口地址格式:https://api.weixin.qq.com/cgi-bin/template/get_all_private_template?access_token=ACCESS_TOKEN
-   * 
- * - * @return templateId 模板Id - * @throws WxErrorException . - */ - List getAllPrivateTemplate() throws WxErrorException; + /** + *
+     * 获取模板列表
+     * 获取已添加至账号下所有模板列表,可在MP中查看模板列表信息,为方便第三方开发者,提供通过接口调用的方式来获取账号下所有模板信息
+     * 
+ * + * @return 模板列表,包含所有已添加的模板信息 + * @throws WxErrorException 微信API调用异常,可能包括: + *
    + *
  • 40001 - 获取access_token时AppSecret错误,或者access_token无效
  • + *
  • 40002 - 请确保grant_type字段值为client_credential
  • + *
  • 40003 - appid对应公众号请开发者使用绑定的公众号测试
  • + *
  • 40004 - appid不正确
  • + *
  • 40006 - access_token超时
  • + *
  • 48001 - api功能未授权
  • + *
  • 45009 - 调用接口的QPS超限
  • + *
  • 其他业务错误码
  • + *
+ * @see 获取模板列表 + */ + List getAllPrivateTemplate() throws WxErrorException; - /** - *
-   * 删除模板
-   * 删除模板可在MP中完成,为方便第三方开发者,提供通过接口调用的方式来删除某账号下的模板
-   * 详情请见: http://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1433751277&token=&lang=zh_CN
-   * 接口地址格式:https://api.weixin.qq.com/cgi-bin/template/del_private_template?access_token=ACCESS_TOKEN
-   * 
- * - * @param templateId 模板Id - * @return . boolean - * @throws WxErrorException . - */ - boolean delPrivateTemplate(String templateId) throws WxErrorException; + /** + *
+     * 删除模板
+     * 删除模板可在MP中完成,为方便第三方开发者,提供通过接口调用的方式来删除某账号下的模板
+     * 
+ * + * @param templateId 模板ID + * @return 是否成功删除 + * @throws WxErrorException 微信API调用异常,可能包括: + *
    + *
  • 40001 - 获取access_token时AppSecret错误,或者access_token无效
  • + *
  • 40002 - 请确保grant_type字段值为client_credential
  • + *
  • 40003 - appid对应公众号请开发者使用绑定的公众号测试
  • + *
  • 40004 - appid不正确
  • + *
  • 40006 - access_token超时
  • + *
  • 48001 - api功能未授权
  • + *
  • 45009 - 调用接口的QPS超限
  • + *
  • 其他业务错误码
  • + *
+ * @see 删除模板 + */ + boolean delPrivateTemplate(String templateId) throws WxErrorException; } diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpUserService.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpUserService.java index 882fe93c00..d696c512ee 100644 --- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpUserService.java +++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpUserService.java @@ -22,9 +22,9 @@ public interface WxMpUserService { * 接口地址:https://api.weixin.qq.com/cgi-bin/user/info/updateremark?access_token=ACCESS_TOKEN *
* - * @param openid 用户openid - * @param remark 备注名 - * @throws WxErrorException the wx error exception + * @param openid 用户openid,标识具体的用户 + * @param remark 备注名,长度限制为30字符以内 + * @throws WxErrorException 微信API调用异常 */ void userUpdateRemark(String openid, String remark) throws WxErrorException; @@ -36,9 +36,9 @@ public interface WxMpUserService { * 接口地址:https://api.weixin.qq.com/cgi-bin/user/info?access_token=ACCESS_TOKEN&openid=OPENID&lang=zh_CN * * - * @param openid 用户openid - * @return the wx mp user - * @throws WxErrorException the wx error exception + * @param openid 用户openid,标识具体的用户 + * @return 用户基本信息,包含昵称、头像、性别、关注时间等 + * @throws WxErrorException 微信API调用异常 */ WxMpUser userInfo(String openid) throws WxErrorException; @@ -50,10 +50,10 @@ public interface WxMpUserService { * 接口地址:https://api.weixin.qq.com/cgi-bin/user/info?access_token=ACCESS_TOKEN&openid=OPENID&lang=zh_CN * * - * @param openid 用户openid + * @param openid 用户openid,标识具体的用户 * @param lang 语言,zh_CN 简体(默认),zh_TW 繁体,en 英语 - * @return the wx mp user - * @throws WxErrorException the wx error exception + * @return 用户基本信息,包含昵称、头像、性别、关注时间等 + * @throws WxErrorException 微信API调用异常 */ WxMpUser userInfo(String openid, String lang) throws WxErrorException; @@ -66,9 +66,9 @@ public interface WxMpUserService { * 接口地址:https://api.weixin.qq.com/cgi-bin/user/info/batchget?access_token=ACCESS_TOKEN * * - * @param openidList 用户openid列表 - * @return the list - * @throws WxErrorException the wx error exception + * @param openidList 用户openid列表,最多100个 + * @return 用户基本信息列表,包含每个用户的基本信息 + * @throws WxErrorException 微信API调用异常 */ List userInfoList(List openidList) throws WxErrorException; @@ -81,9 +81,9 @@ public interface WxMpUserService { * 接口地址:https://api.weixin.qq.com/cgi-bin/user/info/batchget?access_token=ACCESS_TOKEN * * - * @param userQuery 详细查询参数 - * @return the list - * @throws WxErrorException the wx error exception + * @param userQuery 详细查询参数,包含openid列表和语言设置 + * @return 用户基本信息列表,包含每个用户的基本信息 + * @throws WxErrorException 微信API调用异常 */ List userInfoList(WxMpUserQuery userQuery) throws WxErrorException; @@ -99,8 +99,8 @@ public interface WxMpUserService { * * * @param nextOpenid 可选,第一个拉取的OPENID,null为从头开始拉取 - * @return the wx mp user list - * @throws WxErrorException the wx error exception + * @return 用户列表,包含关注者OpenID列表和下一个OpenID + * @throws WxErrorException 微信API调用异常 */ WxMpUserList userList(String nextOpenid) throws WxErrorException; @@ -109,9 +109,13 @@ public interface WxMpUserService { * 获取用户列表(全部) * 公众号可通过本接口来获取账号的关注者列表, * 关注者列表由一串OpenID(加密后的微信号,每个用户对每个公众号的OpenID是唯一的)组成。 - * @return the wx mp user list - * @throws WxErrorException the wx error exception - * @see #userList(java.lang.String) #userList(java.lang.String)的增强,内部进行了多次数据拉取的汇总 详情请见: http://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140840&token=&lang=zh_CN http请求方式: GET(请使用https协议) 接口地址:https://api.weixin.qq.com/cgi-bin/user/get?access_token=ACCESS_TOKEN&next_openid=NEXT_OPENID + * @return 用户列表,包含所有关注者的OpenID列表 + * @throws WxErrorException 微信API调用异常 + * @see #userList(String) #userList(String)的增强,内部进行了多次数据拉取的汇总 + * 详情请见: http://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140840&token=&lang=zh_CN + * http请求方式: GET(请使用https协议) + * 接口地址:https://api.weixin.qq.com/cgi-bin/user/get?access_token=ACCESS_TOKEN&next_openid=NEXT_OPENID + * */ WxMpUserList userList() throws WxErrorException; @@ -126,8 +130,8 @@ public interface WxMpUserService { * * @param fromAppid 原公众号的 appid * @param openidList 需要转换的openid,这些必须是旧账号目前关注的才行,否则会出错;一次最多100个 - * @return the list - * @throws WxErrorException the wx error exception + * @return openid转换结果列表,包含原openid和新openid的映射关系 + * @throws WxErrorException 微信API调用异常 */ List changeOpenid(String fromAppid, List openidList) throws WxErrorException; } diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpUserTagService.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpUserTagService.java index 3f1b7223c7..8c131874c1 100644 --- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpUserTagService.java +++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpUserTagService.java @@ -8,6 +8,14 @@ /** * 用户标签管理相关接口 + *

+ * 提供微信公众号用户标签的创建、查询、更新、删除等功能。 + * 通过标签可以方便地对用户进行分组管理和精准营销。 + * 一个公众号最多可以创建100个标签。 + *

+ *

+ * 详情请见:用户标签管理 + *

* Created by Binary Wang on 2016/9/2. * * @author Binary Wang @@ -17,110 +25,193 @@ public interface WxMpUserTagService { *
      * 创建标签
      * 一个公众号,最多可以创建100个标签。
-     * 详情请见:用户标签管理
-     * 接口url格式: https://api.weixin.qq.com/cgi-bin/tags/create?access_token=ACCESS_TOKEN
      * 
* * @param name 标签名字(30个字符以内) - * @return the wx user tag - * @throws WxErrorException the wx error exception + * @return 创建的标签对象,包含标签ID等信息 + * @throws WxErrorException 微信API调用异常,可能包括: + *
    + *
  • 40001 - 获取access_token时AppSecret错误,或者access_token无效
  • + *
  • 40002 - 请确保grant_type字段值为client_credential
  • + *
  • 40003 - appid对应公众号请开发者使用绑定的公众号测试
  • + *
  • 40004 - appid不正确
  • + *
  • 40006 - access_token超时
  • + *
  • 48001 - api功能未授权
  • + *
  • 45009 - 调用接口的QPS超限
  • + *
  • 其他业务错误码
  • + *
+ * @see 用户标签管理 + * @see 创建标签接口 */ WxUserTag tagCreate(String name) throws WxErrorException; /** *
      * 获取公众号已创建的标签
-     * 详情请见:用户标签管理
-     * 接口url格式: https://api.weixin.qq.com/cgi-bin/tags/get?access_token=ACCESS_TOKEN
      * 
* - * @return the list - * @throws WxErrorException the wx error exception + * @return 标签列表,包含所有已创建的标签信息 + * @throws WxErrorException 微信API调用异常,可能包括: + *
    + *
  • 40001 - 获取access_token时AppSecret错误,或者access_token无效
  • + *
  • 40002 - 请确保grant_type字段值为client_credential
  • + *
  • 40003 - appid对应公众号请开发者使用绑定的公众号测试
  • + *
  • 40004 - appid不正确
  • + *
  • 40006 - access_token超时
  • + *
  • 48001 - api功能未授权
  • + *
  • 45009 - 调用接口的QPS超限
  • + *
  • 其他业务错误码
  • + *
+ * @see 用户标签管理 + * @see 获取标签接口 */ List tagGet() throws WxErrorException; /** *
      * 编辑标签
-     * 详情请见:用户标签管理
-     * 接口url格式: https://api.weixin.qq.com/cgi-bin/tags/update?access_token=ACCESS_TOKEN
+     * 可以修改标签的名称,但不能修改标签ID。
      * 
* - * @param tagId the tag id - * @param name the name - * @return the boolean - * @throws WxErrorException the wx error exception + * @param tagId 标签ID,不能为null + * @param name 新的标签名字(30个字符以内) + * @return 操作是否成功,true表示成功,false表示失败 + * @throws WxErrorException 微信API调用异常,可能包括: + *
    + *
  • 40001 - 获取access_token时AppSecret错误,或者access_token无效
  • + *
  • 40002 - 请确保grant_type字段值为client_credential
  • + *
  • 40003 - appid对应公众号请开发者使用绑定的公众号测试
  • + *
  • 40004 - appid不正确
  • + *
  • 40006 - access_token超时
  • + *
  • 48001 - api功能未授权
  • + *
  • 45009 - 调用接口的QPS超限
  • + *
  • 其他业务错误码
  • + *
+ * @see 用户标签管理 + * @see 编辑标签接口 */ Boolean tagUpdate(Long tagId, String name) throws WxErrorException; /** *
      * 删除标签
-     * 详情请见:用户标签管理
-     * 接口url格式: https://api.weixin.qq.com/cgi-bin/tags/delete?access_token=ACCESS_TOKEN
+     * 删除标签后,该标签下的所有用户将被取消标签。
      * 
* - * @param tagId the tag id - * @return the boolean - * @throws WxErrorException the wx error exception + * @param tagId 标签ID,不能为null + * @return 操作是否成功,true表示成功,false表示失败 + * @throws WxErrorException 微信API调用异常,可能包括: + *
    + *
  • 40001 - 获取access_token时AppSecret错误,或者access_token无效
  • + *
  • 40002 - 请确保grant_type字段值为client_credential
  • + *
  • 40003 - appid对应公众号请开发者使用绑定的公众号测试
  • + *
  • 40004 - appid不正确
  • + *
  • 40006 - access_token超时
  • + *
  • 48001 - api功能未授权
  • + *
  • 45009 - 调用接口的QPS超限
  • + *
  • 其他业务错误码
  • + *
+ * @see 用户标签管理 + * @see 删除标签接口 */ Boolean tagDelete(Long tagId) throws WxErrorException; /** *
      * 获取标签下粉丝列表
-     * 详情请见:用户标签管理
-     * 接口url格式: https://api.weixin.qq.com/cgi-bin/user/tag/get?access_token=ACCESS_TOKEN
+     * 可用于获取某个标签下的所有用户信息,支持分页查询。
      * 
* - * @param tagId the tag id - * @param nextOpenid the next openid - * @return the wx tag list user - * @throws WxErrorException the wx error exception + * @param tagId 标签ID,不能为null + * @param nextOpenid 第一个拉取用户的openid,不填从头开始拉取 + * @return 标签下粉丝列表对象,包含用户信息和分页信息 + * @throws WxErrorException 微信API调用异常,可能包括: + *
    + *
  • 40001 - 获取access_token时AppSecret错误,或者access_token无效
  • + *
  • 40002 - 请确保grant_type字段值为client_credential
  • + *
  • 40003 - appid对应公众号请开发者使用绑定的公众号测试
  • + *
  • 40004 - appid不正确
  • + *
  • 40006 - access_token超时
  • + *
  • 48001 - api功能未授权
  • + *
  • 45009 - 调用接口的QPS超限
  • + *
  • 其他业务错误码
  • + *
+ * @see 用户标签管理 + * @see 获取标签下粉丝列表接口 */ - WxTagListUser tagListUser(Long tagId, String nextOpenid) - throws WxErrorException; + WxTagListUser tagListUser(Long tagId, String nextOpenid) throws WxErrorException; /** *
      * 批量为用户打标签
-     * 详情请见:用户标签管理
-     * 接口url格式: https://api.weixin.qq.com/cgi-bin/tags/members/batchtagging?access_token=ACCESS_TOKEN
+     * 可以为多个用户同时打上同一个标签。
      * 
* - * @param tagId the tag id - * @param openids the openids - * @return the boolean - * @throws WxErrorException the wx error exception + * @param tagId 标签ID,不能为null + * @param openids 用户openid数组,不能为null或空数组 + * @return 操作是否成功,true表示成功,false表示失败 + * @throws WxErrorException 微信API调用异常,可能包括: + *
    + *
  • 40001 - 获取access_token时AppSecret错误,或者access_token无效
  • + *
  • 40002 - 请确保grant_type字段值为client_credential
  • + *
  • 40003 - appid对应公众号请开发者使用绑定的公众号测试
  • + *
  • 40004 - appid不正确
  • + *
  • 40006 - access_token超时
  • + *
  • 48001 - api功能未授权
  • + *
  • 45009 - 调用接口的QPS超限
  • + *
  • 其他业务错误码
  • + *
+ * @see 用户标签管理 + * @see 批量为用户打标签接口 */ boolean batchTagging(Long tagId, String[] openids) throws WxErrorException; /** *
      * 批量为用户取消标签
-     * 详情请见:用户标签管理
-     * 接口url格式: https://api.weixin.qq.com/cgi-bin/tags/members/batchuntagging?access_token=ACCESS_TOKEN
+     * 可以为多个用户同时取消同一个标签。
      * 
* - * @param tagId the tag id - * @param openids the openids - * @return the boolean - * @throws WxErrorException the wx error exception + * @param tagId 标签ID,不能为null + * @param openids 用户openid数组,不能为null或空数组 + * @return 操作是否成功,true表示成功,false表示失败 + * @throws WxErrorException 微信API调用异常,可能包括: + *
    + *
  • 40001 - 获取access_token时AppSecret错误,或者access_token无效
  • + *
  • 40002 - 请确保grant_type字段值为client_credential
  • + *
  • 40003 - appid对应公众号请开发者使用绑定的公众号测试
  • + *
  • 40004 - appid不正确
  • + *
  • 40006 - access_token超时
  • + *
  • 48001 - api功能未授权
  • + *
  • 45009 - 调用接口的QPS超限
  • + *
  • 其他业务错误码
  • + *
+ * @see 用户标签管理 + * @see 批量为用户取消标签接口 */ boolean batchUntagging(Long tagId, String[] openids) throws WxErrorException; - /** *
      * 获取用户身上的标签列表
-     * 详情请见:用户标签管理
-     * 接口url格式: https://api.weixin.qq.com/cgi-bin/tags/getidlist?access_token=ACCESS_TOKEN
+     * 可查询某个用户被打上的所有标签ID。
      * 
* - * @param openid the openid - * @return 标签Id的列表 list - * @throws WxErrorException the wx error exception + * @param openid 用户的openid,不能为null或空字符串 + * @return 标签ID的列表,表示该用户被打上的所有标签 + * @throws WxErrorException 微信API调用异常,可能包括: + *
    + *
  • 40001 - 获取access_token时AppSecret错误,或者access_token无效
  • + *
  • 40002 - 请确保grant_type字段值为client_credential
  • + *
  • 40003 - appid对应公众号请开发者使用绑定的公众号测试
  • + *
  • 40004 - appid不正确
  • + *
  • 40006 - access_token超时
  • + *
  • 48001 - api功能未授权
  • + *
  • 45009 - 调用接口的QPS超限
  • + *
  • 其他业务错误码
  • + *
+ * @see 用户标签管理 + * @see 获取用户身上的标签列表接口 */ List userTagList(String openid) throws WxErrorException; - } 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/WxMpAiOpenServiceImpl.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpAiOpenServiceImpl.java index 9c9bbe84c4..a6dd8135b6 100644 --- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpAiOpenServiceImpl.java +++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpAiOpenServiceImpl.java @@ -15,59 +15,57 @@ import static me.chanjar.weixin.mp.enums.WxMpApiUrl.AiOpen.*; /** - *
- *  Created by BinaryWang on 2018/6/9.
- * 
+ * Created by BinaryWang on 2018/6/9. * * @author Binary Wang */ @RequiredArgsConstructor public class WxMpAiOpenServiceImpl implements WxMpAiOpenService { - private final WxMpService wxMpService; + private final WxMpService wxMpService; - @Override - public void uploadVoice(String voiceId, AiLangType lang, File voiceFile) throws WxErrorException { - if (lang == null) { - lang = AiLangType.zh_CN; + @Override + public void uploadVoice(String voiceId, AiLangType lang, File voiceFile) throws WxErrorException { + if (lang == null) { + lang = AiLangType.zh_CN; + } + + this.wxMpService.execute(VoiceUploadRequestExecutor.create(this.wxMpService.getRequestHttp()), + String.format(VOICE_UPLOAD_URL.getUrl(this.wxMpService.getWxMpConfigStorage()), "mp3", voiceId, lang.getCode()), + voiceFile); } - this.wxMpService.execute(VoiceUploadRequestExecutor.create(this.wxMpService.getRequestHttp()), - String.format(VOICE_UPLOAD_URL.getUrl(this.wxMpService.getWxMpConfigStorage()), "mp3", voiceId, lang.getCode()), - voiceFile); - } + @Override + public String queryRecognitionResult(String voiceId, AiLangType lang) throws WxErrorException { + if (lang == null) { + lang = AiLangType.zh_CN; + } - @Override - public String recogniseVoice(String voiceId, AiLangType lang, File voiceFile) throws WxErrorException { - this.uploadVoice(voiceId, lang, voiceFile); - return this.queryRecognitionResult(voiceId, lang); - } + final String response = this.wxMpService.get(VOICE_QUERY_RESULT_URL, + String.format("voice_id=%s&lang=%s", voiceId, lang.getCode())); + WxError error = WxError.fromJson(response, WxType.MP); + if (error.getErrorCode() != 0) { + throw new WxErrorException(error); + } - @Override - public String translate(AiLangType langFrom, AiLangType langTo, String content) throws WxErrorException { - String response = this.wxMpService.post(String.format(TRANSLATE_URL.getUrl(this.wxMpService.getWxMpConfigStorage()), - langFrom.getCode(), langTo.getCode()), content); + return GsonParser.parse(response).get("result").getAsString(); + } - WxError error = WxError.fromJson(response, WxType.MP); - if (error.getErrorCode() != 0) { - throw new WxErrorException(error); + @Override + public String recogniseVoice(String voiceId, AiLangType lang, File voiceFile) throws WxErrorException { + this.uploadVoice(voiceId, lang, voiceFile); + return this.queryRecognitionResult(voiceId, lang); } - return GsonParser.parse(response).get("to_content").getAsString(); - } + @Override + public String translate(AiLangType langFrom, AiLangType langTo, String content) throws WxErrorException { + String response = this.wxMpService.post(String.format(TRANSLATE_URL.getUrl(this.wxMpService.getWxMpConfigStorage()), + langFrom.getCode(), langTo.getCode()), content); - @Override - public String queryRecognitionResult(String voiceId, AiLangType lang) throws WxErrorException { - if (lang == null) { - lang = AiLangType.zh_CN; - } + WxError error = WxError.fromJson(response, WxType.MP); + if (error.getErrorCode() != 0) { + throw new WxErrorException(error); + } - final String response = this.wxMpService.get(VOICE_QUERY_RESULT_URL, - String.format("voice_id=%s&lang=%s", voiceId, lang.getCode())); - WxError error = WxError.fromJson(response, WxType.MP); - if (error.getErrorCode() != 0) { - throw new WxErrorException(error); + return GsonParser.parse(response).get("to_content").getAsString(); } - - return GsonParser.parse(response).get("result").getAsString(); - } } 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 6d4869b6a1..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 @@ -50,12 +50,12 @@ public void initHttp() { clientBuilder.proxy(getRequestHttpProxy().getProxy()); //设置授权 - clientBuilder.authenticator(new Authenticator() { + clientBuilder.proxyAuthenticator(new Authenticator() { @Override public Request authenticate(Route route, Response response) throws IOException { String credential = Credentials.basic(httpProxy.getProxyUsername(), httpProxy.getProxyPassword()); return response.request().newBuilder() - .header("Authorization", credential) + .header("Proxy-Authorization", credential) .build(); } }); 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/WxMpUserTagServiceImpl.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpUserTagServiceImpl.java index 007942f096..63a3208e4b 100644 --- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpUserTagServiceImpl.java +++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpUserTagServiceImpl.java @@ -46,10 +46,10 @@ public List tagGet() throws WxErrorException { } @Override - public Boolean tagUpdate(Long id, String name) throws WxErrorException { + public Boolean tagUpdate(Long tagId, String name) throws WxErrorException { JsonObject json = new JsonObject(); JsonObject tagJson = new JsonObject(); - tagJson.addProperty("id", id); + tagJson.addProperty("id", tagId); tagJson.addProperty("name", name); json.add("tag", tagJson); @@ -63,10 +63,10 @@ public Boolean tagUpdate(Long id, String name) throws WxErrorException { } @Override - public Boolean tagDelete(Long id) throws WxErrorException { + public Boolean tagDelete(Long tagId) throws WxErrorException { JsonObject json = new JsonObject(); JsonObject tagJson = new JsonObject(); - tagJson.addProperty("id", id); + tagJson.addProperty("id", tagId); json.add("tag", tagJson); String responseContent = this.wxMpService.post(TAGS_DELETE, json.toString()); 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/bean/message/WxMpXmlMessage.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/message/WxMpXmlMessage.java index 27b7eaecc7..3d5f4ac3a0 100644 --- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/message/WxMpXmlMessage.java +++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/message/WxMpXmlMessage.java @@ -186,6 +186,22 @@ public class WxMpXmlMessage implements Serializable { @JacksonXmlCData private String unionId; + @XStreamAlias("ret") + @JacksonXmlProperty(localName = "ret") + private Integer ret; + + @XStreamAlias("nickname") + @JacksonXmlProperty(localName = "nickname") + private String nickname; + + @XStreamAlias("first") + @JacksonXmlProperty(localName = "first") + private String first; + + @XStreamAlias("second") + @JacksonXmlProperty(localName = "second") + private String second; + /////////////////////////////////////// // 群发消息返回的结果 /////////////////////////////////////// diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/subscribe/WxMpSubscribeMessage.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/subscribe/WxMpSubscribeMessage.java index d2695959e8..6820d103b8 100644 --- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/subscribe/WxMpSubscribeMessage.java +++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/subscribe/WxMpSubscribeMessage.java @@ -17,7 +17,7 @@ @NoArgsConstructor @Builder @AllArgsConstructor -public class WxMpSubscribeMessage { +public class WxMpSubscribeMessage implements Serializable { /** * 接收者openid. 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-mp/src/test/java/me/chanjar/weixin/mp/bean/subscribe/WxMpSubscribeMessageTest.java b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/bean/subscribe/WxMpSubscribeMessageTest.java
index 078ad51570..684211659f 100644
--- a/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/bean/subscribe/WxMpSubscribeMessageTest.java
+++ b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/bean/subscribe/WxMpSubscribeMessageTest.java
@@ -2,6 +2,9 @@
 
 import org.testng.annotations.*;
 
+import java.io.Serializable;
+import java.util.Arrays;
+
 import static org.testng.AssertJUnit.*;
 
 /**
@@ -43,4 +46,9 @@ public void testToJson() {
     assertEquals(message.toJson(), actual);
 
   }
+
+  @Test
+  void testWxMpSubscribeMessageIsSerializable() {
+    assertTrue(Arrays.stream(WxMpSubscribeMessage.class.getInterfaces()).anyMatch(anInterface -> anInterface.isInstance(Serializable.class)));
+  }
 }
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 0146f516ad..f354cb16f8 100644
--- a/weixin-java-open/pom.xml
+++ b/weixin-java-open/pom.xml
@@ -7,7 +7,7 @@
   
     com.github.binarywang
     wx-java
-    4.7.0
+    4.7.9.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/WxOpenComponentService.java b/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/WxOpenComponentService.java
index d8e1795e05..eb334f7b1c 100644
--- a/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/WxOpenComponentService.java
+++ b/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/WxOpenComponentService.java
@@ -203,6 +203,21 @@ public interface WxOpenComponentService {
 
   String COMPONENT_CLEAR_QUOTA_URL = "https://api.weixin.qq.com/cgi-bin/component/clear_quota/v2";
 
+  /**
+   * 设置第三方平台服务器域名
+   */
+  String API_MODIFY_WXA_SERVER_DOMAIN = "https://api.weixin.qq.com/cgi-bin/component/modify_wxa_server_domain";
+
+  /**
+   * 获取第三方平台业务域名校验文件
+   */
+  String API_GET_DOMAIN_CONFIRM_FILE = "https://api.weixin.qq.com/cgi-bin/component/get_domain_confirmfile";
+
+  /**
+   * 设置第三方平台业务域名
+   */
+  String API_MODIFY_WXA_JUMP_DOMAIN = "https://api.weixin.qq.com/cgi-bin/component/modify_wxa_jump_domain";
+
   /**
    * Gets wx mp service by appid.
    *
@@ -1117,4 +1132,53 @@ public interface WxOpenComponentService {
    */
   WxOpenResult applySetOrderPathInfo(WxOpenMaApplyOrderPathInfo info) throws WxErrorException;
 
+  /**
+   * 设置第三方平台服务器域名
+   * 文档地址
+   *
+   * @param action           add添加, delete删除, set覆盖, get获取
+   * @param requestDomains   request 合法域名;当 action 是 get 时不需要此字段
+   * @param wsRequestDomains socket 合法域名;当 action 是 get 时不需要此字段
+   * @param uploadDomains    uploadFile 合法域名;当 action 是 get 时不需要此字段
+   * @param downloadDomains  downloadFile 合法域名;当 action 是 get 时不需要此字段
+   * @param tcpDomains       tcp 合法域名;当 action 是 get 时不需要此字段
+   * @param udpDomains       udp 合法域名;当 action 是 get 时不需要此字段
+   * @return the wx open ma domain result
+   * @throws WxErrorException the wx error exception
+   */
+  WxOpenMaDomainResult modifyWxaServerDomain(String action, List requestDomains, List wsRequestDomains,
+                                             List uploadDomains, List downloadDomains,
+                                             List udpDomains, List tcpDomains) throws WxErrorException;
+
+  /**
+   * 获取第三方平台业务域名校验文件
+   * 文档地址
+   *
+   * @return 业务域名校验文件信息
+   * @throws WxErrorException 操作失败时抛出,具体错误码请看文档
+   */
+  WxOpenMaDomainConfirmFileResult getDomainConfirmFile() throws WxErrorException;
+
+  /**
+   * 设置第三方平台业务域名
+   * 文档地址
+   *
+   * @param action     add添加, delete删除, set覆盖, get获取
+   * @param domainList the domain list
+   * @return 直接返回字符串
+   * @throws WxErrorException the wx error exception
+   */
+  String modifyWxaJumpDomain(String action, List domainList) throws WxErrorException;
+
+  /**
+   * 设置第三方平台业务域名
+   * 文档地址
+   *
+   * @param action     add添加, delete删除, set覆盖, get获取
+   * @param domainList the domain list
+   * @return web view domain info
+   * @throws WxErrorException the wx error exception
+   */
+  WxOpenMaWebDomainResult modifyWxaJumpDomainInfo(String action, List domainList) throws WxErrorException;
+
 }
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..5929f83902 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,27 +54,37 @@ 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";
+  /**
+   * 8.7 获取类目名称信息
+   */
+  String OPEN_GET_ALL_CATEGORY_NAME = "https://api.weixin.qq.com/cgi-bin/wxopen/getallcategorynamelist";
 
   /**
    * 获取订单页path信息
    */
   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 +155,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 +186,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 +200,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 +210,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 +218,7 @@ WxFastMaSetNickameResult setNickname(String nickname, String idCard, String lice
   WxFastMaBeenSetCategoryResult getCategory() throws WxErrorException;
 
   /**
-   * 8.5修改类目
+   * 8.6修改类目
    *
    * @param category 实体
    * @return .
@@ -203,6 +226,18 @@ WxFastMaSetNickameResult setNickname(String nickname, String idCard, String lice
    */
   WxOpenResult modifyCategory(WxFastMaCategory category) throws WxErrorException;
 
+  /**
+   * 8.7 获取类目名称信息
+   * 
+   *     获取所有类目名称信息,用于给用户展示选择
+   *     https://developers.weixin.qq.com/doc/oplatform/openApi/miniprogram-management/category-management/api_getallcategoryname.html
+   * 
+ * + * @return 类目名称列表 + * @throws WxErrorException . + */ + WxOpenMaCategoryNameListResult getAllCategoryName() throws WxErrorException; + /** * 获取订单页Path信息 * 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..ab229ba537 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 @@ -32,6 +32,11 @@ public interface WxOpenMaService extends WxMaService { */ String API_MODIFY_DOMAIN = "https://api.weixin.qq.com/wxa/modify_domain"; + /** + * 快速配置小程序服务器域名 + */ + String API_MODIFY_DOMAIN_DIRECTLY = "https://api.weixin.qq.com/wxa/modify_domain_directly"; + /** * 设置小程序业务域名(仅供第三方代小程序调用) *
@@ -44,6 +49,11 @@ public interface WxOpenMaService extends WxMaService {
    */
   String API_SET_WEBVIEW_DOMAIN = "https://api.weixin.qq.com/wxa/setwebviewdomain";
 
+  /**
+   * 快速配置小程序业务域名
+   */
+  String API_SET_WEBVIEW_DOMAIN_DIRECTLY = "https://api.weixin.qq.com/wxa/setwebviewdomain_directly";
+
   /**
    * 获取业务域名校验文件(仅供第三方代小程序调用)
    */
@@ -310,6 +320,24 @@ WxOpenMaDomainResult modifyDomain(String action, List requestDomains, Li
                                     List uploadDomains, List downloadDomains,
                                     List udpDomains, List tcpDomains) throws WxErrorException;
 
+  /**
+   * 快速配置小程序服务器域名
+   * 文档地址
+   *
+   * @param action           add添加, delete删除, set覆盖, get获取
+   * @param requestDomains   request 合法域名;当 action 是 get 时不需要此字段
+   * @param wsRequestDomains socket 合法域名;当 action 是 get 时不需要此字段
+   * @param uploadDomains    uploadFile 合法域名;当 action 是 get 时不需要此字段
+   * @param downloadDomains  downloadFile 合法域名;当 action 是 get 时不需要此字段
+   * @param tcpDomains       tcp 合法域名;当 action 是 get 时不需要此字段
+   * @param udpDomains       udp 合法域名;当 action 是 get 时不需要此字段
+   * @return the wx open ma domain result
+   * @throws WxErrorException the wx error exception
+   */
+  WxOpenMaDomainResult modifyDomainDirectly(String action, List requestDomains, List wsRequestDomains,
+                                            List uploadDomains, List downloadDomains,
+                                            List udpDomains, List tcpDomains) throws WxErrorException;
+
   /**
    * 获取小程序的业务域名
    *
@@ -346,6 +374,28 @@ WxOpenMaDomainResult modifyDomain(String action, List requestDomains, Li
    */
   WxOpenMaWebDomainResult setWebViewDomainInfo(String action, List domainList) throws WxErrorException;
 
+  /**
+   * 快速配置小程序业务域名
+   * 文档地址
+   *
+   * @param action     add添加, delete删除, set覆盖, get获取
+   * @param domainList the domain list
+   * @return 直接返回字符串
+   * @throws WxErrorException the wx error exception
+   */
+  String setWebViewDomainDirectly(String action, List domainList) throws WxErrorException;
+
+  /**
+   * 快速配置小程序业务域名
+   * 文档地址
+   *
+   * @param action     add添加, delete删除, set覆盖, get获取
+   * @param domainList the domain list
+   * @return web view domain info
+   * @throws WxErrorException the wx error exception
+   */
+  WxOpenMaWebDomainResult setWebViewDomainDirectlyInfo(String action, List domainList) throws WxErrorException;
+
   /**
    * 获取业务域名校验文件
    *
@@ -731,6 +781,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..18940c4d20 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);
             }
@@ -1307,4 +1307,55 @@ public WxOpenResult applySetOrderPathInfo(WxOpenMaApplyOrderPathInfo info) throw
     String response = post(OPEN_APPLY_SET_ORDER_PATH_INFO, gson.toJson(info));
     return WxOpenGsonBuilder.create().fromJson(response, WxOpenResult.class);
   }
+
+  @Override
+  public WxOpenMaDomainResult modifyWxaServerDomain(String action, List requestDomains, List wsRequestDomains,
+                                                    List uploadDomains, List downloadDomains,
+                                                    List udpDomains, List tcpDomains) throws WxErrorException {
+    JsonObject requestJson = new JsonObject();
+    requestJson.addProperty("action", action);
+    if (!"get".equals(action)) {
+      requestJson.add("requestdomain", toJsonArray(requestDomains));
+      requestJson.add("wsrequestdomain", toJsonArray(wsRequestDomains));
+      requestJson.add("uploaddomain", toJsonArray(uploadDomains));
+      requestJson.add("downloaddomain", toJsonArray(downloadDomains));
+      requestJson.add("udpdomain", toJsonArray(udpDomains));
+      requestJson.add("tcpdomain", toJsonArray(tcpDomains));
+    }
+
+    String response = post(API_MODIFY_WXA_SERVER_DOMAIN, requestJson.toString());
+    return WxOpenGsonBuilder.create().fromJson(response, WxOpenMaDomainResult.class);
+  }
+
+  @Override
+  public WxOpenMaDomainConfirmFileResult getDomainConfirmFile() throws WxErrorException {
+    String responseContent = post(API_GET_DOMAIN_CONFIRM_FILE, "{}");
+    return WxOpenMaDomainConfirmFileResult.fromJson(responseContent);
+  }
+
+  @Override
+  public String modifyWxaJumpDomain(String action, List domainList) throws WxErrorException {
+    JsonObject requestJson = new JsonObject();
+    requestJson.addProperty("action", action);
+    if (!"get".equals(action)) {
+      requestJson.add("webviewdomain", toJsonArray(domainList));
+    }
+    return post(API_MODIFY_WXA_JUMP_DOMAIN, requestJson.toString());
+  }
+
+  @Override
+  public WxOpenMaWebDomainResult modifyWxaJumpDomainInfo(String action, List domainList) throws WxErrorException {
+    String response = this.modifyWxaJumpDomain(action, domainList);
+    return WxOpenGsonBuilder.create().fromJson(response, WxOpenMaWebDomainResult.class);
+  }
+
+  private JsonArray toJsonArray(List list) {
+    JsonArray jsonArray = new JsonArray();
+    if (list != null) {
+      for (String item : list) {
+        jsonArray.add(item);
+      }
+    }
+    return jsonArray;
+  }
 }
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..c80ce03c3e 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<>();
@@ -142,6 +152,12 @@ public WxOpenResult modifyCategory(WxFastMaCategory category) throws WxErrorExce
     return WxOpenGsonBuilder.create().fromJson(response, WxOpenResult.class);
   }
 
+  @Override
+  public WxOpenMaCategoryNameListResult getAllCategoryName() throws WxErrorException {
+    String response = get(OPEN_GET_ALL_CATEGORY_NAME, "");
+    return WxOpenGsonBuilder.create().fromJson(response, WxOpenMaCategoryNameListResult.class);
+  }
+
   /**
    * 获取订单页Path信息
    *
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/WxOpenInRedisConfigStorage.java b/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenInRedisConfigStorage.java
index 9ce7851065..c6dbc8f468 100644
--- a/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenInRedisConfigStorage.java
+++ b/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenInRedisConfigStorage.java
@@ -36,7 +36,7 @@ public String getComponentVerifyTicket() {
 
   @Override
   public void setComponentVerifyTicket(String componentVerifyTicket) {
-    redisOps.setValue(this.componentVerifyTicketKey, componentVerifyTicket, Integer.MAX_VALUE, TimeUnit.SECONDS);
+    redisOps.setValue(this.componentVerifyTicketKey, componentVerifyTicket, 43200, TimeUnit.SECONDS);
   }
 
   @Override
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..cb55e45ad0 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;
 
@@ -39,7 +37,7 @@ public String getComponentVerifyTicket() {
 
   @Override
   public void setComponentVerifyTicket(String componentVerifyTicket) {
-    redisOps.setValue(this.componentVerifyTicketKey, componentVerifyTicket, Integer.MAX_VALUE, TimeUnit.SECONDS);
+    redisOps.setValue(this.componentVerifyTicketKey, componentVerifyTicket, 43200, TimeUnit.SECONDS);
   }
 
   @Override
diff --git a/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenInRedissonConfigStorage.java b/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenInRedissonConfigStorage.java
index 7a3a9d79af..0de9b88d7e 100644
--- a/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenInRedissonConfigStorage.java
+++ b/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenInRedissonConfigStorage.java
@@ -36,7 +36,7 @@ public String getComponentVerifyTicket() {
 
   @Override
   public void setComponentVerifyTicket(String componentVerifyTicket) {
-    redisOps.setValue(this.componentVerifyTicketKey, componentVerifyTicket, Integer.MAX_VALUE, TimeUnit.SECONDS);
+    redisOps.setValue(this.componentVerifyTicketKey, componentVerifyTicket, 43200, TimeUnit.SECONDS);
   }
 
   @Override
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..6204b1e260 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<>();
@@ -124,6 +146,12 @@ public WxOpenResult modifyCategory(WxFastMaCategory category) throws WxErrorExce
     return WxOpenGsonBuilder.create().fromJson(response, WxOpenResult.class);
   }
 
+  @Override
+  public WxOpenMaCategoryNameListResult getAllCategoryName() throws WxErrorException {
+    String response = wxMaService.get(OPEN_GET_ALL_CATEGORY_NAME, "");
+    return WxOpenGsonBuilder.create().fromJson(response, WxOpenMaCategoryNameListResult.class);
+  }
+
   /**
    * 获取订单页Path信息
    *
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..da9f910eb2 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();
   }
 
@@ -110,6 +113,25 @@ public WxOpenMaDomainResult modifyDomain(String action, List requestDoma
     return WxMaGsonBuilder.create().fromJson(response, WxOpenMaDomainResult.class);
   }
 
+  @Override
+  public WxOpenMaDomainResult modifyDomainDirectly(String action, List requestDomains, List wsRequestDomains,
+                                                   List uploadDomains, List downloadDomains,
+                                                   List udpDomains, List tcpDomains) throws WxErrorException {
+    JsonObject requestJson = new JsonObject();
+    requestJson.addProperty(ACTION, action);
+    if (!ACTION_GET.equals(action)) {
+      requestJson.add("requestdomain", toJsonArray(requestDomains));
+      requestJson.add("wsrequestdomain", toJsonArray(wsRequestDomains));
+      requestJson.add("uploaddomain", toJsonArray(uploadDomains));
+      requestJson.add("downloaddomain", toJsonArray(downloadDomains));
+      requestJson.add("udpdomain", toJsonArray(udpDomains));
+      requestJson.add("tcpdomain", toJsonArray(tcpDomains));
+    }
+
+    String response = post(API_MODIFY_DOMAIN_DIRECTLY, GSON.toJson(requestJson));
+    return WxMaGsonBuilder.create().fromJson(response, WxOpenMaDomainResult.class);
+  }
+
   @Override
   public String getWebViewDomain() throws WxErrorException {
     return setWebViewDomain(ACTION_GET, null);
@@ -137,6 +159,22 @@ public WxOpenMaWebDomainResult setWebViewDomainInfo(String action, List
     return WxMaGsonBuilder.create().fromJson(response, WxOpenMaWebDomainResult.class);
   }
 
+  @Override
+  public String setWebViewDomainDirectly(String action, List domainList) throws WxErrorException {
+    JsonObject requestJson = new JsonObject();
+    requestJson.addProperty(ACTION, action);
+    if (!ACTION_GET.equals(action)) {
+      requestJson.add("webviewdomain", toJsonArray(domainList));
+    }
+    return post(API_SET_WEBVIEW_DOMAIN_DIRECTLY, GSON.toJson(requestJson));
+  }
+
+  @Override
+  public WxOpenMaWebDomainResult setWebViewDomainDirectlyInfo(String action, List domainList) throws WxErrorException {
+    String response = this.setWebViewDomainDirectly(action, domainList);
+    return WxMaGsonBuilder.create().fromJson(response, WxOpenMaWebDomainResult.class);
+  }
+
   @Override
   public WxOpenMaDomainConfirmFileResult getWebviewDomainConfirmFile() throws WxErrorException {
     String responseContent = post(API_GET_WEBVIEW_DOMAIN_CONFIRM_FILE, "{}");
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..449697c5a6 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
 
   /**
@@ -274,7 +299,9 @@ public static String wxMpOutXmlMessageToEncryptedXml(WxMpXmlOutMessage message,
 
   public static WxOpenXmlMessage fromXml(String xml) {
     //修改微信变态的消息内容格式,方便解析
-    xml = xml.replace("", "");
+    if (xml != null) {
+      xml = xml.replace("", "");
+    }
     return XStreamTransformer.fromXml(WxOpenXmlMessage.class, xml);
   }
 
@@ -296,7 +323,14 @@ 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);
+    
+    if (plainText == null || plainText.trim().isEmpty()) {
+      throw new WxRuntimeException("解密后的xml消息内容为空,请检查加密参数是否正确");
+    }
+    
+    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/WxOpenMaCategoryNameListResult.java b/weixin-java-open/src/main/java/me/chanjar/weixin/open/bean/result/WxOpenMaCategoryNameListResult.java
new file mode 100644
index 0000000000..014a4ae84a
--- /dev/null
+++ b/weixin-java-open/src/main/java/me/chanjar/weixin/open/bean/result/WxOpenMaCategoryNameListResult.java
@@ -0,0 +1,75 @@
+package me.chanjar.weixin.open.bean.result;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import me.chanjar.weixin.open.util.json.WxOpenGsonBuilder;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * 获取类目名称信息的返回结果.
+ * 

+ * 用于获取所有小程序类目的 ID 和名称信息,包括一级类目和二级类目。 + *

+ * + * @author Binary Wang + * @see 官方文档 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class WxOpenMaCategoryNameListResult extends WxOpenResult { + private static final long serialVersionUID = 8989721350285449879L; + + /** + * 类目名称列表. + */ + @SerializedName("category_name_list") + private List categoryNameList; + + @Override + public String toString() { + return WxOpenGsonBuilder.create().toJson(this); + } + + /** + * 类目名称信息. + *

+ * 包含一级类目和二级类目的 ID 和名称。 + *

+ */ + @Data + public static class CategoryName implements Serializable { + private static final long serialVersionUID = 8989721350285449880L; + + /** + * 一级类目ID. + */ + @SerializedName("first_id") + private Integer firstId; + + /** + * 一级类目名称. + */ + @SerializedName("first_name") + private String firstName; + + /** + * 二级类目ID. + */ + @SerializedName("second_id") + private Integer secondId; + + /** + * 二级类目名称. + */ + @SerializedName("second_name") + private String secondName; + + @Override + public String toString() { + return WxOpenGsonBuilder.create().toJson(this); + } + } +} 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-open/src/main/java/me/chanjar/weixin/open/util/json/WxOpenGsonBuilder.java b/weixin-java-open/src/main/java/me/chanjar/weixin/open/util/json/WxOpenGsonBuilder.java index 9cb4abd072..6b07438b11 100644 --- a/weixin-java-open/src/main/java/me/chanjar/weixin/open/util/json/WxOpenGsonBuilder.java +++ b/weixin-java-open/src/main/java/me/chanjar/weixin/open/util/json/WxOpenGsonBuilder.java @@ -1,12 +1,17 @@ package me.chanjar.weixin.open.util.json; +import com.google.gson.ExclusionStrategy; +import com.google.gson.FieldAttributes; import com.google.gson.Gson; import com.google.gson.GsonBuilder; +import me.chanjar.weixin.common.util.http.apache.ApacheHttpClientBuilder; import me.chanjar.weixin.open.bean.WxOpenAuthorizerAccessToken; import me.chanjar.weixin.open.bean.WxOpenComponentAccessToken; import me.chanjar.weixin.open.bean.auth.WxOpenAuthorizationInfo; import me.chanjar.weixin.open.bean.auth.WxOpenAuthorizerInfo; import me.chanjar.weixin.open.bean.result.*; + +import java.io.File; import java.util.Objects; /** @@ -27,6 +32,18 @@ public class WxOpenGsonBuilder { INSTANCE.registerTypeAdapter(WxOpenAuthorizerInfoResult.class, new WxOpenAuthorizerInfoResultGsonAdapter()); INSTANCE.registerTypeAdapter(WxOpenAuthorizerOptionResult.class, new WxOpenAuthorizerOptionResultGsonAdapter()); INSTANCE.registerTypeAdapter(WxOpenAuthorizerListResult.class, new WxOpenAuthorizerListResultGsonAdapter()); + + INSTANCE.setExclusionStrategies(new ExclusionStrategy() { + @Override + public boolean shouldSkipField(FieldAttributes fieldAttributes) { + return false; + } + + @Override + public boolean shouldSkipClass(Class aClass) { + return aClass == File.class || aClass == ApacheHttpClientBuilder.class; + } + }); } public static Gson create() { diff --git a/weixin-java-open/src/test/java/me/chanjar/weixin/open/api/impl/WxOpenInRedisConfigStorageTest.java b/weixin-java-open/src/test/java/me/chanjar/weixin/open/api/impl/WxOpenInRedisConfigStorageTest.java index 26a30a2040..bd82081113 100644 --- a/weixin-java-open/src/test/java/me/chanjar/weixin/open/api/impl/WxOpenInRedisConfigStorageTest.java +++ b/weixin-java-open/src/test/java/me/chanjar/weixin/open/api/impl/WxOpenInRedisConfigStorageTest.java @@ -128,4 +128,18 @@ public void testGetCardApiTicket() { expired = this.wxOpenConfigStorage.isCardApiTicketExpired(appid); Assert.assertEquals(expired, true); } + + @Test + public void testComponentVerifyTicketExpiration() { + // Test that ComponentVerifyTicket is set correctly + this.wxOpenConfigStorage.setComponentVerifyTicket("test_ticket_for_expiration"); + String componentVerifyTicket = this.wxOpenConfigStorage.getComponentVerifyTicket(); + Assert.assertEquals(componentVerifyTicket, "test_ticket_for_expiration"); + + // This test verifies that setComponentVerifyTicket now uses 43200 seconds (12 hours) + // instead of Integer.MAX_VALUE for expiration. The actual expiration test would + // require waiting or mocking time, which is not practical in unit tests. + // The change is validated by code inspection and the fact that other tokens + // use similar expiration patterns with specific timeouts. + } } diff --git a/weixin-java-open/src/test/java/me/chanjar/weixin/open/api/impl/WxOpenInRedissonConfigStorageTest.java b/weixin-java-open/src/test/java/me/chanjar/weixin/open/api/impl/WxOpenInRedissonConfigStorageTest.java index 7168d93f1b..8042853de2 100644 --- a/weixin-java-open/src/test/java/me/chanjar/weixin/open/api/impl/WxOpenInRedissonConfigStorageTest.java +++ b/weixin-java-open/src/test/java/me/chanjar/weixin/open/api/impl/WxOpenInRedissonConfigStorageTest.java @@ -126,4 +126,18 @@ public void testGetCardApiTicket() { expired = this.wxOpenConfigStorage.isCardApiTicketExpired(appid); Assert.assertEquals(expired, true); } + + @Test + public void testComponentVerifyTicketExpiration() { + // Test that ComponentVerifyTicket is set correctly + this.wxOpenConfigStorage.setComponentVerifyTicket("test_ticket_for_expiration"); + String componentVerifyTicket = this.wxOpenConfigStorage.getComponentVerifyTicket(); + Assert.assertEquals(componentVerifyTicket, "test_ticket_for_expiration"); + + // This test verifies that setComponentVerifyTicket now uses 43200 seconds (12 hours) + // instead of Integer.MAX_VALUE for expiration. The actual expiration test would + // require waiting or mocking time, which is not practical in unit tests. + // The change is validated by code inspection and the fact that other tokens + // use similar expiration patterns with specific timeouts. + } } 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/SUBSCRIPTION_BILLING_USAGE.md b/weixin-java-pay/SUBSCRIPTION_BILLING_USAGE.md new file mode 100644 index 0000000000..acc8f269b8 --- /dev/null +++ b/weixin-java-pay/SUBSCRIPTION_BILLING_USAGE.md @@ -0,0 +1,194 @@ +# 微信支付预约扣费功能使用说明 + +## 概述 + +微信支付预约扣费功能(连续包月功能)允许商户在用户授权的情况下,按照约定的时间和金额,自动从用户的支付账户中扣取费用。主要适用于连续包月、订阅服务等场景。 + +## 功能特性 + +- **预约扣费**:创建未来某个时间点的扣费计划 +- **查询预约**:查询已创建的扣费计划状态 +- **取消预约**:取消已创建的扣费计划 +- **立即扣费**:立即执行扣费操作 +- **扣费记录查询**:查询历史扣费记录 + +## 快速开始 + +### 1. 获取服务实例 + +```java +// 通过 WxPayService 获取预约扣费服务 +SubscriptionBillingService subscriptionService = wxPayService.getSubscriptionBillingService(); +``` + +### 2. 创建预约扣费 + +```java +// 创建预约扣费请求 +SubscriptionScheduleRequest request = new SubscriptionScheduleRequest(); +request.setOutTradeNo("subscription_" + System.currentTimeMillis()); +request.setOpenid("用户的openid"); +request.setDescription("腾讯视频VIP会员"); +request.setScheduleTime("2024-09-01T10:00:00+08:00"); + +// 设置扣费金额 +SubscriptionAmount amount = new SubscriptionAmount(); +amount.setTotal(3000); // 30元,单位为分 +amount.setCurrency("CNY"); +request.setAmount(amount); + +// 设置扣费计划(可选) +BillingPlan billingPlan = new BillingPlan(); +billingPlan.setPlanType("MONTHLY"); // 按月扣费 +billingPlan.setPeriod(1); // 每1个月 +billingPlan.setTotalCount(12); // 总共12次 +request.setBillingPlan(billingPlan); + +// 发起预约扣费 +SubscriptionScheduleResult result = subscriptionService.scheduleSubscription(request); +System.out.println("预约扣费ID: " + result.getSubscriptionId()); +``` + +### 3. 查询预约扣费 + +```java +// 通过预约扣费ID查询 +String subscriptionId = "从预约扣费结果中获取的ID"; +SubscriptionQueryResult queryResult = subscriptionService.querySubscription(subscriptionId); +System.out.println("预约状态: " + queryResult.getStatus()); +``` + +### 4. 取消预约扣费 + +```java +// 创建取消请求 +SubscriptionCancelRequest cancelRequest = new SubscriptionCancelRequest(); +cancelRequest.setSubscriptionId(subscriptionId); +cancelRequest.setCancelReason("用户主动取消"); + +// 取消预约扣费 +SubscriptionCancelResult cancelResult = subscriptionService.cancelSubscription(cancelRequest); +System.out.println("取消结果: " + cancelResult.getStatus()); +``` + +### 5. 立即扣费 + +```java +// 创建立即扣费请求 +SubscriptionInstantBillingRequest instantRequest = new SubscriptionInstantBillingRequest(); +instantRequest.setOutTradeNo("instant_" + System.currentTimeMillis()); +instantRequest.setOpenid("用户的openid"); +instantRequest.setDescription("补扣上月会员费"); + +// 设置扣费金额 +SubscriptionAmount instantAmount = new SubscriptionAmount(); +instantAmount.setTotal(3000); // 30元 +instantAmount.setCurrency("CNY"); +instantRequest.setAmount(instantAmount); + +// 执行立即扣费 +SubscriptionInstantBillingResult instantResult = subscriptionService.instantBilling(instantRequest); +System.out.println("扣费结果: " + instantResult.getTradeState()); +``` + +### 6. 查询扣费记录 + +```java +// 创建查询请求 +SubscriptionTransactionQueryRequest queryRequest = new SubscriptionTransactionQueryRequest(); +queryRequest.setOpenid("用户的openid"); +queryRequest.setBeginTime("2024-08-01T00:00:00+08:00"); +queryRequest.setEndTime("2024-08-31T23:59:59+08:00"); +queryRequest.setLimit(20); +queryRequest.setOffset(0); + +// 查询扣费记录 +SubscriptionTransactionQueryResult transactionResult = subscriptionService.queryTransactions(queryRequest); +System.out.println("总记录数: " + transactionResult.getTotalCount()); +for (SubscriptionTransactionQueryResult.SubscriptionTransaction transaction : transactionResult.getData()) { + System.out.println("订单号: " + transaction.getOutTradeNo() + ", 状态: " + transaction.getTradeState()); +} +``` + +## 扣费计划类型 + +- `MONTHLY`:按月扣费 +- `WEEKLY`:按周扣费 +- `DAILY`:按日扣费 +- `YEARLY`:按年扣费 + +## 预约状态说明 + +- `SCHEDULED`:已预约 +- `CANCELLED`:已取消 +- `EXECUTED`:已执行 +- `FAILED`:执行失败 + +## 交易状态说明 + +- `SUCCESS`:支付成功 +- `REFUND`:转入退款 +- `NOTPAY`:未支付 +- `CLOSED`:已关闭 +- `REVOKED`:已撤销(刷卡支付) +- `USERPAYING`:用户支付中 +- `PAYERROR`:支付失败 + +## 注意事项 + +1. **用户授权**:使用预约扣费功能前,需要用户在微信内完成签约授权 +2. **商户资质**:需要具备相应的业务资质才能开通此功能 +3. **金额限制**:扣费金额需要在签约模板规定的范围内 +4. **频率限制**:API调用有频率限制,请注意控制调用频次 +5. **异常处理**:建议对所有API调用进行异常处理 + +## 相关文档 + +- [微信支付预约扣费API文档](https://pay.weixin.qq.com/doc/v3/merchant/4012161105) +- [微信支付开发指南](https://pay.weixin.qq.com/wiki/doc/apiv3/index.shtml) + +## 示例完整代码 + +```java +import com.github.binarywang.wxpay.service.SubscriptionBillingService; +import com.github.binarywang.wxpay.bean.subscriptionbilling.*; + +public class SubscriptionBillingExample { + + private SubscriptionBillingService subscriptionService; + + public void example() throws Exception { + // 1. 创建预约扣费 + SubscriptionScheduleRequest request = new SubscriptionScheduleRequest(); + request.setOutTradeNo("subscription_" + System.currentTimeMillis()); + request.setOpenid("用户openid"); + request.setDescription("VIP会员续费"); + request.setScheduleTime("2024-09-01T10:00:00+08:00"); + + SubscriptionAmount amount = new SubscriptionAmount(); + amount.setTotal(3000); + amount.setCurrency("CNY"); + request.setAmount(amount); + + BillingPlan plan = new BillingPlan(); + plan.setPlanType("MONTHLY"); + plan.setPeriod(1); + plan.setTotalCount(12); + request.setBillingPlan(plan); + + SubscriptionScheduleResult result = subscriptionService.scheduleSubscription(request); + + // 2. 查询预约状态 + SubscriptionQueryResult query = subscriptionService.querySubscription(result.getSubscriptionId()); + + // 3. 如需取消 + if ("SCHEDULED".equals(query.getStatus())) { + SubscriptionCancelRequest cancelReq = new SubscriptionCancelRequest(); + cancelReq.setSubscriptionId(result.getSubscriptionId()); + cancelReq.setCancelReason("用户取消"); + + SubscriptionCancelResult cancelResult = subscriptionService.cancelSubscription(cancelReq); + } + } +} +``` \ No newline at end of file diff --git a/weixin-java-pay/pom.xml b/weixin-java-pay/pom.xml index cba2ede006..1902f01892 100644 --- a/weixin-java-pay/pom.xml +++ b/weixin-java-pay/pom.xml @@ -5,7 +5,7 @@ com.github.binarywang wx-java - 4.7.0 + 4.7.9.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/ApplySubjectConfirmCreateRequest.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/applyconfirm/ApplySubjectConfirmCreateRequest.java index b95061461c..d77522ecce 100644 --- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/applyconfirm/ApplySubjectConfirmCreateRequest.java +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/applyconfirm/ApplySubjectConfirmCreateRequest.java @@ -119,8 +119,8 @@ public static class ApplySubConfirmIdentificationInfo implements Serializable { * IDENTIFICATION_TYPE_TAIWAN_RESIDENT:台湾居民证 * 示例值:IDENTIFICATION_TYPE_IDCARD */ - @SerializedName("id_doc_type") - private IdTypeEnum idDocType; + @SerializedName("identification_type") + private IdTypeEnum identificationType; /** * 法定代表人说明函 @@ -414,13 +414,13 @@ public static class ApplySubConfirmSubjectInfo implements Serializable { * 若未传入将默认填写:false。 * 示例值:true */ - @SerializedName("finance_institution") + @SerializedName("is_finance_institution") private Boolean financeInstitution; /** * 营业执照 */ - @SerializedName("business_license_info") + @SerializedName("business_licence_info") private ApplySubConfirmBusinessLicenseInfo businessLicenseInfo; /** * 登记证书 @@ -736,8 +736,8 @@ public static class ApplySubConfirmSpecialOperationList implements Serializable * 参看微信支付提供的特殊行业id对照表 * 示例值:100 */ - @SerializedName("finance_type") - private Integer financeType; + @SerializedName("category_id") + private Integer categoryId; /** * 行业经营许可证资质照片 @@ -791,29 +791,10 @@ public static class ApplySubConfirmAdditionInfo implements Serializable { private static final long serialVersionUID = 1L; /** - * 法人开户承诺函 - */ - @SerializedName("legal_person_commitment") - private String legalPersonCommitment; - - /** - * 法人开户意愿视频 + * 待确认商户号列表 */ - @SerializedName("legal_person_video") - private String legalPersonVideo; - - /** - * 补充材料 - */ - @SerializedName("business_addition_pics") - private List businessAdditionPics; - - /** - * 补充说明 - */ - @SerializedName("business_addition_msg") - private String businessAdditionMsg; - + @SerializedName("confirm_mchid_list") + private List confirmMchidList; } } 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/applyment/WxPayApplyment4SubCreateRequest.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/applyment/WxPayApplyment4SubCreateRequest.java index c204e2911e..b7b8a02050 100644 --- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/applyment/WxPayApplyment4SubCreateRequest.java +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/applyment/WxPayApplyment4SubCreateRequest.java @@ -23,7 +23,7 @@ @AllArgsConstructor @Accessors(chain = true) public class WxPayApplyment4SubCreateRequest implements Serializable { - private static final long serialVersionUID = 1L; + private static final long serialVersionUID = 4104022969945059126L; /** * 业务申请编号 @@ -78,7 +78,7 @@ public class WxPayApplyment4SubCreateRequest implements Serializable { @AllArgsConstructor @Accessors(chain = true) public static class ContactInfo implements Serializable { - private static final long serialVersionUID = 1L; + private static final long serialVersionUID = -9087348002744428474L; /** * 超级管理员类型 @@ -211,7 +211,7 @@ public static class ContactInfo implements Serializable { @AllArgsConstructor @Accessors(chain = true) public static class SubjectInfo implements Serializable { - private static final long serialVersionUID = 1L; + private static final long serialVersionUID = -6651911735969445765L; /** * 主体类型 @@ -242,6 +242,13 @@ public static class SubjectInfo implements Serializable { @SerializedName("certificate_letter_copy") private String certificateLetterCopy; + /** + * 小微辅助证明材料 + * 主体类型为小微商户时,小微辅助证明材料必填 + */ + @SerializedName("micro_biz_info") + private MicroBizInfo microBizInfo; + /** * 金融机构许可证信息 */ @@ -393,6 +400,88 @@ public static class FinanceInstitutionInfo implements Serializable { private List financeLicensePics; } + /** + * 小微辅助证明材料 + */ + @Data + @Builder + @NoArgsConstructor + @AllArgsConstructor + @Accessors(chain = true) + public static class MicroBizInfo implements Serializable { + + private static final long serialVersionUID = 2327302539406612422L; + + /** + * 小微经营类型 + * 枚举值: + * MICRO_TYPE_STORE:门店场所 + * MICRO_TYPE_MOBILE:流动经营/便民服务 + * MICRO_TYPE_ONLINE:线上商品/服务交易 + * 示例值:MICRO_TYPE_STORE + */ + @SerializedName("micro_biz_type") + private MicroBizTypeEnum microBizType; + + /** + * 门店名称 + * 1、填写规范: + * 门店场所:填写门店名称 + * 流动经营/便民服务:填写经营/服务名称 + * 线上商品/服务交易:填写线上店铺名称 + * 2、格式规范: + * 长度为1-50个字符 + * 前后不能有空格、制表符、换行符 + * 不能仅含数字、特殊字符 + * 仅能填写数字、英文字母、汉字及特殊字符 + * 仅支持utf-8格式 + * 示例值:大郎烧饼 + */ + @SerializedName("micro_name") + private String microName; + + /** + * 门店省市编码 + * 1、只能由数字组成 + * 2、详细参见微信支付提供的省市对照表 + * 3、填写规范: + * 门店场所:填写门店省市编码 + * 流动经营/便民服务:填写经营/服务所在地省市编码 + * 线上商品/服务交易:填写卖家所在地省市编码 + * 示例值:440305 + */ + @SerializedName("micro_address_code") + private String microAddressCode; + + /** + * 门店地址 + * 1、填写规范: + * 门店场所:填写店铺详细地址,具体区/县及街道门牌号或大厦楼层 + * 流动经营/便民服务:填写"无" + * 线上商品/服务交易:填写电商平台名称 + * 2、格式规范: + * 长度为4-512个字符 + * 前后不能有空格、制表符、换行符 + * 不能仅含数字、特殊字符 + * 仅能填写数字、英文字母、汉字及特殊字符 + * 仅支持utf-8格式 + * 示例值:广东省深圳市南山区xx大厦x层xxxx室 + */ + @SerializedName("micro_address") + private String microAddress; + + /** + * 门店门头照片/经营场景照片 + * 1、门店场所:请上传门头正面照片(要求门店招牌、门框完整、清晰、可辨识);若为停车场等无固定门头照片的经营场所,可上传岗亭/出入闸口; + * 2、流动经营/便民服务:填写媒体文件ID列表,最多5张; + * 3、线上商品/服务交易:请上传线上店铺网页截图(清晰度足够识别店铺名称的首页截图); + * 4、请填写通过《图片上传API》预先上传图片生成好的MediaID + * 示例值:0P3ng6KTIW4-Q_l2FjKLZuhHjBWoMAjmVtCz7ScmhEIThCaV-4BBgVwtNkCHO_XXqK5dE5YdOmFJBZR9FwczhJehHhAZN6BKXQPcs-VvdSo + */ + @SerializedName("micro_pics") + private List microPics; + } + @Data @Builder @NoArgsConstructor @@ -603,7 +692,7 @@ public static class UboInfo implements Serializable { @AllArgsConstructor @Accessors(chain = true) public static class BusinessInfo implements Serializable { - private static final long serialVersionUID = 1L; + private static final long serialVersionUID = -8605049544105644011L; /** * 商户简称 @@ -876,7 +965,7 @@ public static class WeworkInfo implements Serializable { @AllArgsConstructor @Accessors(chain = true) public static class SettlementInfo implements Serializable { - private static final long serialVersionUID = 1L; + private static final long serialVersionUID = -5025743467243760522L; /** * 入驻结算规则ID @@ -937,7 +1026,7 @@ public static class SettlementInfo implements Serializable { @AllArgsConstructor @Accessors(chain = true) public static class BankAccountInfo implements Serializable { - private static final long serialVersionUID = 1L; + private static final long serialVersionUID = -5853122395888860086L; /** * 账户类型 @@ -995,7 +1084,7 @@ public static class BankAccountInfo implements Serializable { @AllArgsConstructor @Accessors(chain = true) public static class AdditionInfo implements Serializable { - private static final long serialVersionUID = 1L; + private static final long serialVersionUID = -7526912529114022379L; /** * 法人开户承诺函 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/ComplaintDetailResult.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/complaint/ComplaintDetailResult.java index 157e095bba..f8562dce39 100644 --- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/complaint/ComplaintDetailResult.java +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/complaint/ComplaintDetailResult.java @@ -327,4 +327,141 @@ public static class ServiceOrder implements Serializable { */ @SerializedName("user_tag_list") private String[] userTagList; + + /** + *
+   * 字段名:补充信息
+   * 是否必填:否
+   * 描述: 用在特定行业或场景下返回的补充信息
+   * 
+ */ + @SerializedName("additional_info") + private AdditionalInfo additionalInfo; + + @Data + public static class AdditionalInfo implements Serializable { + private static final long serialVersionUID = 7917816070738944147L; + + /** + *
+     * 字段名:补充信息类型
+     * 是否必填:否
+     * 描述: 补充信息类型
+     * 示例值:SHARE_POWER_TYPE: 充电宝投诉相关行业
+     * 
+ */ + @SerializedName("type") + private String type; + + /** + *
+     * 字段名:充电宝投诉相关信息
+     * 是否必填:否
+     * 描述:当type为充电宝投诉相关时有值
+     * 
+ */ + @SerializedName("share_power_info") + private SharePowerInfo sharePowerInfo; + + /** + * 充电宝投诉相关信息 + */ + @Data + public static class SharePowerInfo implements Serializable { + private static final long serialVersionUID = -2878382307459369354L; + + /** + *
+       * 字段名:归还时间
+       * 是否必填:否
+       * 描述:遵循rfc3339标准格式,格式为yyyy-MM-DDTHH:mm:ss+TIMEZONE,
+       *      yyyy-MM-DD表示年月日,T出现在字符串中,表示time元素的开头,
+       *      HH:mm:ss表示时分秒,
+       *      TIMEZONE表示时区(+08:00表示东八区时间,领先UTC 8小时,即北京时间)。
+       * 示例值:2015-05-20T13:29:35+08:00表示,北京时间2015年5月20日 13点29分35秒
+       * 
+ */ + @SerializedName("return_time") + private String returnTime; + + /** + *
+       * 字段名:归还地点信息
+       * 是否必填:否
+       * 描述:  归还地点信息
+       * 
+ */ + @SerializedName("return_address_info") + private ReturnAddressInfo returnAddressInfo; + + @Data + public static class ReturnAddressInfo implements Serializable { + private static final long serialVersionUID = -7649986542568217256L; + + /** + *
+         * 字段名:归还地点
+         * 是否必填:否 string(256)
+         * 描述:归还地点
+         * 
+ */ + @SerializedName("return_address") + private String returnAddress; + + /** + *
+         * 字段名:归还地点经度
+         * 是否必填:否 string(32)
+         * 描述:经度,字符串,范围为-180~180,负数表示西经。使用GCJ-02坐标系
+         * 
+ */ + @SerializedName("longitude") + private String longitude; + + /** + *
+         * 字段名:归还地点纬度
+         * 是否必填:否 string(32)
+         * 描述:纬度,字符串,范围为-90~90,负数表示南纬。使用GCJ-02坐标系
+         * 
+ */ + @SerializedName("latitude") + private String latitude; + } + + /** + *
+       * 字段名:是否归还同一柜机
+       * 是否必填:否
+       * 描述:用户声明是否将充电宝归还至与借取时同一柜机
+       * 
+ */ + @SerializedName("is_returned_to_same_machine") + private Boolean isReturnedToSameMachine; + } + } + + /** + *
+   * 字段名:是否在平台协助中
+   * 是否必填:否
+   * 描述:标识当前投诉单是否正处在平台协助流程中。
+   * 注:在协助期间由微信支付客服为用户服务,期间商户向用户发送的留言用户不可见
+   * 
+ */ + @SerializedName("in_platform_service") + private Boolean inPlatformService; + + /** + *
+   * 字段名:是否需即时服务用户
+   * 是否必填:否
+   * 描述:因用户诉求紧急度、用户界面差异等因素,部分投诉单建议商户更即时地响应用户诉求。
+   *      如此处标识为“是”,建议商户提升服务时效,给用户带来更好的体验
+   * 
+ */ + @SerializedName("need_immediate_service") + private Boolean needImmediateService; + + } 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/complaint/NegotiationHistoryResult.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/complaint/NegotiationHistoryResult.java index 2da216446d..7c8738fe29 100644 --- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/complaint/NegotiationHistoryResult.java +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/complaint/NegotiationHistoryResult.java @@ -142,20 +142,30 @@ public static class ComplaintMedia implements Serializable { * 是否必填:是 * 描述: * 当前投诉协商记录的操作类型,对应枚举: - * USER_CREATE_COMPLAINT:用户提交投诉 - * USER_CONTINUE_COMPLAINT:用户继续投诉 - * USER_RESPONSE:用户留言 - * PLATFORM_RESPONSE:平台留言 - * MERCHANT_RESPONSE:商户留言 - * MERCHANT_CONFIRM_COMPLETE:商户申请结单 - * COMPLAINT_FULL_REFUNDED:投诉单全额退款 - * USER_CREATE_COMPLAINT_SYSTEM_MESSAGE:用户提交投诉系统通知 - * COMPLAINT_FULL_REFUNDED_SYSTEM_MESSAGE:投诉单全额退款系统通知 - * USER_CONTINUE_COMPLAINT_SYSTEM_MESSAGE:用户继续投诉系统通知 - * MERCHANT_CONFIRM_COMPLETE_SYSTEM_MESSAGE:商户申请结单系统通知 - * USER_REVOKE_COMPLAINT:用户主动撤诉(只存在于历史投诉单的协商历史中) - * PLATFORM_HELP_APPLICATION:平台问询 - * USER_APPLY_PLATFORM_HELP:申请协助 + * USER_CREATE_COMPLAINT: 用户提交投诉 + * USER_CONTINUE_COMPLAINT: 用户继续投诉 + * USER_RESPONSE: 用户留言 + * PLATFORM_RESPONSE: 平台留言 + * MERCHANT_RESPONSE: 商户留言 + * MERCHANT_CONFIRM_COMPLETE: 商户申请结单 + * USER_CREATE_COMPLAINT_SYSTEM_MESSAGE: 用户提交投诉系统通知 + * COMPLAINT_FULL_REFUNDED_SYSTEM_MESSAGE: 投诉单发起全额退款系统通知 + * USER_CONTINUE_COMPLAINT_SYSTEM_MESSAGE: 用户继续投诉系统通知 + * USER_REVOKE_COMPLAINT: 用户主动撤诉(只存在于历史投诉单的协商历史中) + * USER_COMFIRM_COMPLAINT: 用户确认投诉解决(只存在于历史投诉单的协商历史中) + * PLATFORM_HELP_APPLICATION: 平台催办 + * USER_APPLY_PLATFORM_HELP: 用户申请平台协助 + * MERCHANT_APPROVE_REFUND: 商户同意退款申请 + * MERCHANT_REFUSE_RERUND: 商户拒绝退款申请, 此时操作内容里展示拒绝原因 + * USER_SUBMIT_SATISFACTION: 用户提交满意度调查结果,此时操作内容里会展示满意度分数 + * SERVICE_ORDER_CANCEL: 服务订单已取消 + * SERVICE_ORDER_COMPLETE: 服务订单已完成 + * COMPLAINT_PARTIAL_REFUNDED_SYSTEM_MESSAGE: 投诉单发起部分退款系统通知 + * COMPLAINT_REFUND_RECEIVED_SYSTEM_MESSAGE: 投诉单退款到账系统通知 + * COMPLAINT_ENTRUSTED_REFUND_SYSTEM_MESSAGE: 投诉单受托退款系统通知 + * USER_APPLY_PLATFORM_SERVICE: 用户申请平台协助 + * USER_CANCEL_PLATFORM_SERVICE: 用户取消平台协助 + * PLATFORM_SERVICE_FINISHED: 客服结束平台协助 *
*/ @SerializedName("operate_type") @@ -179,11 +189,32 @@ public static class ComplaintMedia implements Serializable { * 描述: * 当前投诉协商记录提交的图片凭证(url格式),最多返回4张图片,url有效时间为1小时。如未查询到协商历史图片凭证,则返回空数组。 * 注:本字段包含商户、微信支付客服在协商解决投诉时上传的图片凭证,若希望查看用户图片,请使用complaint_media_list字段并联系微信支付客服 + * 注:此字段不包含用户提交的图片凭证,建议统一使用complaint_media_list字段接收和请求资料凭证,未来该字段将废弃 *
*/ @SerializedName("image_list") private List imageList; + /** + *
+     * 字段名:用户申请平台协助原因
+     * 是否必填:否
+     * 描述:用户此次申请平台协助时选择的申请协助原因
+     * 
+ */ + @SerializedName("user_appy_platform_service_reason") + private String userApplyPlatformServiceReason; + + /** + *
+     * 字段名:用户申请平台协助原因描述
+     * 是否必填:否
+     * 描述:用户此次申请平台协助时填写的具体申请协助原因描述
+     * 
+ */ + @SerializedName("user_appy_platform_service_reason_description") + private String userApplyPlatformServiceReasonDescription; + } } diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/complaint/ResponseRequest.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/complaint/ResponseRequest.java index 6f582b9301..470f2bed11 100644 --- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/complaint/ResponseRequest.java +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/complaint/ResponseRequest.java @@ -94,4 +94,57 @@ public class ResponseRequest implements Serializable { @SerializedName("jump_url_text") private String jumpUrlText; + /** + *
+   * 字段名:跳转小程序信息
+   * 是否必填:否
+   * 描述:商户可在回复中附加小程序信息,引导用户跳转至商户客诉处理小程序。
+   * 注:配置小程序属于灰度功能,若有需要请联系对接的行业运营进行咨询。
+   * 
+ */ + @SerializedName("mini_program_jump_info") + private MiniProgramJumpInfo miniProgramJumpInfo; + + + /** + * 跳转小程序信息 + */ + @Data + public static class MiniProgramJumpInfo implements Serializable { + private static final long serialVersionUID = 1169503275787468380L; + + /** + *
+     * 字段名:跳转小程序APPID
+     * 是否必填:是
+     * 描述:商户可在回复中附加小程序页面路径,引导用户跳转至商户服务工具页面。
+     *      该字段为小程序APPID。
+     * 
+ */ + @SerializedName("appid") + private String appId; + + /** + *
+     * 字段名:跳转小程序页面PATH
+     * 是否必填:是
+     * 描述:商户可在回复中附加小程序页面路径,引导用户跳转至商户服务工具页面。
+     *      该字段为小程序路径。
+     * 
+ */ + @SerializedName("path") + private String path; + + /** + *
+     * 字段名:跳转小程序页面名称
+     * 是否必填:是
+     * 描述:商户可在回复中附加小程序页面路径,引导用户跳转至商户服务工具页面。
+     *      该字段为商户可自定义的页面名称。
+     * 
+ */ + @SerializedName("text") + private String text; + } + } diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/complaint/UpdateRefundProgressRequest.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/complaint/UpdateRefundProgressRequest.java index f7715c522e..79668bd0ce 100644 --- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/complaint/UpdateRefundProgressRequest.java +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/complaint/UpdateRefundProgressRequest.java @@ -61,7 +61,7 @@ public class UpdateRefundProgressRequest implements Serializable { /** *
    * 字段名:拒绝退款原因
-   * 是否必填:否
+   * 是否必填:否 string(200)
    * 描述:在拒绝退款时返回拒绝退款的原因
    * 
*/ @@ -72,7 +72,9 @@ public class UpdateRefundProgressRequest implements Serializable { *
    * 字段名:拒绝退款的举证图片列表
    * 是否必填:否
-   * 描述:在拒绝退款时,如果有拒绝的图片举证,可以提供 最多上传4张图片, 传入调用“商户上传反馈图片”接口返回的media_id,最多上传4张图片凭证
+   * 描述:在拒绝退款时,如果有拒绝的图片举证,可以提供 最多上传4张图片,
+   *      传入调用“商户上传反馈图片”接口返回的media_id,最多上传4张图片凭证
+   *
    * 
*/ @SerializedName("reject_media_list") @@ -81,7 +83,7 @@ public class UpdateRefundProgressRequest implements Serializable { /** *
    * 字段名:备注
-   * 是否必填:否
+   * 是否必填:否 string(200)
    * 描述:任何需要向微信支付客服反馈的信息
    * 
*/ 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/ecommerce/SpWithdrawRequest.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/SpWithdrawRequest.java index 0b836366d4..a3d70557f0 100644 --- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/SpWithdrawRequest.java +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/SpWithdrawRequest.java @@ -9,7 +9,7 @@ /** * 电商平台提现 *
- *   文档地址:https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/ecommerce/fund/chapter3_5.shtml
+ *   文档地址:https://pay.weixin.qq.com/doc/v3/partner/4012476670
  * 
*/ @Data @@ -88,4 +88,19 @@ public class SpWithdrawRequest implements Serializable { @SerializedName(value = "account_type") private String accountType; + /** + *
+   * 字段名:回调通知地址
+   * 变量名:notify_url
+   * 是否必填:否
+   * 类型:string(256)
+   * 描述:
+   *  异步接收提现状态变更通知的回调地址,通知url必须为外网可访问的url,不能携带参数。
+   *  如果参数中传了notify_url,则商户平台上配置的回调地址将不会生效,优先回调当前传的地址。
+   * 示例值:https://www.weixin.qq.com/wxpay/pay.php
+   * 
+ */ + @SerializedName(value = "notify_url") + private String notifyUrl; + } diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/SubDayEndBalanceWithdrawRequest.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/SubDayEndBalanceWithdrawRequest.java new file mode 100644 index 0000000000..11749ef117 --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/SubDayEndBalanceWithdrawRequest.java @@ -0,0 +1,125 @@ +package com.github.binarywang.wxpay.bean.ecommerce; + +import com.google.gson.annotations.SerializedName; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * 二级商户按日终余额预约提现 + *
+ *   文档地址:https://pay.weixin.qq.com/doc/v3/partner/4013328143
+ * 
+ * + * @author copilot + * created on 2024/12/24 + */ +@Data +@NoArgsConstructor +public class SubDayEndBalanceWithdrawRequest implements Serializable { + + private static final long serialVersionUID = -8745123456789012345L; + + /** + *
+   * 字段名:二级商户号
+   * 变量名:sub_mchid
+   * 是否必填:是
+   * 类型:string(32)
+   * 描述:
+   *  电商平台二级商户号,由微信支付生成并下发。
+   * 示例值:1900000109
+   * 
+ */ + @SerializedName(value = "sub_mchid") + private String subMchid; + + /** + *
+   * 字段名:商户提现单号
+   * 变量名:out_request_no
+   * 是否必填:是
+   * 类型:string(32)
+   * 描述:
+   *  必须是字母数字
+   * 示例值:20190611222222222200000000012122
+   * 
+ */ + @SerializedName(value = "out_request_no") + private String outRequestNo; + + /** + *
+   * 字段名:提现金额
+   * 变量名:amount
+   * 是否必填:是
+   * 类型:int64
+   * 描述:
+   *  提现金额(单位:分)
+   * 示例值:100
+   * 
+ */ + @SerializedName(value = "amount") + private Integer amount; + + /** + *
+   * 字段名:备注
+   * 变量名:remark
+   * 是否必填:否
+   * 类型:string(56)
+   * 描述:
+   *  商户对提现单的备注
+   * 示例值:交易提现
+   * 
+ */ + @SerializedName(value = "remark") + private String remark; + + /** + *
+   * 字段名:银行附言
+   * 变量名:bank_memo
+   * 是否必填:否
+   * 类型:string(32)
+   * 描述:
+   *  展示在收款银行系统中的附言,数字、字母最长32个汉字(能否成功展示依赖银行系统支持)。
+   * 示例值:微信支付提现
+   * 
+ */ + @SerializedName(value = "bank_memo") + private String bankMemo; + + /** + *
+   * 字段名:出款账户类型
+   * 变量名:account_type
+   * 是否必填:是
+   * 类型:string(16)
+   * 描述:
+   *  枚举值:
+   *    BASIC:基本户
+   *    OPERATION:运营账户
+   *    FEES:手续费账户
+   * 示例值:BASIC
+   * 
+ */ + @SerializedName(value = "account_type") + private String accountType; + + /** + *
+   * 字段名:回调通知地址
+   * 变量名:notify_url
+   * 是否必填:否
+   * 类型:string(256)
+   * 描述:
+   *  异步接收提现状态变更通知的回调地址,通知url必须为外网可访问的url,不能携带参数。
+   *  如果参数中传了notify_url,则商户平台上配置的回调地址将不会生效,优先回调当前传的地址。
+   * 示例值:https://www.weixin.qq.com/wxpay/pay.php
+   * 
+ */ + @SerializedName(value = "notify_url") + private String notifyUrl; +} diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/SubDayEndBalanceWithdrawResult.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/SubDayEndBalanceWithdrawResult.java new file mode 100644 index 0000000000..f64eacb7b2 --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/SubDayEndBalanceWithdrawResult.java @@ -0,0 +1,99 @@ +package com.github.binarywang.wxpay.bean.ecommerce; + +import com.google.gson.annotations.SerializedName; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * 二级商户按日终余额预约提现结果 + *
+ *   文档地址:https://pay.weixin.qq.com/doc/v3/partner/4013328143
+ * 
+ * + * @author copilot + * created on 2024/12/24 + */ +@Data +@NoArgsConstructor +public class SubDayEndBalanceWithdrawResult implements Serializable { + + private static final long serialVersionUID = -8745123456789012346L; + + /** + *
+   * 字段名:二级商户号
+   * 变量名:sub_mchid
+   * 是否必填:是
+   * 类型:string(32)
+   * 描述:
+   *  电商平台二级商户号,由微信支付生成并下发。
+   * 示例值:1900000109
+   * 
+ */ + @SerializedName(value = "sub_mchid") + private String subMchid; + + /** + *
+   * 字段名:电商平台商户号
+   * 变量名:sp_mchid
+   * 是否必填:是
+   * 类型:string(32)
+   * 描述:
+   *  电商平台商户号
+   * 示例值:1800000123
+   * 
+ */ + @SerializedName(value = "sp_mchid") + private String spMchid; + + /** + *
+   * 字段名:商户提现单号
+   * 变量名:out_request_no
+   * 是否必填:是
+   * 类型:string(32)
+   * 描述:
+   *  商户提现单号,由商户自定义生成。
+   * 示例值:20190611222222222200000000012122
+   * 
+ */ + @SerializedName(value = "out_request_no") + private String outRequestNo; + + /** + *
+   * 字段名:微信支付提现单号
+   * 变量名:withdraw_id
+   * 是否必填:是
+   * 类型:string(128)
+   * 描述:
+   *  电商平台提交二级商户提现申请后,由微信支付返回的申请单号,作为查询申请状态的唯一标识。
+   * 示例值:12321937198237912739132791732912793127931279317929791239112123
+   * 
+ */ + @SerializedName(value = "withdraw_id") + private String withdrawId; + + /** + *
+   * 字段名:提现单状态
+   * 变量名:status
+   * 是否必填:是
+   * 类型:string(16)
+   * 描述:
+   *  枚举值:
+   *  CREATE_SUCCESS:受理成功
+   *  SUCCESS:提现成功
+   *  FAIL:提现失败
+   *  REFUND:提现退票
+   *  CLOSE:关单
+   *  INIT:业务单已创建
+   * 示例值:CREATE_SUCCESS
+   * 
+ */ + @SerializedName(value = "status") + private String status; +} diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/SubDayEndBalanceWithdrawStatusResult.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/SubDayEndBalanceWithdrawStatusResult.java new file mode 100644 index 0000000000..c642ab15d2 --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/SubDayEndBalanceWithdrawStatusResult.java @@ -0,0 +1,261 @@ +package com.github.binarywang.wxpay.bean.ecommerce; + +import com.google.gson.annotations.SerializedName; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * 查询二级商户按日终余额预约提现状态 + *
+ *   文档地址:https://pay.weixin.qq.com/doc/v3/partner/4013328163
+ * 
+ * + * @author copilot + * created on 2024/12/24 + */ +@Data +@NoArgsConstructor +public class SubDayEndBalanceWithdrawStatusResult implements Serializable { + + private static final long serialVersionUID = -8745123456789012347L; + + /** + *
+   * 字段名:二级商户号
+   * 变量名:sub_mchid
+   * 是否必填:是
+   * 类型:string(32)
+   * 描述:
+   *  电商平台二级商户号,由微信支付生成并下发。
+   * 示例值:1900000109
+   * 
+ */ + @SerializedName(value = "sub_mchid") + private String subMchid; + + /** + *
+   * 字段名:电商平台商户号
+   * 变量名:sp_mchid
+   * 是否必填:是
+   * 类型:string(32)
+   * 描述:
+   *  电商平台商户号
+   * 示例值:1800000123
+   * 
+ */ + @SerializedName(value = "sp_mchid") + private String spMchid; + + /** + *
+   * 字段名:提现单状态
+   * 变量名:status
+   * 是否必填:是
+   * 类型:string(16)
+   * 描述:
+   *  枚举值:
+   *  CREATE_SUCCESS:受理成功
+   *  SUCCESS:提现成功
+   *  FAIL:提现失败
+   *  REFUND:提现退票
+   *  CLOSE:关单
+   *  INIT:业务单已创建
+   * 示例值:CREATE_SUCCESS
+   * 
+ */ + @SerializedName(value = "status") + private String status; + + /** + *
+   * 字段名:微信支付提现单号
+   * 变量名:withdraw_id
+   * 是否必填:是
+   * 类型:string(128)
+   * 描述:
+   *  电商平台提交二级商户提现申请后,由微信支付返回的申请单号,作为查询申请状态的唯一标识。
+   * 示例值:12321937198237912739132791732912793127931279317929791239112123
+   * 
+ */ + @SerializedName(value = "withdraw_id") + private String withdrawId; + + /** + *
+   * 字段名:商户提现单号
+   * 变量名:out_request_no
+   * 是否必填:是
+   * 类型:string(32)
+   * 描述:
+   *  商户提现单号,由商户自定义生成。
+   * 示例值:20190611222222222200000000012122
+   * 
+ */ + @SerializedName(value = "out_request_no") + private String outRequestNo; + + /** + *
+   * 字段名:提现金额
+   * 变量名:amount
+   * 是否必填:是
+   * 类型:int64
+   * 描述:
+   *  单位:分
+   * 示例值:1
+   * 
+ */ + @SerializedName(value = "amount") + private Integer amount; + + /** + *
+   * 字段名:发起提现时间
+   * 变量名:create_time
+   * 是否必填:是
+   * 类型:string(29)
+   * 描述:
+   *  遵循rfc3339标准格式,格式为YYYY-MM-DDTHH:mm:ss:sss+TIMEZONE,
+   *  YYYY-MM-DD表示年月日,T出现在字符串中,表示time元素的开头,HH:mm:ss:sss表示时分秒毫秒,
+   *  TIMEZONE表示时区(+08:00表示东八区时间,领先UTC 8小时,即北京时间)。
+   *  例如:2015-05-20T13:29:35+08:00表示,北京时间2015年5月20日13点29分35秒。
+   * 示例值:2015-05-20T13:29:35.120+08:00
+   * 
+ */ + @SerializedName(value = "create_time") + private String createTime; + + /** + *
+   * 字段名:提现状态更新时间
+   * 变量名:update_time
+   * 是否必填:是
+   * 类型:string(29)
+   * 描述:
+   *  遵循rfc3339标准格式,格式为YYYY-MM-DDTHH:mm:ss:sss+TIMEZONE,
+   *  YYYY-MM-DD表示年月日,T出现在字符串中,表示time元素的开头,HH:mm:ss:sss表示时分秒毫秒,
+   *  TIMEZONE表示时区(+08:00表示东八区时间,领先UTC 8小时,即北京时间)。
+   *  例如:2015-05-20T13:29:35+08:00表示,北京时间2015年5月20日13点29分35秒。
+   * 示例值:2015-05-20T13:29:35.120+08:00
+   * 
+ */ + @SerializedName(value = "update_time") + private String updateTime; + + /** + *
+   * 字段名:失败原因
+   * 变量名:reason
+   * 是否必填:否
+   * 类型:string(255)
+   * 描述:
+   *  仅在提现失败、退票、关单时有值
+   * 示例值:卡号错误
+   * 
+ */ + @SerializedName(value = "reason") + private String reason; + + /** + *
+   * 字段名:提现备注
+   * 变量名:remark
+   * 是否必填:否
+   * 类型:string(56)
+   * 描述:
+   *  商户对提现单的备注,若发起提现时未传入相应值或输入不合法,则该值为空
+   * 示例值:交易提现
+   * 
+ */ + @SerializedName(value = "remark") + private String remark; + + /** + *
+   * 字段名:银行附言
+   * 变量名:bank_memo
+   * 是否必填:否
+   * 类型:string(32)
+   * 描述:
+   *  展示在收款银行系统中的附言,由数字、字母、汉字组成(能否成功展示依赖银行系统支持)。若发起提现时未传入相应值或输入不合法,则该值为空
+   * 示例值:微信提现
+   * 
+ */ + @SerializedName(value = "bank_memo") + private String bankMemo; + + /** + *
+   * 字段名:出款账户类型
+   * 变量名:account_type
+   * 是否必填:是
+   * 类型:string(16)
+   * 描述:
+   *  BASIC:基本户
+   *  OPERATION:运营账户
+   *  FEES:手续费账户
+   * 示例值:BASIC
+   * 
+ */ + @SerializedName(value = "account_type") + private String accountType; + + /** + *
+   * 字段名:提现失败解决方案
+   * 变量名:solution
+   * 是否必填:否
+   * 类型:string(255)
+   * 描述:
+   *  仅在提现失败、退票、关单时有值
+   * 示例值:请修改结算银行卡信息
+   * 
+ */ + @SerializedName(value = "solution") + private String solution; + + /** + *
+   * 字段名:出款户名
+   * 变量名:account_name
+   * 是否必填:否
+   * 类型:string(256)
+   * 描述:
+   *  出款户名(加密)
+   * 示例值:2mPt3pWzZ+O3dSGbGnCrR3bqMZ5pwfpQy1NNrA==
+   * 
+ */ + @SerializedName(value = "account_name") + private String accountName; + + /** + *
+   * 字段名:出款账号
+   * 变量名:account_number
+   * 是否必填:否
+   * 类型:string(256)
+   * 描述:
+   *  出款账号(加密)
+   * 示例值:2mPt3pWzZ+O3dSGbGnCrR3bqMZ5pwfpQy1NNrA==
+   * 
+ */ + @SerializedName(value = "account_number") + private String accountNumber; + + /** + *
+   * 字段名:出款银行全称(含支行)
+   * 变量名:bank_name
+   * 是否必填:否
+   * 类型:string(256)
+   * 描述:
+   *  出款银行全称(含支行)(加密)
+   * 示例值:2mPt3pWzZ+O3dSGbGnCrR3bqMZ5pwfpQy1NNrA==
+   * 
+ */ + @SerializedName(value = "bank_name") + private String bankName; +} diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/SubWithdrawRequest.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/SubWithdrawRequest.java index 3c74db24c9..541d779a30 100644 --- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/SubWithdrawRequest.java +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/SubWithdrawRequest.java @@ -9,7 +9,7 @@ /** * 二级商户账户余额提现 *
- *   文档地址:https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pay/combine/chapter3_3.shtml
+ *   文档地址:https://pay.weixin.qq.com/doc/v3/partner/4012476652
  * 
*/ @Data @@ -86,4 +86,19 @@ public class SubWithdrawRequest implements Serializable { @SerializedName(value = "bank_memo") private String bankMemo; + /** + *
+   * 字段名:回调通知地址
+   * 变量名:notify_url
+   * 是否必填:否
+   * 类型:string(256)
+   * 描述:
+   *  异步接收提现状态变更通知的回调地址,通知url必须为外网可访问的url,不能携带参数。
+   *  如果参数中传了notify_url,则商户平台上配置的回调地址将不会生效,优先回调当前传的地址。
+   * 示例值:https://www.weixin.qq.com/wxpay/pay.php
+   * 
+ */ + @SerializedName(value = "notify_url") + private String notifyUrl; + } diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/WithdrawNotifyResult.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/WithdrawNotifyResult.java new file mode 100644 index 0000000000..c5223dd123 --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/WithdrawNotifyResult.java @@ -0,0 +1,266 @@ +package com.github.binarywang.wxpay.bean.ecommerce; + +import com.google.gson.annotations.SerializedName; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * 提现状态变更通知结果 + *
+ *   文档地址:https://pay.weixin.qq.com/doc/v3/partner/4013049135
+ * 
+ * + * @author copilot + * created on 2024/12/24 + */ +@Data +@NoArgsConstructor +public class WithdrawNotifyResult implements Serializable { + + private static final long serialVersionUID = -7451351849088368701L; + + /** + * 源数据 + */ + private NotifyResponse rawData; + + /** + *
+   * 字段名:电商平台商户号
+   * 变量名:sp_mchid
+   * 是否必填:是
+   * 类型:string(32)
+   * 描述:
+   *  微信支付分配给电商平台的商户号
+   * 示例值:1900000100
+   * 
+ */ + @SerializedName(value = "sp_mchid") + private String spMchid; + + /** + *
+   * 字段名:二级商户号
+   * 变量名:sub_mchid
+   * 是否必填:否
+   * 类型:string(32)
+   * 描述:
+   *  微信支付分配给二级商户的商户号,仅二级商户提现时返回
+   * 示例值:1900000109
+   * 
+ */ + @SerializedName(value = "sub_mchid") + private String subMchid; + + /** + *
+   * 字段名:商户提现单号
+   * 变量名:out_request_no
+   * 是否必填:是
+   * 类型:string(32)
+   * 描述:
+   *  商户提现单号,由商户自定义生成
+   * 示例值:20190611222222222200000000012122
+   * 
+ */ + @SerializedName(value = "out_request_no") + private String outRequestNo; + + /** + *
+   * 字段名:微信支付提现单号
+   * 变量名:withdraw_id
+   * 是否必填:是
+   * 类型:string(32)
+   * 描述:
+   *  微信支付提现单号
+   * 示例值:12321002198704230011101200
+   * 
+ */ + @SerializedName(value = "withdraw_id") + private String withdrawId; + + /** + *
+   * 字段名:提现状态
+   * 变量名:status
+   * 是否必填:是
+   * 类型:string(16)
+   * 描述:
+   *  提现状态:
+   *  CREATE_SUCCESS:受理成功
+   *  SUCCESS:提现成功
+   *  FAILED:提现失败
+   *  REFUND:提现退票
+   *  CLOSE:关单
+   * 示例值:SUCCESS
+   * 
+ */ + @SerializedName(value = "status") + private String status; + + /** + *
+   * 字段名:提现金额
+   * 变量名:amount
+   * 是否必填:是
+   * 类型:int64
+   * 描述:
+   *  提现金额,单位:分(人民币)
+   * 示例值:100
+   * 
+ */ + @SerializedName(value = "amount") + private Integer amount; + + /** + *
+   * 字段名:提现发起时间
+   * 变量名:create_time
+   * 是否必填:是
+   * 类型:string(29)
+   * 描述:
+   *  提现发起时间,遵循rfc3339标准格式,格式为YYYY-MM-DDTHH:mm:ss:sss+TIMEZONE,
+   *  YYYY-MM-DD表示年月日,T出现在字符串中,表示time元素的开头,HH:mm:ss:sss表示时分秒毫秒,
+   *  TIMEZONE表示时区(+08:00表示东八区时间,领先UTC 8小时,即北京时间)。
+   *  例如:2015-05-20T13:29:35.120+08:00表示,北京时间2015年5月20日13点29分35秒
+   * 示例值:2018-06-08T10:34:56+08:00
+   * 
+ */ + @SerializedName(value = "create_time") + private String createTime; + + /** + *
+   * 字段名:提现更新时间
+   * 变量名:update_time
+   * 是否必填:是
+   * 类型:string(29)
+   * 描述:
+   *  提现更新时间,遵循rfc3339标准格式,格式为YYYY-MM-DDTHH:mm:ss:sss+TIMEZONE,
+   *  YYYY-MM-DD表示年月日,T出现在字符串中,表示time元素的开头,HH:mm:ss:sss表示时分秒毫秒,
+   *  TIMEZONE表示时区(+08:00表示东八区时间,领先UTC 8小时,即北京时间)。
+   *  例如:2015-05-20T13:29:35.120+08:00表示,北京时间2015年5月20日13点29分35秒
+   * 示例值:2018-06-08T10:34:56+08:00
+   * 
+ */ + @SerializedName(value = "update_time") + private String updateTime; + + /** + *
+   * 字段名:失败原因
+   * 变量名:reason
+   * 是否必填:否
+   * 类型:string(256)
+   * 描述:
+   *  提现失败原因,仅在提现失败、退票时有值
+   * 示例值:账户余额不足
+   * 
+ */ + @SerializedName(value = "reason") + private String reason; + + /** + *
+   * 字段名:备注
+   * 变量名:remark
+   * 是否必填:否
+   * 类型:string(256)
+   * 描述:
+   *  商户对提现单的备注,若提现申请时未传递,则无此字段
+   * 示例值:交易提现
+   * 
+ */ + @SerializedName(value = "remark") + private String remark; + + /** + *
+   * 字段名:银行附言
+   * 变量名:bank_memo
+   * 是否必填:否
+   * 类型:string(256)
+   * 描述:
+   *  展示在收款银行系统中的附言,若提现申请时未传递,则无此字段
+   * 示例值:微信支付提现
+   * 
+ */ + @SerializedName(value = "bank_memo") + private String bankMemo; + + /** + *
+   * 字段名:账户类型
+   * 变量名:account_type
+   * 是否必填:否
+   * 类型:string(16)
+   * 描述:
+   *  提现账户类型,仅电商平台提现时返回:
+   *  BASIC:基本账户
+   *  OPERATION:运营账户
+   *  FEES:手续费账户
+   * 示例值:BASIC
+   * 
+ */ + @SerializedName(value = "account_type") + private String accountType; + + /** + *
+   * 字段名:提现失败解决方案
+   * 变量名:solution
+   * 是否必填:否
+   * 类型:string(255)
+   * 描述:
+   *  仅在提现失败、退票、关单时有值
+   * 示例值:请修改结算银行卡信息
+   * 
+ */ + @SerializedName(value = "solution") + private String solution; + + /** + *
+   * 字段名:出款户名
+   * 变量名:account_name
+   * 是否必填:否
+   * 类型:string(256)
+   * 描述:
+   *  出款户名(加密)
+   * 示例值:2mPt3pWzZ+O3dSGbGnCrR3bqMZ5pwfpQy1NNrA==
+   * 
+ */ + @SerializedName(value = "account_name") + private String accountName; + + /** + *
+   * 字段名:出款账号
+   * 变量名:account_number
+   * 是否必填:否
+   * 类型:string(256)
+   * 描述:
+   *  出款账号(加密)
+   * 示例值:2mPt3pWzZ+O3dSGbGnCrR3bqMZ5pwfpQy1NNrA==
+   * 
+ */ + @SerializedName(value = "account_number") + private String accountNumber; + + /** + *
+   * 字段名:出款银行全称(含支行)
+   * 变量名:bank_name
+   * 是否必填:否
+   * 类型:string(256)
+   * 描述:
+   *  出款银行全称(含支行)(加密)
+   * 示例值:2mPt3pWzZ+O3dSGbGnCrR3bqMZ5pwfpQy1NNrA==
+   * 
+ */ + @SerializedName(value = "bank_name") + private String bankName; +} \ No newline at end of file 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/ComplaintNotifyResult.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/notify/ComplaintNotifyResult.java
index a5d18df6df..9464144c1d 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/notify/ComplaintNotifyResult.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/notify/ComplaintNotifyResult.java
@@ -47,13 +47,23 @@ public static class DecryptNotifyResult implements Serializable {
      * 是否必填:是
      * 描述:
      * 触发本次投诉通知回调的具体动作类型,枚举如下:
+     * 常规通知:
      * CREATE_COMPLAINT:用户提交投诉
      * CONTINUE_COMPLAINT:用户继续投诉
      * USER_RESPONSE:用户新留言
      * RESPONSE_BY_PLATFORM:平台新留言
-     * SELLER_REFUND:收款方全额退款
+     * SELLER_REFUND:商户发起全额退款
      * MERCHANT_RESPONSE:商户新回复
      * MERCHANT_CONFIRM_COMPLETE:商户反馈处理完成
+     * USER_APPLY_PLATFORM_SERVICE:用户申请平台协助
+     * USER_CANCEL_PLATFORM_SERVICE:用户取消平台协助
+     * PLATFORM_SERVICE_FINISHED:客服结束平台协助
+     *
+     * 申请退款单的附加通知:
+     * 以下通知会更新投诉单状态,建议收到后查询投诉单详情。
+     * MERCHANT_APPROVE_REFUND:商户同意退款
+     * MERCHANT_REJECT_REFUND:商户驳回退款
+     * REFUND_SUCCESS:退款到账
      * 
*/ @SerializedName(value = "action_type") 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..8f16e5d4e4 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 @@ -342,7 +342,9 @@ public static WxPayOrderNotifyResult fromXML(String xmlString) { @Override public Map toMap() { - Map resultMap = SignUtils.xmlBean2Map(this); + // 使用父类的 toMap() 方法,直接从原始 XML 解析所有字段, + // 确保包含未在 Java Bean 中定义的字段,避免签名验证失败 + Map resultMap = super.toMap(); if (this.getCouponCount() != null && this.getCouponCount() > 0) { for (int i = 0; i < this.getCouponCount(); i++) { WxPayOrderNotifyCoupon coupon = couponList.get(i); @@ -387,7 +389,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/WxDepositConsumeRequest.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/request/WxDepositConsumeRequest.java new file mode 100644 index 0000000000..b99093cd44 --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/request/WxDepositConsumeRequest.java @@ -0,0 +1,96 @@ +package com.github.binarywang.wxpay.bean.request; + +import com.github.binarywang.wxpay.exception.WxPayException; +import com.thoughtworks.xstream.annotations.XStreamAlias; +import lombok.*; +import me.chanjar.weixin.common.annotation.Required; + +import java.util.Map; + +/** + *
+ *   押金消费请求
+ *   详见:https://pay.weixin.qq.com/wiki/doc/api/deposit_sl.php?chapter=27_7&index=4
+ * 
+ * + * @author Binary Wang + * created on 2024-09-24 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@Builder(builderMethodName = "newBuilder") +@NoArgsConstructor +@AllArgsConstructor +@XStreamAlias("xml") +public class WxDepositConsumeRequest extends BaseWxPayRequest { + + /** + *
+   * 微信押金订单号
+   * transaction_id
+   * 是
+   * String(32)
+   * 1009660380201506130728806387
+   * 微信押金订单号
+   * 
+ */ + @Required + @XStreamAlias("transaction_id") + private String transactionId; + + /** + *
+   * 商户消费单号
+   * out_trade_no
+   * 是
+   * String(32)
+   * 20150806125346
+   * 商户系统内部的消费单号,要求32个字符内,只能是数字、大小写字母_-|*@ ,且在同一个商户号下唯一
+   * 
+ */ + @Required + @XStreamAlias("out_trade_no") + private String outTradeNo; + + /** + *
+   * 消费金额
+   * consume_fee
+   * 是
+   * Int
+   * 88
+   * 消费金额,单位为分,不能大于押金金额
+   * 
+ */ + @Required + @XStreamAlias("consume_fee") + private Integer consumeFee; + + /** + *
+   * 消费描述
+   * consume_desc
+   * 否
+   * String(128)
+   * 单车使用费
+   * 对一笔消费的具体描述信息
+   * 
+ */ + @XStreamAlias("consume_desc") + private String consumeDesc; + + @Override + protected void checkConstraints() throws WxPayException { + // No additional constraints beyond @Required fields + } + + @Override + protected void storeMap(Map map) { + map.put("transaction_id", transactionId); + map.put("out_trade_no", outTradeNo); + map.put("consume_fee", consumeFee.toString()); + if (consumeDesc != null) { + map.put("consume_desc", consumeDesc); + } + } +} \ No newline at end of file diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/request/WxDepositOrderQueryRequest.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/request/WxDepositOrderQueryRequest.java new file mode 100644 index 0000000000..d087649fb5 --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/request/WxDepositOrderQueryRequest.java @@ -0,0 +1,69 @@ +package com.github.binarywang.wxpay.bean.request; + +import com.github.binarywang.wxpay.exception.WxPayException; +import com.thoughtworks.xstream.annotations.XStreamAlias; +import lombok.*; +import me.chanjar.weixin.common.annotation.Required; + +import java.util.Map; + +/** + *
+ *   查询押金订单请求
+ *   详见:https://pay.weixin.qq.com/wiki/doc/api/deposit_sl.php?chapter=27_7&index=3
+ * 
+ * + * @author Binary Wang + * created on 2024-09-24 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@Builder(builderMethodName = "newBuilder") +@NoArgsConstructor +@AllArgsConstructor +@XStreamAlias("xml") +public class WxDepositOrderQueryRequest extends BaseWxPayRequest { + + /** + *
+   * 微信订单号
+   * transaction_id
+   * 否
+   * String(32)
+   * 1009660380201506130728806387
+   * 微信的订单号,优先使用
+   * 
+ */ + @XStreamAlias("transaction_id") + private String transactionId; + + /** + *
+   * 商户订单号
+   * out_trade_no
+   * 否
+   * String(32)
+   * 20150806125346
+   * 商户系统内部的订单号,当没提供transaction_id时需要传这个
+   * 
+ */ + @XStreamAlias("out_trade_no") + private String outTradeNo; + + @Override + protected void checkConstraints() throws WxPayException { + if (transactionId == null && outTradeNo == null) { + throw new WxPayException("transaction_id 和 out_trade_no 不能同时为空"); + } + } + + @Override + protected void storeMap(Map map) { + if (transactionId != null) { + map.put("transaction_id", transactionId); + } + if (outTradeNo != null) { + map.put("out_trade_no", outTradeNo); + } + } +} \ No newline at end of file diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/request/WxDepositRefundRequest.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/request/WxDepositRefundRequest.java new file mode 100644 index 0000000000..65394be4a9 --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/request/WxDepositRefundRequest.java @@ -0,0 +1,115 @@ +package com.github.binarywang.wxpay.bean.request; + +import com.github.binarywang.wxpay.exception.WxPayException; +import com.thoughtworks.xstream.annotations.XStreamAlias; +import lombok.*; +import me.chanjar.weixin.common.annotation.Required; + +import java.util.Map; + +/** + *
+ *   押金退款请求
+ *   详见:https://pay.weixin.qq.com/wiki/doc/api/deposit_sl.php?chapter=27_7&index=6
+ * 
+ * + * @author Binary Wang + * created on 2024-09-24 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@Builder(builderMethodName = "newBuilder") +@NoArgsConstructor +@AllArgsConstructor +@XStreamAlias("xml") +public class WxDepositRefundRequest extends BaseWxPayRequest { + + /** + *
+   * 微信押金订单号
+   * transaction_id
+   * 否
+   * String(32)
+   * 1009660380201506130728806387
+   * 微信押金订单号,与out_trade_no二选一
+   * 
+ */ + @XStreamAlias("transaction_id") + private String transactionId; + + /** + *
+   * 商户押金订单号
+   * out_trade_no
+   * 否
+   * String(32)
+   * 20150806125346
+   * 商户系统内部的押金订单号,与transaction_id二选一
+   * 
+ */ + @XStreamAlias("out_trade_no") + private String outTradeNo; + + /** + *
+   * 商户退款单号
+   * out_refund_no
+   * 是
+   * String(32)
+   * 1217752501201407033233368018
+   * 商户系统内部的退款单号,商户系统内部唯一,同一退款单号多次请求只退一笔
+   * 
+ */ + @Required + @XStreamAlias("out_refund_no") + private String outRefundNo; + + /** + *
+   * 退款金额
+   * refund_fee
+   * 是
+   * Int
+   * 100
+   * 退款总金额,订单总金额,单位为分,只能为整数
+   * 
+ */ + @Required + @XStreamAlias("refund_fee") + private Integer refundFee; + + /** + *
+   * 退款原因
+   * refund_desc
+   * 否
+   * String(80)
+   * 商品已售完
+   * 若商户传入,会在下发给用户的退款消息中体现退款原因
+   * 
+ */ + @XStreamAlias("refund_desc") + private String refundDesc; + + @Override + protected void checkConstraints() throws WxPayException { + if (transactionId == null && outTradeNo == null) { + throw new WxPayException("transaction_id 和 out_trade_no 不能同时为空"); + } + } + + @Override + protected void storeMap(Map map) { + if (transactionId != null) { + map.put("transaction_id", transactionId); + } + if (outTradeNo != null) { + map.put("out_trade_no", outTradeNo); + } + map.put("out_refund_no", outRefundNo); + map.put("refund_fee", refundFee.toString()); + if (refundDesc != null) { + map.put("refund_desc", refundDesc); + } + } +} \ No newline at end of file diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/request/WxDepositUnfreezeRequest.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/request/WxDepositUnfreezeRequest.java new file mode 100644 index 0000000000..63980fc00f --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/request/WxDepositUnfreezeRequest.java @@ -0,0 +1,96 @@ +package com.github.binarywang.wxpay.bean.request; + +import com.github.binarywang.wxpay.exception.WxPayException; +import com.thoughtworks.xstream.annotations.XStreamAlias; +import lombok.*; +import me.chanjar.weixin.common.annotation.Required; + +import java.util.Map; + +/** + *
+ *   押金撤销请求
+ *   详见:https://pay.weixin.qq.com/wiki/doc/api/deposit_sl.php?chapter=27_7&index=5
+ * 
+ * + * @author Binary Wang + * created on 2024-09-24 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@Builder(builderMethodName = "newBuilder") +@NoArgsConstructor +@AllArgsConstructor +@XStreamAlias("xml") +public class WxDepositUnfreezeRequest extends BaseWxPayRequest { + + /** + *
+   * 微信押金订单号
+   * transaction_id
+   * 是
+   * String(32)
+   * 1009660380201506130728806387
+   * 微信押金订单号
+   * 
+ */ + @Required + @XStreamAlias("transaction_id") + private String transactionId; + + /** + *
+   * 商户撤销单号
+   * out_trade_no
+   * 是
+   * String(32)
+   * 20150806125346
+   * 商户系统内部的撤销单号,要求32个字符内,只能是数字、大小写字母_-|*@ ,且在同一个商户号下唯一
+   * 
+ */ + @Required + @XStreamAlias("out_trade_no") + private String outTradeNo; + + /** + *
+   * 撤销金额
+   * unfreeze_fee
+   * 是
+   * Int
+   * 99
+   * 撤销金额,单位为分,不能大于剩余押金金额
+   * 
+ */ + @Required + @XStreamAlias("unfreeze_fee") + private Integer unfreezeFee; + + /** + *
+   * 撤销原因
+   * unfreeze_desc
+   * 否
+   * String(128)
+   * 用户主动取消
+   * 对一笔撤销的具体原因描述
+   * 
+ */ + @XStreamAlias("unfreeze_desc") + private String unfreezeDesc; + + @Override + protected void checkConstraints() throws WxPayException { + // No additional constraints beyond @Required fields + } + + @Override + protected void storeMap(Map map) { + map.put("transaction_id", transactionId); + map.put("out_trade_no", outTradeNo); + map.put("unfreeze_fee", unfreezeFee.toString()); + if (unfreezeDesc != null) { + map.put("unfreeze_desc", unfreezeDesc); + } + } +} \ No newline at end of file diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/request/WxDepositUnifiedOrderRequest.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/request/WxDepositUnifiedOrderRequest.java new file mode 100644 index 0000000000..cffb9a34e5 --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/request/WxDepositUnifiedOrderRequest.java @@ -0,0 +1,223 @@ +package com.github.binarywang.wxpay.bean.request; + +import com.github.binarywang.wxpay.exception.WxPayException; +import com.thoughtworks.xstream.annotations.XStreamAlias; +import lombok.*; +import me.chanjar.weixin.common.annotation.Required; + +import java.util.Map; + +/** + *
+ *   押金下单请求
+ *   详见:https://pay.weixin.qq.com/wiki/doc/api/deposit_sl.php?chapter=27_7&index=2
+ * 
+ * + * @author Binary Wang + * created on 2024-09-24 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@Builder(builderMethodName = "newBuilder") +@NoArgsConstructor +@AllArgsConstructor +@XStreamAlias("xml") +public class WxDepositUnifiedOrderRequest extends BaseWxPayRequest { + + /** + *
+   * 押金商品描述
+   * body
+   * 是
+   * String(128)
+   * 共享单车押金
+   * 押金商品描述
+   * 
+ */ + @Required + @XStreamAlias("body") + private String body; + + /** + *
+   * 商户订单号
+   * out_trade_no
+   * 是
+   * String(32)
+   * 20150806125346
+   * 商户系统内部的订单号,32个字符内、可包含字母, 其他说明见商户订单号
+   * 
+ */ + @Required + @XStreamAlias("out_trade_no") + private String outTradeNo; + + /** + *
+   * 押金金额
+   * total_fee
+   * 是
+   * Int
+   * 99
+   * 押金金额,单位为分,只能为整数,详见支付金额
+   * 
+ */ + @Required + @XStreamAlias("total_fee") + private Integer totalFee; + + /** + *
+   * 终端IP
+   * spbill_create_ip
+   * 是
+   * String(16)
+   * 123.12.12.123
+   * 用户端实际ip
+   * 
+ */ + @Required + @XStreamAlias("spbill_create_ip") + private String spbillCreateIp; + + /** + *
+   * 通知地址
+   * notify_url
+   * 是
+   * String(256)
+   * http://www.weixin.qq.com/wxpay/pay.php
+   * 接收微信支付异步通知回调地址
+   * 
+ */ + @Required + @XStreamAlias("notify_url") + private String notifyUrl; + + /** + *
+   * 交易类型
+   * trade_type
+   * 是
+   * String(16)
+   * JSAPI
+   * 交易类型,取值如下:JSAPI,NATIVE,APP,WAP
+   * 
+ */ + @Required + @XStreamAlias("trade_type") + private String tradeType; + + /** + *
+   * 用户标识
+   * openid
+   * 否
+   * String(128)
+   * oUpF8uMuAJO_M2pxb1Q9zNjWeS6o
+   * trade_type=JSAPI时,此参数必传,用户在商户appid下的唯一标识。
+   * 
+ */ + @XStreamAlias("openid") + private String openid; + + /** + *
+   * 商品详情
+   * detail
+   * 否
+   * String(8192)
+   * 详情
+   * 商品名称明细列表
+   * 
+ */ + @XStreamAlias("detail") + private String detail; + + /** + *
+   * 附加数据
+   * attach
+   * 否
+   * String(127)
+   * 深圳分店
+   * 附加数据,在查询API和支付通知中原样返回,该字段主要用于商户携带订单的自定义数据
+   * 
+ */ + @XStreamAlias("attach") + private String attach; + + /** + *
+   * 货币类型
+   * fee_type
+   * 否
+   * String(16)
+   * CNY
+   * 符合ISO 4217标准的三位字母代码,默认人民币:CNY
+   * 
+ */ + @XStreamAlias("fee_type") + private String feeType; + + /** + *
+   * 交易起始时间
+   * time_start
+   * 否
+   * String(14)
+   * 20091225091010
+   * 订单生成时间,格式为yyyyMMddHHmmss
+   * 
+ */ + @XStreamAlias("time_start") + private String timeStart; + + /** + *
+   * 交易结束时间
+   * time_expire
+   * 否
+   * String(14)
+   * 20091227091010
+   * 订单失效时间,格式为yyyyMMddHHmmss
+   * 
+ */ + @XStreamAlias("time_expire") + private String timeExpire; + + @Override + protected void checkConstraints() throws WxPayException { + if ("JSAPI".equals(this.tradeType) && this.openid == null) { + throw new WxPayException("当trade_type为JSAPI时,openid为必填参数"); + } + } + + @Override + protected void storeMap(Map map) { + map.put("body", body); + map.put("out_trade_no", outTradeNo); + map.put("total_fee", totalFee.toString()); + map.put("spbill_create_ip", spbillCreateIp); + map.put("notify_url", notifyUrl); + map.put("trade_type", tradeType); + if (openid != null) { + map.put("openid", openid); + } + if (detail != null) { + map.put("detail", detail); + } + if (attach != null) { + map.put("attach", attach); + } + if (feeType != null) { + map.put("fee_type", feeType); + } + if (timeStart != null) { + map.put("time_start", timeStart); + } + if (timeExpire != null) { + map.put("time_expire", timeExpire); + } + } +} \ No newline at end of file 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/WxDepositConsumeResult.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/result/WxDepositConsumeResult.java new file mode 100644 index 0000000000..dfb1bd3e69 --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/result/WxDepositConsumeResult.java @@ -0,0 +1,103 @@ +package com.github.binarywang.wxpay.bean.result; + +import com.thoughtworks.xstream.annotations.XStreamAlias; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import org.w3c.dom.Document; + +import java.io.Serializable; + +/** + *
+ *   押金消费结果
+ *   详见:https://pay.weixin.qq.com/wiki/doc/api/deposit_sl.php?chapter=27_7&index=4
+ * 
+ * + * @author Binary Wang + * created on 2024-09-24 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@AllArgsConstructor +@NoArgsConstructor +@XStreamAlias("xml") +public class WxDepositConsumeResult extends BaseWxPayResult implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + *
+   * 微信订单号
+   * transaction_id
+   * 是
+   * String(32)
+   * 1217752501201407033233368018
+   * 微信支付押金订单号
+   * 
+ */ + @XStreamAlias("transaction_id") + private String transactionId; + + /** + *
+   * 商户消费单号
+   * out_trade_no
+   * 是
+   * String(32)
+   * 20150806125346
+   * 商户系统内部的消费单号
+   * 
+ */ + @XStreamAlias("out_trade_no") + private String outTradeNo; + + /** + *
+   * 消费金额
+   * consume_fee
+   * 是
+   * Int
+   * 88
+   * 本次消费的金额,单位为分
+   * 
+ */ + @XStreamAlias("consume_fee") + private Integer consumeFee; + + /** + *
+   * 剩余押金
+   * remain_fee
+   * 是
+   * Int
+   * 11
+   * 剩余押金金额,单位为分
+   * 
+ */ + @XStreamAlias("remain_fee") + private Integer remainFee; + + /** + *
+   * 消费时间
+   * time_end
+   * 是
+   * String(14)
+   * 20141030133525
+   * 消费完成时间,格式为yyyyMMddHHmmss
+   * 
+ */ + @XStreamAlias("time_end") + private String timeEnd; + + @Override + protected void loadXml(Document d) { + transactionId = readXmlString(d, "transaction_id"); + outTradeNo = readXmlString(d, "out_trade_no"); + consumeFee = readXmlInteger(d, "consume_fee"); + remainFee = readXmlInteger(d, "remain_fee"); + timeEnd = readXmlString(d, "time_end"); + } +} \ No newline at end of file diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/result/WxDepositOrderQueryResult.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/result/WxDepositOrderQueryResult.java new file mode 100644 index 0000000000..66a5acb658 --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/result/WxDepositOrderQueryResult.java @@ -0,0 +1,152 @@ +package com.github.binarywang.wxpay.bean.result; + +import com.thoughtworks.xstream.annotations.XStreamAlias; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import org.w3c.dom.Document; + +import java.io.Serializable; + +/** + *
+ *   查询押金订单结果
+ *   详见:https://pay.weixin.qq.com/wiki/doc/api/deposit_sl.php?chapter=27_7&index=3
+ * 
+ * + * @author Binary Wang + * created on 2024-09-24 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@AllArgsConstructor +@NoArgsConstructor +@XStreamAlias("xml") +public class WxDepositOrderQueryResult extends BaseWxPayResult implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + *
+   * 微信订单号
+   * transaction_id
+   * 是
+   * String(32)
+   * 1217752501201407033233368018
+   * 微信支付订单号
+   * 
+ */ + @XStreamAlias("transaction_id") + private String transactionId; + + /** + *
+   * 商户订单号
+   * out_trade_no
+   * 是
+   * String(32)
+   * 20150806125346
+   * 商户系统内部订单号
+   * 
+ */ + @XStreamAlias("out_trade_no") + private String outTradeNo; + + /** + *
+   * 交易状态
+   * trade_state
+   * 是
+   * String(32)
+   * SUCCESS
+   * 交易状态:
+   * SUCCESS—支付成功
+   * REFUND—转入退款
+   * NOTPAY—未支付
+   * CLOSED—已关闭
+   * REVOKED—已撤销(付款码支付)
+   * USERPAYING—用户支付中(付款码支付)
+   * PAYERROR—支付失败(其他原因,如银行返回失败)
+   * 
+ */ + @XStreamAlias("trade_state") + private String tradeState; + + /** + *
+   * 交易状态描述
+   * trade_state_desc
+   * 是
+   * String(256)
+   * 支付成功
+   * 对当前查询订单状态的描述和下一步操作的指引
+   * 
+ */ + @XStreamAlias("trade_state_desc") + private String tradeStateDesc; + + /** + *
+   * 押金金额
+   * total_fee
+   * 否
+   * Int
+   * 99
+   * 订单总金额,单位为分
+   * 
+ */ + @XStreamAlias("total_fee") + private Integer totalFee; + + /** + *
+   * 现金支付金额
+   * cash_fee
+   * 否
+   * Int
+   * 99
+   * 现金支付金额订单现金支付金额
+   * 
+ */ + @XStreamAlias("cash_fee") + private Integer cashFee; + + /** + *
+   * 支付完成时间
+   * time_end
+   * 否
+   * String(14)
+   * 20141030133525
+   * 订单支付时间,格式为yyyyMMddHHmmss
+   * 
+ */ + @XStreamAlias("time_end") + private String timeEnd; + + /** + *
+   * 剩余押金
+   * remain_fee
+   * 否
+   * Int
+   * 88
+   * 剩余押金金额,单位为分
+   * 
+ */ + @XStreamAlias("remain_fee") + private Integer remainFee; + + @Override + protected void loadXml(Document d) { + transactionId = readXmlString(d, "transaction_id"); + outTradeNo = readXmlString(d, "out_trade_no"); + tradeState = readXmlString(d, "trade_state"); + tradeStateDesc = readXmlString(d, "trade_state_desc"); + totalFee = readXmlInteger(d, "total_fee"); + cashFee = readXmlInteger(d, "cash_fee"); + timeEnd = readXmlString(d, "time_end"); + remainFee = readXmlInteger(d, "remain_fee"); + } +} \ No newline at end of file diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/result/WxDepositRefundResult.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/result/WxDepositRefundResult.java new file mode 100644 index 0000000000..7c25b534a5 --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/result/WxDepositRefundResult.java @@ -0,0 +1,103 @@ +package com.github.binarywang.wxpay.bean.result; + +import com.thoughtworks.xstream.annotations.XStreamAlias; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import org.w3c.dom.Document; + +import java.io.Serializable; + +/** + *
+ *   押金退款结果
+ *   详见:https://pay.weixin.qq.com/wiki/doc/api/deposit_sl.php?chapter=27_7&index=6
+ * 
+ * + * @author Binary Wang + * created on 2024-09-24 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@AllArgsConstructor +@NoArgsConstructor +@XStreamAlias("xml") +public class WxDepositRefundResult extends BaseWxPayResult implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + *
+   * 微信订单号
+   * transaction_id
+   * 是
+   * String(32)
+   * 1217752501201407033233368018
+   * 微信支付押金订单号
+   * 
+ */ + @XStreamAlias("transaction_id") + private String transactionId; + + /** + *
+   * 商户退款单号
+   * out_refund_no
+   * 是
+   * String(32)
+   * 1217752501201407033233368018
+   * 商户系统内部的退款单号
+   * 
+ */ + @XStreamAlias("out_refund_no") + private String outRefundNo; + + /** + *
+   * 微信退款单号
+   * refund_id
+   * 是
+   * String(32)
+   * 1217752501201407033233368018
+   * 微信退款单号
+   * 
+ */ + @XStreamAlias("refund_id") + private String refundId; + + /** + *
+   * 退款金额
+   * refund_fee
+   * 是
+   * Int
+   * 100
+   * 退款总金额,单位为分,可以做部分退款
+   * 
+ */ + @XStreamAlias("refund_fee") + private Integer refundFee; + + /** + *
+   * 现金退款金额
+   * cash_refund_fee
+   * 否
+   * Int
+   * 100
+   * 现金退款金额,单位为分,只能为整数
+   * 
+ */ + @XStreamAlias("cash_refund_fee") + private Integer cashRefundFee; + + @Override + protected void loadXml(Document d) { + transactionId = readXmlString(d, "transaction_id"); + outRefundNo = readXmlString(d, "out_refund_no"); + refundId = readXmlString(d, "refund_id"); + refundFee = readXmlInteger(d, "refund_fee"); + cashRefundFee = readXmlInteger(d, "cash_refund_fee"); + } +} \ No newline at end of file diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/result/WxDepositUnfreezeResult.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/result/WxDepositUnfreezeResult.java new file mode 100644 index 0000000000..98da8c878b --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/result/WxDepositUnfreezeResult.java @@ -0,0 +1,103 @@ +package com.github.binarywang.wxpay.bean.result; + +import com.thoughtworks.xstream.annotations.XStreamAlias; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import org.w3c.dom.Document; + +import java.io.Serializable; + +/** + *
+ *   押金撤销结果
+ *   详见:https://pay.weixin.qq.com/wiki/doc/api/deposit_sl.php?chapter=27_7&index=5
+ * 
+ * + * @author Binary Wang + * created on 2024-09-24 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@AllArgsConstructor +@NoArgsConstructor +@XStreamAlias("xml") +public class WxDepositUnfreezeResult extends BaseWxPayResult implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + *
+   * 微信订单号
+   * transaction_id
+   * 是
+   * String(32)
+   * 1217752501201407033233368018
+   * 微信支付押金订单号
+   * 
+ */ + @XStreamAlias("transaction_id") + private String transactionId; + + /** + *
+   * 商户撤销单号
+   * out_trade_no
+   * 是
+   * String(32)
+   * 20150806125346
+   * 商户系统内部的撤销单号
+   * 
+ */ + @XStreamAlias("out_trade_no") + private String outTradeNo; + + /** + *
+   * 撤销金额
+   * unfreeze_fee
+   * 是
+   * Int
+   * 99
+   * 撤销的押金金额,单位为分
+   * 
+ */ + @XStreamAlias("unfreeze_fee") + private Integer unfreezeFee; + + /** + *
+   * 剩余押金
+   * remain_fee
+   * 是
+   * Int
+   * 0
+   * 剩余押金金额,单位为分
+   * 
+ */ + @XStreamAlias("remain_fee") + private Integer remainFee; + + /** + *
+   * 撤销时间
+   * time_end
+   * 是
+   * String(14)
+   * 20141030133525
+   * 撤销完成时间,格式为yyyyMMddHHmmss
+   * 
+ */ + @XStreamAlias("time_end") + private String timeEnd; + + @Override + protected void loadXml(Document d) { + transactionId = readXmlString(d, "transaction_id"); + outTradeNo = readXmlString(d, "out_trade_no"); + unfreezeFee = readXmlInteger(d, "unfreeze_fee"); + remainFee = readXmlInteger(d, "remain_fee"); + timeEnd = readXmlString(d, "time_end"); + } +} \ No newline at end of file diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/result/WxDepositUnifiedOrderResult.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/result/WxDepositUnifiedOrderResult.java new file mode 100644 index 0000000000..120aeb111a --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/result/WxDepositUnifiedOrderResult.java @@ -0,0 +1,89 @@ +package com.github.binarywang.wxpay.bean.result; + +import com.thoughtworks.xstream.annotations.XStreamAlias; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import org.w3c.dom.Document; + +import java.io.Serializable; + +/** + *
+ *   押金下单结果
+ *   详见:https://pay.weixin.qq.com/wiki/doc/api/deposit_sl.php?chapter=27_7&index=2
+ * 
+ * + * @author Binary Wang + * created on 2024-09-24 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@AllArgsConstructor +@NoArgsConstructor +@XStreamAlias("xml") +public class WxDepositUnifiedOrderResult extends BaseWxPayResult implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + *
+   * 交易类型
+   * trade_type
+   * 是
+   * String(16)
+   * JSAPI
+   * 交易类型,取值为:JSAPI,NATIVE,APP等
+   * 
+ */ + @XStreamAlias("trade_type") + private String tradeType; + + /** + *
+   * 预支付交易会话标识
+   * prepay_id
+   * 是
+   * String(64)
+   * wx201410272009395522657a690389285100
+   * 微信生成的预支付会话标识,用于后续接口调用中使用,该值有效期为2小时
+   * 
+ */ + @XStreamAlias("prepay_id") + private String prepayId; + + /** + *
+   * 二维码链接
+   * code_url
+   * 否
+   * String(64)
+   * URl:weixin://wxpay/s/An4baqw
+   * trade_type 为 NATIVE 时有返回,可将该参数值生成二维码展示出来进行扫码支付
+   * 
+ */ + @XStreamAlias("code_url") + private String codeUrl; + + /** + *
+   * 微信订单号
+   * transaction_id
+   * 是
+   * String(32)
+   * 1217752501201407033233368018
+   * 微信支付分配的交易会话标识
+   * 
+ */ + @XStreamAlias("transaction_id") + private String transactionId; + + @Override + protected void loadXml(Document d) { + tradeType = readXmlString(d, "trade_type"); + prepayId = readXmlString(d, "prepay_id"); + codeUrl = readXmlString(d, "code_url"); + transactionId = readXmlString(d, "transaction_id"); + } +} \ 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/bean/subscriptionbilling/BillingPlan.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/subscriptionbilling/BillingPlan.java new file mode 100644 index 0000000000..b664f4cb7b --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/subscriptionbilling/BillingPlan.java @@ -0,0 +1,110 @@ +package com.github.binarywang.wxpay.bean.subscriptionbilling; + +import com.google.gson.annotations.SerializedName; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * 扣费计划信息 + *
+ *   文档地址:https://pay.weixin.qq.com/doc/v3/merchant/4012161105
+ * 
+ * + * @author Binary Wang + */ +@Data +@NoArgsConstructor +public class BillingPlan implements Serializable { + private static final long serialVersionUID = 1L; + + /** + *
+   * 字段名:计划类型
+   * 变量名:plan_type
+   * 是否必填:是
+   * 类型:string(32)
+   * 描述:
+   *  扣费计划类型
+   *  MONTHLY:按月扣费
+   *  WEEKLY:按周扣费
+   *  DAILY:按日扣费
+   *  YEARLY:按年扣费
+   *  示例值:MONTHLY
+   * 
+ */ + @SerializedName("plan_type") + private String planType; + + /** + *
+   * 字段名:扣费周期
+   * 变量名:period
+   * 是否必填:是
+   * 类型:int
+   * 描述:
+   *  扣费周期,配合plan_type使用
+   *  例如:plan_type为MONTHLY,period为1,表示每1个月扣费一次
+   *  示例值:1
+   * 
+ */ + @SerializedName("period") + private Integer period; + + /** + *
+   * 字段名:总扣费次数
+   * 变量名:total_count
+   * 是否必填:否
+   * 类型:int
+   * 描述:
+   *  总扣费次数,不填表示无限次扣费
+   *  示例值:12
+   * 
+ */ + @SerializedName("total_count") + private Integer totalCount; + + /** + *
+   * 字段名:已扣费次数
+   * 变量名:executed_count
+   * 是否必填:否
+   * 类型:int
+   * 描述:
+   *  已扣费次数,查询时返回
+   *  示例值:2
+   * 
+ */ + @SerializedName("executed_count") + private Integer executedCount; + + /** + *
+   * 字段名:计划开始时间
+   * 变量名:start_time
+   * 是否必填:否
+   * 类型:string(32)
+   * 描述:
+   *  计划开始时间,遵循rfc3339标准格式,格式为YYYY-MM-DDTHH:mm:ss+TIMEZONE
+   *  示例值:2018-06-08T10:34:56+08:00
+   * 
+ */ + @SerializedName("start_time") + private String startTime; + + /** + *
+   * 字段名:计划结束时间
+   * 变量名:end_time
+   * 是否必填:否
+   * 类型:string(32)
+   * 描述:
+   *  计划结束时间,遵循rfc3339标准格式,格式为YYYY-MM-DDTHH:mm:ss+TIMEZONE
+   *  示例值:2019-06-08T10:34:56+08:00
+   * 
+ */ + @SerializedName("end_time") + private String endTime; +} \ No newline at end of file diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/subscriptionbilling/SubscriptionAmount.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/subscriptionbilling/SubscriptionAmount.java new file mode 100644 index 0000000000..c778a8ecb6 --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/subscriptionbilling/SubscriptionAmount.java @@ -0,0 +1,49 @@ +package com.github.binarywang.wxpay.bean.subscriptionbilling; + +import com.google.gson.annotations.SerializedName; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * 预约扣费金额信息 + *
+ *   文档地址:https://pay.weixin.qq.com/doc/v3/merchant/4012161105
+ * 
+ * + * @author Binary Wang + */ +@Data +@NoArgsConstructor +public class SubscriptionAmount implements Serializable { + private static final long serialVersionUID = 1L; + + /** + *
+   * 字段名:总金额
+   * 变量名:total
+   * 是否必填:是
+   * 类型:int
+   * 描述:
+   *  订单总金额,单位为分
+   *  示例值:100
+   * 
+ */ + @SerializedName("total") + private Integer total; + + /** + *
+   * 字段名:货币类型
+   * 变量名:currency
+   * 是否必填:否
+   * 类型:string(16)
+   * 描述:
+   *  CNY:人民币,境内商户号仅支持人民币
+   *  示例值:CNY
+   * 
+ */ + @SerializedName("currency") + private String currency; +} \ No newline at end of file diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/subscriptionbilling/SubscriptionCancelRequest.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/subscriptionbilling/SubscriptionCancelRequest.java new file mode 100644 index 0000000000..233d756f03 --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/subscriptionbilling/SubscriptionCancelRequest.java @@ -0,0 +1,49 @@ +package com.github.binarywang.wxpay.bean.subscriptionbilling; + +import com.google.gson.annotations.SerializedName; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * 取消预约扣费请求参数 + *
+ *   文档地址:https://pay.weixin.qq.com/doc/v3/merchant/4012161105
+ * 
+ * + * @author Binary Wang + */ +@Data +@NoArgsConstructor +public class SubscriptionCancelRequest implements Serializable { + private static final long serialVersionUID = 1L; + + /** + *
+   * 字段名:预约扣费ID
+   * 变量名:subscription_id
+   * 是否必填:是
+   * 类型:string(64)
+   * 描述:
+   *  微信支付预约扣费ID
+   *  示例值:1217752501201407033233368018
+   * 
+ */ + @SerializedName("subscription_id") + private String subscriptionId; + + /** + *
+   * 字段名:取消原因
+   * 变量名:cancel_reason
+   * 是否必填:否
+   * 类型:string(256)
+   * 描述:
+   *  取消原因描述
+   *  示例值:用户主动取消
+   * 
+ */ + @SerializedName("cancel_reason") + private String cancelReason; +} \ No newline at end of file diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/subscriptionbilling/SubscriptionCancelResult.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/subscriptionbilling/SubscriptionCancelResult.java new file mode 100644 index 0000000000..74ca22f130 --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/subscriptionbilling/SubscriptionCancelResult.java @@ -0,0 +1,77 @@ +package com.github.binarywang.wxpay.bean.subscriptionbilling; + +import com.google.gson.annotations.SerializedName; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * 取消预约扣费响应结果 + *
+ *   文档地址:https://pay.weixin.qq.com/doc/v3/merchant/4012161105
+ * 
+ * + * @author Binary Wang + */ +@Data +@NoArgsConstructor +public class SubscriptionCancelResult implements Serializable { + private static final long serialVersionUID = 1L; + + /** + *
+   * 字段名:预约扣费ID
+   * 变量名:subscription_id
+   * 是否必填:是
+   * 类型:string(64)
+   * 描述:
+   *  微信支付预约扣费ID
+   *  示例值:1217752501201407033233368018
+   * 
+ */ + @SerializedName("subscription_id") + private String subscriptionId; + + /** + *
+   * 字段名:预约状态
+   * 变量名:status
+   * 是否必填:是
+   * 类型:string(32)
+   * 描述:
+   *  预约状态,取消后应为CANCELLED
+   *  示例值:CANCELLED
+   * 
+ */ + @SerializedName("status") + private String status; + + /** + *
+   * 字段名:取消时间
+   * 变量名:cancel_time
+   * 是否必填:是
+   * 类型:string(32)
+   * 描述:
+   *  取消时间,遵循rfc3339标准格式
+   *  示例值:2018-06-08T10:34:56+08:00
+   * 
+ */ + @SerializedName("cancel_time") + private String cancelTime; + + /** + *
+   * 字段名:取消原因
+   * 变量名:cancel_reason
+   * 是否必填:否
+   * 类型:string(256)
+   * 描述:
+   *  取消原因描述
+   *  示例值:用户主动取消
+   * 
+ */ + @SerializedName("cancel_reason") + private String cancelReason; +} \ No newline at end of file diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/subscriptionbilling/SubscriptionInstantBillingRequest.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/subscriptionbilling/SubscriptionInstantBillingRequest.java new file mode 100644 index 0000000000..2b5a3dec37 --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/subscriptionbilling/SubscriptionInstantBillingRequest.java @@ -0,0 +1,104 @@ +package com.github.binarywang.wxpay.bean.subscriptionbilling; + +import com.google.gson.annotations.SerializedName; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * 立即扣费请求参数 + *
+ *   文档地址:https://pay.weixin.qq.com/doc/v3/merchant/4012161105
+ * 
+ * + * @author Binary Wang + */ +@Data +@NoArgsConstructor +public class SubscriptionInstantBillingRequest implements Serializable { + private static final long serialVersionUID = 1L; + + /** + *
+   * 字段名:商户订单号
+   * 变量名:out_trade_no
+   * 是否必填:是
+   * 类型:string(32)
+   * 描述:
+   *  商户系统内部订单号,只能是数字、大小写字母_-*且在同一个商户号下唯一
+   *  示例值:1217752501201407033233368018
+   * 
+ */ + @SerializedName("out_trade_no") + private String outTradeNo; + + /** + *
+   * 字段名:用户标识
+   * 变量名:openid
+   * 是否必填:是
+   * 类型:string(128)
+   * 描述:
+   *  用户在直连商户appid下的唯一标识
+   *  示例值:oUpF8uMuAJO_M2pxb1Q9zNjWeS6o
+   * 
+ */ + @SerializedName("openid") + private String openid; + + /** + *
+   * 字段名:订单描述
+   * 变量名:description
+   * 是否必填:是
+   * 类型:string(127)
+   * 描述:
+   *  订单描述
+   *  示例值:腾讯充值中心-QQ会员充值
+   * 
+ */ + @SerializedName("description") + private String description; + + /** + *
+   * 字段名:扣费金额
+   * 变量名:amount
+   * 是否必填:是
+   * 类型:object
+   * 描述:
+   *  扣费金额信息
+   * 
+ */ + @SerializedName("amount") + private SubscriptionAmount amount; + + /** + *
+   * 字段名:通知地址
+   * 变量名:notify_url
+   * 是否必填:否
+   * 类型:string(256)
+   * 描述:
+   *  异步接收微信支付结果通知的回调地址,通知url必须为外网可访问的url,不能携带参数
+   *  示例值:https://www.weixin.qq.com/wxpay/pay.php
+   * 
+ */ + @SerializedName("notify_url") + private String notifyUrl; + + /** + *
+   * 字段名:附加数据
+   * 变量名:attach
+   * 是否必填:否
+   * 类型:string(128)
+   * 描述:
+   *  附加数据,在查询API和支付通知中原样返回,可作为自定义参数使用
+   *  示例值:自定义数据
+   * 
+ */ + @SerializedName("attach") + private String attach; +} \ No newline at end of file diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/subscriptionbilling/SubscriptionInstantBillingResult.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/subscriptionbilling/SubscriptionInstantBillingResult.java new file mode 100644 index 0000000000..ac34307cd4 --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/subscriptionbilling/SubscriptionInstantBillingResult.java @@ -0,0 +1,111 @@ +package com.github.binarywang.wxpay.bean.subscriptionbilling; + +import com.google.gson.annotations.SerializedName; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * 立即扣费响应结果 + *
+ *   文档地址:https://pay.weixin.qq.com/doc/v3/merchant/4012161105
+ * 
+ * + * @author Binary Wang + */ +@Data +@NoArgsConstructor +public class SubscriptionInstantBillingResult implements Serializable { + private static final long serialVersionUID = 1L; + + /** + *
+   * 字段名:微信支付订单号
+   * 变量名:transaction_id
+   * 是否必填:是
+   * 类型:string(32)
+   * 描述:
+   *  微信支付系统生成的订单号
+   *  示例值:1217752501201407033233368018
+   * 
+ */ + @SerializedName("transaction_id") + private String transactionId; + + /** + *
+   * 字段名:商户订单号
+   * 变量名:out_trade_no
+   * 是否必填:是
+   * 类型:string(32)
+   * 描述:
+   *  商户系统内部订单号
+   *  示例值:1217752501201407033233368018
+   * 
+ */ + @SerializedName("out_trade_no") + private String outTradeNo; + + /** + *
+   * 字段名:交易状态
+   * 变量名:trade_state
+   * 是否必填:是
+   * 类型:string(32)
+   * 描述:
+   *  交易状态
+   *  SUCCESS:支付成功
+   *  REFUND:转入退款
+   *  NOTPAY:未支付
+   *  CLOSED:已关闭
+   *  REVOKED:已撤销(刷卡支付)
+   *  USERPAYING:用户支付中
+   *  PAYERROR:支付失败
+   *  示例值:SUCCESS
+   * 
+ */ + @SerializedName("trade_state") + private String tradeState; + + /** + *
+   * 字段名:交易状态描述
+   * 变量名:trade_state_desc
+   * 是否必填:是
+   * 类型:string(256)
+   * 描述:
+   *  交易状态描述
+   *  示例值:支付成功
+   * 
+ */ + @SerializedName("trade_state_desc") + private String tradeStateDesc; + + /** + *
+   * 字段名:支付完成时间
+   * 变量名:success_time
+   * 是否必填:否
+   * 类型:string(32)
+   * 描述:
+   *  支付完成时间,遵循rfc3339标准格式
+   *  示例值:2018-06-08T10:34:56+08:00
+   * 
+ */ + @SerializedName("success_time") + private String successTime; + + /** + *
+   * 字段名:扣费金额
+   * 变量名:amount
+   * 是否必填:是
+   * 类型:object
+   * 描述:
+   *  扣费金额信息
+   * 
+ */ + @SerializedName("amount") + private SubscriptionAmount amount; +} \ No newline at end of file diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/subscriptionbilling/SubscriptionQueryResult.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/subscriptionbilling/SubscriptionQueryResult.java new file mode 100644 index 0000000000..17e2c7dc19 --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/subscriptionbilling/SubscriptionQueryResult.java @@ -0,0 +1,177 @@ +package com.github.binarywang.wxpay.bean.subscriptionbilling; + +import com.google.gson.annotations.SerializedName; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * 预约扣费查询结果 + *
+ *   文档地址:https://pay.weixin.qq.com/doc/v3/merchant/4012161105
+ * 
+ * + * @author Binary Wang + */ +@Data +@NoArgsConstructor +public class SubscriptionQueryResult implements Serializable { + private static final long serialVersionUID = 1L; + + /** + *
+   * 字段名:预约扣费ID
+   * 变量名:subscription_id
+   * 是否必填:是
+   * 类型:string(64)
+   * 描述:
+   *  微信支付预约扣费ID
+   *  示例值:1217752501201407033233368018
+   * 
+ */ + @SerializedName("subscription_id") + private String subscriptionId; + + /** + *
+   * 字段名:商户订单号
+   * 变量名:out_trade_no
+   * 是否必填:是
+   * 类型:string(32)
+   * 描述:
+   *  商户系统内部订单号
+   *  示例值:1217752501201407033233368018
+   * 
+ */ + @SerializedName("out_trade_no") + private String outTradeNo; + + /** + *
+   * 字段名:用户标识
+   * 变量名:openid
+   * 是否必填:是
+   * 类型:string(128)
+   * 描述:
+   *  用户在直连商户appid下的唯一标识
+   *  示例值:oUpF8uMuAJO_M2pxb1Q9zNjWeS6o
+   * 
+ */ + @SerializedName("openid") + private String openid; + + /** + *
+   * 字段名:订单描述
+   * 变量名:description
+   * 是否必填:是
+   * 类型:string(127)
+   * 描述:
+   *  订单描述
+   *  示例值:腾讯充值中心-QQ会员充值
+   * 
+ */ + @SerializedName("description") + private String description; + + /** + *
+   * 字段名:预约状态
+   * 变量名:status
+   * 是否必填:是
+   * 类型:string(32)
+   * 描述:
+   *  预约状态
+   *  SCHEDULED:已预约
+   *  CANCELLED:已取消
+   *  EXECUTED:已执行
+   *  FAILED:执行失败
+   *  示例值:SCHEDULED
+   * 
+ */ + @SerializedName("status") + private String status; + + /** + *
+   * 字段名:预约扣费时间
+   * 变量名:schedule_time
+   * 是否必填:是
+   * 类型:string(32)
+   * 描述:
+   *  预约扣费的时间,遵循rfc3339标准格式
+   *  示例值:2018-06-08T10:34:56+08:00
+   * 
+ */ + @SerializedName("schedule_time") + private String scheduleTime; + + /** + *
+   * 字段名:创建时间
+   * 变量名:create_time
+   * 是否必填:是
+   * 类型:string(32)
+   * 描述:
+   *  预约创建时间,遵循rfc3339标准格式
+   *  示例值:2018-06-08T10:34:56+08:00
+   * 
+ */ + @SerializedName("create_time") + private String createTime; + + /** + *
+   * 字段名:更新时间
+   * 变量名:update_time
+   * 是否必填:否
+   * 类型:string(32)
+   * 描述:
+   *  预约更新时间,遵循rfc3339标准格式
+   *  示例值:2018-06-08T10:34:56+08:00
+   * 
+ */ + @SerializedName("update_time") + private String updateTime; + + /** + *
+   * 字段名:预约扣费金额
+   * 变量名:amount
+   * 是否必填:是
+   * 类型:object
+   * 描述:
+   *  预约扣费金额信息
+   * 
+ */ + @SerializedName("amount") + private SubscriptionAmount amount; + + /** + *
+   * 字段名:扣费计划
+   * 变量名:billing_plan
+   * 是否必填:否
+   * 类型:object
+   * 描述:
+   *  扣费计划信息
+   * 
+ */ + @SerializedName("billing_plan") + private BillingPlan billingPlan; + + /** + *
+   * 字段名:附加数据
+   * 变量名:attach
+   * 是否必填:否
+   * 类型:string(128)
+   * 描述:
+   *  附加数据
+   *  示例值:自定义数据
+   * 
+ */ + @SerializedName("attach") + private String attach; +} \ No newline at end of file diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/subscriptionbilling/SubscriptionScheduleRequest.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/subscriptionbilling/SubscriptionScheduleRequest.java new file mode 100644 index 0000000000..51cf5aebd1 --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/subscriptionbilling/SubscriptionScheduleRequest.java @@ -0,0 +1,131 @@ +package com.github.binarywang.wxpay.bean.subscriptionbilling; + +import com.google.gson.annotations.SerializedName; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * 预约扣费请求参数 + *
+ *   文档地址:https://pay.weixin.qq.com/doc/v3/merchant/4012161105
+ * 
+ * + * @author Binary Wang + */ +@Data +@NoArgsConstructor +public class SubscriptionScheduleRequest implements Serializable { + private static final long serialVersionUID = 1L; + + /** + *
+   * 字段名:商户订单号
+   * 变量名:out_trade_no
+   * 是否必填:是
+   * 类型:string(32)
+   * 描述:
+   *  商户系统内部订单号,只能是数字、大小写字母_-*且在同一个商户号下唯一
+   *  示例值:1217752501201407033233368018
+   * 
+ */ + @SerializedName("out_trade_no") + private String outTradeNo; + + /** + *
+   * 字段名:用户标识
+   * 变量名:openid
+   * 是否必填:是
+   * 类型:string(128)
+   * 描述:
+   *  用户在直连商户appid下的唯一标识
+   *  示例值:oUpF8uMuAJO_M2pxb1Q9zNjWeS6o
+   * 
+ */ + @SerializedName("openid") + private String openid; + + /** + *
+   * 字段名:订单描述
+   * 变量名:description
+   * 是否必填:是
+   * 类型:string(127)
+   * 描述:
+   *  订单描述
+   *  示例值:腾讯充值中心-QQ会员充值
+   * 
+ */ + @SerializedName("description") + private String description; + + /** + *
+   * 字段名:预约扣费金额
+   * 变量名:amount
+   * 是否必填:是
+   * 类型:object
+   * 描述:
+   *  预约扣费金额信息
+   * 
+ */ + @SerializedName("amount") + private SubscriptionAmount amount; + + /** + *
+   * 字段名:预约扣费时间
+   * 变量名:schedule_time
+   * 是否必填:是
+   * 类型:string(32)
+   * 描述:
+   *  预约扣费的时间,遵循rfc3339标准格式,格式为YYYY-MM-DDTHH:mm:ss+TIMEZONE
+   *  示例值:2018-06-08T10:34:56+08:00
+   * 
+ */ + @SerializedName("schedule_time") + private String scheduleTime; + + /** + *
+   * 字段名:扣费计划
+   * 变量名:billing_plan
+   * 是否必填:否
+   * 类型:object
+   * 描述:
+   *  扣费计划信息,用于连续包月等场景
+   * 
+ */ + @SerializedName("billing_plan") + private BillingPlan billingPlan; + + /** + *
+   * 字段名:通知地址
+   * 变量名:notify_url
+   * 是否必填:否
+   * 类型:string(256)
+   * 描述:
+   *  异步接收微信支付结果通知的回调地址,通知url必须为外网可访问的url,不能携带参数
+   *  示例值:https://www.weixin.qq.com/wxpay/pay.php
+   * 
+ */ + @SerializedName("notify_url") + private String notifyUrl; + + /** + *
+   * 字段名:附加数据
+   * 变量名:attach
+   * 是否必填:否
+   * 类型:string(128)
+   * 描述:
+   *  附加数据,在查询API和支付通知中原样返回,可作为自定义参数使用
+   *  示例值:自定义数据
+   * 
+ */ + @SerializedName("attach") + private String attach; +} \ No newline at end of file diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/subscriptionbilling/SubscriptionScheduleResult.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/subscriptionbilling/SubscriptionScheduleResult.java new file mode 100644 index 0000000000..fc0f9f6615 --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/subscriptionbilling/SubscriptionScheduleResult.java @@ -0,0 +1,121 @@ +package com.github.binarywang.wxpay.bean.subscriptionbilling; + +import com.google.gson.annotations.SerializedName; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * 预约扣费响应结果 + *
+ *   文档地址:https://pay.weixin.qq.com/doc/v3/merchant/4012161105
+ * 
+ * + * @author Binary Wang + */ +@Data +@NoArgsConstructor +public class SubscriptionScheduleResult implements Serializable { + private static final long serialVersionUID = 1L; + + /** + *
+   * 字段名:预约扣费ID
+   * 变量名:subscription_id
+   * 是否必填:是
+   * 类型:string(64)
+   * 描述:
+   *  微信支付预约扣费ID
+   *  示例值:1217752501201407033233368018
+   * 
+ */ + @SerializedName("subscription_id") + private String subscriptionId; + + /** + *
+   * 字段名:商户订单号
+   * 变量名:out_trade_no
+   * 是否必填:是
+   * 类型:string(32)
+   * 描述:
+   *  商户系统内部订单号
+   *  示例值:1217752501201407033233368018
+   * 
+ */ + @SerializedName("out_trade_no") + private String outTradeNo; + + /** + *
+   * 字段名:预约状态
+   * 变量名:status
+   * 是否必填:是
+   * 类型:string(32)
+   * 描述:
+   *  预约状态
+   *  SCHEDULED:已预约
+   *  CANCELLED:已取消
+   *  EXECUTED:已执行
+   *  FAILED:执行失败
+   *  示例值:SCHEDULED
+   * 
+ */ + @SerializedName("status") + private String status; + + /** + *
+   * 字段名:预约扣费时间
+   * 变量名:schedule_time
+   * 是否必填:是
+   * 类型:string(32)
+   * 描述:
+   *  预约扣费的时间,遵循rfc3339标准格式
+   *  示例值:2018-06-08T10:34:56+08:00
+   * 
+ */ + @SerializedName("schedule_time") + private String scheduleTime; + + /** + *
+   * 字段名:创建时间
+   * 变量名:create_time
+   * 是否必填:是
+   * 类型:string(32)
+   * 描述:
+   *  预约创建时间,遵循rfc3339标准格式
+   *  示例值:2018-06-08T10:34:56+08:00
+   * 
+ */ + @SerializedName("create_time") + private String createTime; + + /** + *
+   * 字段名:预约扣费金额
+   * 变量名:amount
+   * 是否必填:是
+   * 类型:object
+   * 描述:
+   *  预约扣费金额信息
+   * 
+ */ + @SerializedName("amount") + private SubscriptionAmount amount; + + /** + *
+   * 字段名:扣费计划
+   * 变量名:billing_plan
+   * 是否必填:否
+   * 类型:object
+   * 描述:
+   *  扣费计划信息
+   * 
+ */ + @SerializedName("billing_plan") + private BillingPlan billingPlan; +} \ No newline at end of file diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/subscriptionbilling/SubscriptionTransactionQueryRequest.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/subscriptionbilling/SubscriptionTransactionQueryRequest.java new file mode 100644 index 0000000000..17b3681cea --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/subscriptionbilling/SubscriptionTransactionQueryRequest.java @@ -0,0 +1,91 @@ +package com.github.binarywang.wxpay.bean.subscriptionbilling; + +import com.google.gson.annotations.SerializedName; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * 查询扣费记录请求参数 + *
+ *   文档地址:https://pay.weixin.qq.com/doc/v3/merchant/4012161105
+ * 
+ * + * @author Binary Wang + */ +@Data +@NoArgsConstructor +public class SubscriptionTransactionQueryRequest implements Serializable { + private static final long serialVersionUID = 1L; + + /** + *
+   * 字段名:用户标识
+   * 变量名:openid
+   * 是否必填:否
+   * 类型:string(128)
+   * 描述:
+   *  用户在直连商户appid下的唯一标识
+   *  示例值:oUpF8uMuAJO_M2pxb1Q9zNjWeS6o
+   * 
+ */ + @SerializedName("openid") + private String openid; + + /** + *
+   * 字段名:开始时间
+   * 变量名:begin_time
+   * 是否必填:否
+   * 类型:string(32)
+   * 描述:
+   *  查询开始时间,遵循rfc3339标准格式,格式为YYYY-MM-DDTHH:mm:ss+TIMEZONE
+   *  示例值:2018-06-08T10:34:56+08:00
+   * 
+ */ + @SerializedName("begin_time") + private String beginTime; + + /** + *
+   * 字段名:结束时间
+   * 变量名:end_time
+   * 是否必填:否
+   * 类型:string(32)
+   * 描述:
+   *  查询结束时间,遵循rfc3339标准格式,格式为YYYY-MM-DDTHH:mm:ss+TIMEZONE
+   *  示例值:2018-06-08T10:34:56+08:00
+   * 
+ */ + @SerializedName("end_time") + private String endTime; + + /** + *
+   * 字段名:分页大小
+   * 变量名:limit
+   * 是否必填:否
+   * 类型:int
+   * 描述:
+   *  分页大小,不超过50
+   *  示例值:20
+   * 
+ */ + @SerializedName("limit") + private Integer limit; + + /** + *
+   * 字段名:分页偏移量
+   * 变量名:offset
+   * 是否必填:否
+   * 类型:int
+   * 描述:
+   *  分页偏移量
+   *  示例值:0
+   * 
+ */ + @SerializedName("offset") + private Integer offset; +} \ No newline at end of file diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/subscriptionbilling/SubscriptionTransactionQueryResult.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/subscriptionbilling/SubscriptionTransactionQueryResult.java new file mode 100644 index 0000000000..75fff11a22 --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/subscriptionbilling/SubscriptionTransactionQueryResult.java @@ -0,0 +1,190 @@ +package com.github.binarywang.wxpay.bean.subscriptionbilling; + +import com.google.gson.annotations.SerializedName; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; +import java.util.List; + +/** + * 查询扣费记录响应结果 + *
+ *   文档地址:https://pay.weixin.qq.com/doc/v3/merchant/4012161105
+ * 
+ * + * @author Binary Wang + */ +@Data +@NoArgsConstructor +public class SubscriptionTransactionQueryResult implements Serializable { + private static final long serialVersionUID = 1L; + + /** + *
+   * 字段名:总数量
+   * 变量名:total_count
+   * 是否必填:是
+   * 类型:int
+   * 描述:
+   *  符合条件的记录总数量
+   *  示例值:100
+   * 
+ */ + @SerializedName("total_count") + private Integer totalCount; + + /** + *
+   * 字段名:扣费记录列表
+   * 变量名:data
+   * 是否必填:是
+   * 类型:array
+   * 描述:
+   *  扣费记录列表
+   * 
+ */ + @SerializedName("data") + private List data; + + /** + * 扣费记录 + */ + @Data + @NoArgsConstructor + public static class SubscriptionTransaction implements Serializable { + private static final long serialVersionUID = 1L; + + /** + *
+     * 字段名:微信支付订单号
+     * 变量名:transaction_id
+     * 是否必填:是
+     * 类型:string(32)
+     * 描述:
+     *  微信支付系统生成的订单号
+     *  示例值:1217752501201407033233368018
+     * 
+ */ + @SerializedName("transaction_id") + private String transactionId; + + /** + *
+     * 字段名:商户订单号
+     * 变量名:out_trade_no
+     * 是否必填:是
+     * 类型:string(32)
+     * 描述:
+     *  商户系统内部订单号
+     *  示例值:1217752501201407033233368018
+     * 
+ */ + @SerializedName("out_trade_no") + private String outTradeNo; + + /** + *
+     * 字段名:预约扣费ID
+     * 变量名:subscription_id
+     * 是否必填:否
+     * 类型:string(64)
+     * 描述:
+     *  微信支付预约扣费ID,预约扣费产生的交易才有此字段
+     *  示例值:1217752501201407033233368018
+     * 
+ */ + @SerializedName("subscription_id") + private String subscriptionId; + + /** + *
+     * 字段名:交易状态
+     * 变量名:trade_state
+     * 是否必填:是
+     * 类型:string(32)
+     * 描述:
+     *  交易状态
+     *  SUCCESS:支付成功
+     *  REFUND:转入退款
+     *  NOTPAY:未支付
+     *  CLOSED:已关闭
+     *  REVOKED:已撤销(刷卡支付)
+     *  USERPAYING:用户支付中
+     *  PAYERROR:支付失败
+     *  示例值:SUCCESS
+     * 
+ */ + @SerializedName("trade_state") + private String tradeState; + + /** + *
+     * 字段名:支付完成时间
+     * 变量名:success_time
+     * 是否必填:否
+     * 类型:string(32)
+     * 描述:
+     *  支付完成时间,遵循rfc3339标准格式
+     *  示例值:2018-06-08T10:34:56+08:00
+     * 
+ */ + @SerializedName("success_time") + private String successTime; + + /** + *
+     * 字段名:扣费金额
+     * 变量名:amount
+     * 是否必填:是
+     * 类型:object
+     * 描述:
+     *  扣费金额信息
+     * 
+ */ + @SerializedName("amount") + private SubscriptionAmount amount; + + /** + *
+     * 字段名:用户标识
+     * 变量名:openid
+     * 是否必填:是
+     * 类型:string(128)
+     * 描述:
+     *  用户在直连商户appid下的唯一标识
+     *  示例值:oUpF8uMuAJO_M2pxb1Q9zNjWeS6o
+     * 
+ */ + @SerializedName("openid") + private String openid; + + /** + *
+     * 字段名:订单描述
+     * 变量名:description
+     * 是否必填:是
+     * 类型:string(127)
+     * 描述:
+     *  订单描述
+     *  示例值:腾讯充值中心-QQ会员充值
+     * 
+ */ + @SerializedName("description") + private String description; + + /** + *
+     * 字段名:附加数据
+     * 变量名:attach
+     * 是否必填:否
+     * 类型:string(128)
+     * 描述:
+     *  附加数据
+     *  示例值:自定义数据
+     * 
+ */ + @SerializedName("attach") + private String attach; + } +} \ No newline at end of file diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/transfer/BusinessOperationTransferQueryRequest.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/transfer/BusinessOperationTransferQueryRequest.java new file mode 100644 index 0000000000..f1323655a1 --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/transfer/BusinessOperationTransferQueryRequest.java @@ -0,0 +1,44 @@ +package com.github.binarywang.wxpay.bean.transfer; + +import com.google.gson.annotations.SerializedName; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * 运营工具-商家转账查询请求参数 + * + * @author WxJava Team + * @see 运营工具-商家转账API + */ +@Data +@Builder(builderMethodName = "newBuilder") +@NoArgsConstructor +@AllArgsConstructor +public class BusinessOperationTransferQueryRequest implements Serializable { + private static final long serialVersionUID = 1L; + + /** + * 商户系统内部的商家单号 + * 与transfer_bill_no二选一 + */ + @SerializedName("out_bill_no") + private String outBillNo; + + /** + * 微信转账单号 + * 与out_bill_no二选一 + */ + @SerializedName("transfer_bill_no") + private String transferBillNo; + + /** + * 直连商户的appid + * 可选 + */ + @SerializedName("appid") + private String appid; +} \ No newline at end of file diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/transfer/BusinessOperationTransferQueryResult.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/transfer/BusinessOperationTransferQueryResult.java new file mode 100644 index 0000000000..0cfd8f8570 --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/transfer/BusinessOperationTransferQueryResult.java @@ -0,0 +1,101 @@ +package com.github.binarywang.wxpay.bean.transfer; + +import com.google.gson.annotations.SerializedName; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * 运营工具-商家转账查询结果 + * + * @author WxJava Team + * @see 运营工具-商家转账API + */ +@Data +@NoArgsConstructor +public class BusinessOperationTransferQueryResult implements Serializable { + private static final long serialVersionUID = 1L; + + /** + * 直连商户的appid + */ + @SerializedName("appid") + private String appid; + + /** + * 商户系统内部的商家单号 + */ + @SerializedName("out_bill_no") + private String outBillNo; + + /** + * 微信转账单号 + */ + @SerializedName("transfer_bill_no") + private String transferBillNo; + + /** + * 运营工具转账场景ID + */ + @SerializedName("operation_scene_id") + private String operationSceneId; + + /** + * 用户在直连商户应用下的用户标示 + */ + @SerializedName("openid") + private String openid; + + /** + * 收款用户姓名 + * 已脱敏 + */ + @SerializedName("user_name") + private String userName; + + /** + * 转账金额 + * 单位为"分" + */ + @SerializedName("transfer_amount") + private Integer transferAmount; + + /** + * 转账备注 + */ + @SerializedName("transfer_remark") + private String transferRemark; + + /** + * 转账状态 + * WAIT_PAY:等待确认 + * PROCESSING:转账中 + * SUCCESS:转账成功 + * FAIL:转账失败 + * REFUND:已退款 + */ + @SerializedName("transfer_state") + private String transferState; + + /** + * 发起转账的时间 + * 遵循rfc3339标准格式,格式为yyyy-MM-DDTHH:mm:ss+TIMEZONE + */ + @SerializedName("create_time") + private String createTime; + + /** + * 转账更新时间 + * 遵循rfc3339标准格式,格式为yyyy-MM-DDTHH:mm:ss+TIMEZONE + */ + @SerializedName("update_time") + private String updateTime; + + /** + * 失败原因 + * 当transfer_state为FAIL时返回 + */ + @SerializedName("fail_reason") + private String failReason; +} \ No newline at end of file diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/transfer/BusinessOperationTransferRequest.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/transfer/BusinessOperationTransferRequest.java new file mode 100644 index 0000000000..91d9438833 --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/transfer/BusinessOperationTransferRequest.java @@ -0,0 +1,89 @@ +package com.github.binarywang.wxpay.bean.transfer; + +import com.github.binarywang.wxpay.v3.SpecEncrypt; +import com.google.gson.annotations.SerializedName; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * 运营工具-商家转账请求参数 + * + * @author WxJava Team + * @see 运营工具-商家转账API + */ +@Data +@Builder(builderMethodName = "newBuilder") +@NoArgsConstructor +@AllArgsConstructor +public class BusinessOperationTransferRequest implements Serializable { + private static final long serialVersionUID = 1L; + + /** + * 直连商户的appid + * 必须 + */ + @SerializedName("appid") + private String appid; + + /** + * 商户系统内部的商家单号 + * 必须,要求此参数只能由数字、大小写字母组成,在商户系统内部唯一 + */ + @SerializedName("out_bill_no") + private String outBillNo; + + /** + * 运营工具转账场景ID + * 必须,用于标识运营工具转账的具体业务场景 + */ + @SerializedName("operation_scene_id") + private String operationSceneId; + + /** + * 用户在直连商户应用下的用户标示 + * 必须 + */ + @SerializedName("openid") + private String openid; + + /** + * 收款用户姓名 + * 可选,传入则校验收款用户姓名 + * 使用RSA加密,使用OAEP填充方式 + */ + @SpecEncrypt + @SerializedName("user_name") + private String userName; + + /** + * 转账金额 + * 必须,单位为"分" + */ + @SerializedName("transfer_amount") + private Integer transferAmount; + + /** + * 转账备注 + * 必须,会在转账成功消息和转账详情页向用户展示 + */ + @SerializedName("transfer_remark") + private String transferRemark; + + /** + * 用户收款感知 + * 可选,用于在转账成功消息中向用户展示特定内容 + */ + @SerializedName("user_recv_perception") + private String userRecvPerception; + + /** + * 异步接收微信支付转账结果通知的回调地址 + * 可选,通知URL必须为外网可以正常访问的地址,不能携带查询参数 + */ + @SerializedName("notify_url") + private String notifyUrl; +} \ No newline at end of file diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/transfer/BusinessOperationTransferResult.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/transfer/BusinessOperationTransferResult.java new file mode 100644 index 0000000000..a380d6133e --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/transfer/BusinessOperationTransferResult.java @@ -0,0 +1,64 @@ +package com.github.binarywang.wxpay.bean.transfer; + +import com.google.gson.annotations.SerializedName; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * 运营工具-商家转账结果 + * + * @author WxJava Team + * @see 运营工具-商家转账API + */ +@Data +@NoArgsConstructor +public class BusinessOperationTransferResult implements Serializable { + private static final long serialVersionUID = 1L; + + /** + * 商户系统内部的商家单号 + */ + @SerializedName("out_bill_no") + private String outBillNo; + + /** + * 微信转账单号 + * 微信商家转账系统返回的唯一标识 + */ + @SerializedName("transfer_bill_no") + private String transferBillNo; + + /** + * 转账状态 + * WAIT_PAY:等待确认 + * PROCESSING:转账中 + * SUCCESS:转账成功 + * FAIL:转账失败 + * REFUND:已退款 + */ + @SerializedName("transfer_state") + private String transferState; + + /** + * 发起转账的时间 + * 遵循rfc3339标准格式,格式为yyyy-MM-DDTHH:mm:ss+TIMEZONE + */ + @SerializedName("create_time") + private String createTime; + + /** + * 转账更新时间 + * 遵循rfc3339标准格式,格式为yyyy-MM-DDTHH:mm:ss+TIMEZONE + */ + @SerializedName("update_time") + private String updateTime; + + /** + * 失败原因 + * 当transfer_state为FAIL时返回 + */ + @SerializedName("fail_reason") + private String failReason; +} \ No newline at end of file diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/transfer/ReservationTransferBatchGetResult.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/transfer/ReservationTransferBatchGetResult.java new file mode 100644 index 0000000000..d32db8c7c2 --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/transfer/ReservationTransferBatchGetResult.java @@ -0,0 +1,170 @@ +package com.github.binarywang.wxpay.bean.transfer; + +import com.google.gson.annotations.SerializedName; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; +import java.util.List; + +/** + *
+ * 预约转账批次单号查询接口响应结果
+ * 通过预约批次单号查询批量预约商家转账批次单基本信息。
+ * 文档地址:https://pay.weixin.qq.com/doc/v3/merchant/4015901167
+ * 
+ * + * @author wanggang + * created on 2025/11/28 + */ +@Data +@NoArgsConstructor +public class ReservationTransferBatchGetResult implements Serializable { + private static final long serialVersionUID = 1L; + + /** + * 【商户号】 微信支付分配的商户号 + */ + @SerializedName("mch_id") + private String mchId; + + /** + * 【商户预约批次单号】 商户系统内部的商家预约批次单号 + */ + @SerializedName("out_batch_no") + private String outBatchNo; + + /** + * 【微信预约批次单号】 微信预约批次单号,微信商家转账系统返回的唯一标识 + */ + @SerializedName("reservation_batch_no") + private String reservationBatchNo; + + /** + * 【商户AppID】 商户在微信申请公众号或移动应用成功后分配的账号ID + */ + @SerializedName("appid") + private String appid; + + /** + * 【批次备注】 批次备注 + */ + @SerializedName("batch_remark") + private String batchRemark; + + /** + * 【转账场景ID】 商户在商户平台-产品中心-商家转账中申请的转账场景ID + */ + @SerializedName("transfer_scene_id") + private String transferSceneId; + + /** + * 【批次状态】 + * ACCEPTED: 批次已受理 + * PROCESSING: 批次处理中 + * FINISHED: 批次处理完成 + * CLOSED: 批次已关闭 + */ + @SerializedName("batch_state") + private String batchState; + + /** + * 【转账总金额】 转账金额单位为"分" + */ + @SerializedName("total_amount") + private Integer totalAmount; + + /** + * 【转账总笔数】 转账总笔数 + */ + @SerializedName("total_num") + private Integer totalNum; + + /** + * 【转账成功金额】 转账成功金额单位为"分" + */ + @SerializedName("success_amount") + private Integer successAmount; + + /** + * 【转账成功笔数】 转账成功笔数 + */ + @SerializedName("success_num") + private Integer successNum; + + /** + * 【转账失败金额】 转账失败金额单位为"分" + */ + @SerializedName("fail_amount") + private Integer failAmount; + + /** + * 【转账失败笔数】 转账失败笔数 + */ + @SerializedName("fail_num") + private Integer failNum; + + /** + * 【批次创建时间】 批次受理成功时返回 + * 遵循rfc3339标准格式,格式为yyyy-MM-DDTHH:mm:ss+TIMEZONE + */ + @SerializedName("create_time") + private String createTime; + + /** + * 【批次更新时间】 批次最后更新时间 + * 遵循rfc3339标准格式,格式为yyyy-MM-DDTHH:mm:ss+TIMEZONE + */ + @SerializedName("update_time") + private String updateTime; + + /** + * 【批次关闭原因】 批次关闭原因 + * MERCHANT_REVOCATION: 商户主动撤销 + * OVERDUE_CLOSE: 系统超时关闭 + */ + @SerializedName("close_reason") + private String closeReason; + + /** + * 【是否需要查询明细】 + */ + @SerializedName("need_query_detail") + private Boolean needQueryDetail; + + /** + * 【转账明细列表】 + */ + @SerializedName("transfer_detail_list") + private List transferDetailList; + + /** + * 转账明细 + */ + @Data + @NoArgsConstructor + public static class TransferDetail implements Serializable { + private static final long serialVersionUID = 1L; + + /** + * 【商户明细单号】 商户系统内部区分转账批次单下不同转账明细单的唯一标识 + */ + @SerializedName("out_detail_no") + private String outDetailNo; + + /** + * 【微信转账单号】 微信转账单号,微信商家转账系统返回的唯一标识 + */ + @SerializedName("transfer_bill_no") + private String transferBillNo; + + /** + * 【明细状态】 + * PROCESSING: 转账处理中 + * SUCCESS: 转账成功 + * FAIL: 转账失败 + */ + @SerializedName("detail_state") + private String detailState; + } +} diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/transfer/ReservationTransferBatchRequest.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/transfer/ReservationTransferBatchRequest.java new file mode 100644 index 0000000000..82e3d6f328 --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/transfer/ReservationTransferBatchRequest.java @@ -0,0 +1,148 @@ +package com.github.binarywang.wxpay.bean.transfer; + +import com.github.binarywang.wxpay.v3.SpecEncrypt; +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; + +/** + *
+ * 批量预约商家转账请求参数
+ * 商户可以通过批量预约接口一次发起批量转账请求,最多可以同时向50个用户发起转账。
+ * 文档地址:https://pay.weixin.qq.com/doc/v3/merchant/4015901167
+ * 
+ * + * @author wanggang + * created on 2025/11/28 + */ +@Data +@Builder(builderMethodName = "newBuilder") +@NoArgsConstructor +@AllArgsConstructor +public class ReservationTransferBatchRequest implements Serializable { + private static final long serialVersionUID = 1L; + + /** + * 【商户AppID】 商户在微信申请公众号或移动应用成功后分配的账号ID + */ + @SerializedName("appid") + private String appid; + + /** + * 【商户预约批次单号】 商户系统内部的商家预约批次单号,要求此参数只能由数字、大小写字母组成,在商户系统内部唯一 + */ + @SerializedName("out_batch_no") + private String outBatchNo; + + /** + * 【转账场景ID】 商户在商户平台-产品中心-商家转账中申请的转账场景ID + */ + @SerializedName("transfer_scene_id") + private String transferSceneId; + + /** + * 【批次备注】 批次备注 + */ + @SerializedName("batch_remark") + private String batchRemark; + + /** + * 【转账总金额】 转账金额单位为"分",转账总金额必须与批次内所有转账明细金额之和保持一致,否则无法发起转账操作 + */ + @SerializedName("total_amount") + private Integer totalAmount; + + /** + * 【转账总笔数】 转账总笔数,需要与批次内所有转账明细笔数保持一致,否则无法发起转账操作 + */ + @SerializedName("total_num") + private Integer totalNum; + + /** + * 【转账明细列表】 转账明细列表,最多50条 + */ + @SerializedName("transfer_detail_list") + private List transferDetailList; + + /** + * 【异步回调地址】 异步接收微信支付结果通知的回调地址,通知url必须为公网可访问的url,必须为https,不能携带参数 + */ + @SerializedName("notify_url") + private String notifyUrl; + + /** + * 转账明细 + */ + @Data + @Builder(builderMethodName = "newBuilder") + @NoArgsConstructor + @AllArgsConstructor + public static class TransferDetail implements Serializable { + private static final long serialVersionUID = 1L; + + /** + * 【商户明细单号】 商户系统内部区分转账批次单下不同转账明细单的唯一标识,要求此参数只能由数字、大小写字母组成 + */ + @SerializedName("out_detail_no") + private String outDetailNo; + + /** + * 【转账金额】 转账金额单位为"分" + */ + @SerializedName("transfer_amount") + private Integer transferAmount; + + /** + * 【转账备注】 单条转账备注(微信用户会收到该备注),UTF8编码,最多允许32个字符 + */ + @SerializedName("transfer_remark") + private String transferRemark; + + /** + * 【收款用户OpenID】 商户AppID下,某用户的OpenID + */ + @SerializedName("openid") + private String openid; + + /** + * 【收款用户姓名】 收款方真实姓名。支持标准RSA算法和国密算法,公钥由微信侧提供 + */ + @SpecEncrypt + @SerializedName("user_name") + private String userName; + + /** + * 【转账场景报备信息】 + */ + @SerializedName("transfer_scene_report_infos") + private List transferSceneReportInfos; + } + + /** + * 转账场景报备信息 + */ + @Data + @Builder(builderMethodName = "newBuilder") + @NoArgsConstructor + @AllArgsConstructor + public static class TransferSceneReportInfo implements Serializable { + private static final long serialVersionUID = 1L; + + /** + * 【信息类型】 信息类型编码 + */ + @SerializedName("info_type") + private String infoType; + + /** + * 【信息内容】 信息内容 + */ + @SerializedName("info_content") + private String infoContent; + } +} diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/transfer/ReservationTransferBatchResult.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/transfer/ReservationTransferBatchResult.java new file mode 100644 index 0000000000..ef762cee5b --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/transfer/ReservationTransferBatchResult.java @@ -0,0 +1,51 @@ +package com.github.binarywang.wxpay.bean.transfer; + +import com.google.gson.annotations.SerializedName; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + *
+ * 批量预约商家转账响应结果
+ * 文档地址:https://pay.weixin.qq.com/doc/v3/merchant/4015901167
+ * 
+ * + * @author wanggang + * created on 2025/11/28 + */ +@Data +@NoArgsConstructor +public class ReservationTransferBatchResult implements Serializable { + private static final long serialVersionUID = 1L; + + /** + * 【商户预约批次单号】 商户系统内部的商家预约批次单号 + */ + @SerializedName("out_batch_no") + private String outBatchNo; + + /** + * 【微信预约批次单号】 微信预约批次单号,微信商家转账系统返回的唯一标识 + */ + @SerializedName("reservation_batch_no") + private String reservationBatchNo; + + /** + * 【批次创建时间】 批次受理成功时返回 + * 遵循rfc3339标准格式,格式为yyyy-MM-DDTHH:mm:ss+TIMEZONE + */ + @SerializedName("create_time") + private String createTime; + + /** + * 【批次状态】 + * ACCEPTED: 批次已受理 + * PROCESSING: 批次处理中 + * FINISHED: 批次处理完成 + * CLOSED: 批次已关闭 + */ + @SerializedName("batch_state") + private String batchState; +} diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/transfer/ReservationTransferNotifyResult.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/transfer/ReservationTransferNotifyResult.java new file mode 100644 index 0000000000..438354e7bd --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/transfer/ReservationTransferNotifyResult.java @@ -0,0 +1,173 @@ +package com.github.binarywang.wxpay.bean.transfer; + +import com.github.binarywang.wxpay.bean.notify.OriginNotifyResponse; +import com.github.binarywang.wxpay.bean.notify.WxPayBaseNotifyV3Result; +import com.google.gson.annotations.SerializedName; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; +import java.util.List; + +/** + *
+ * 预约商家转账通知回调结果
+ * 预约批次单中的明细单在转账成功或转账失败时,微信会把相关结果信息发送给商户。
+ * 文档地址:https://pay.weixin.qq.com/doc/v3/merchant/4015901167
+ * 
+ * + * @author wanggang + * created on 2025/11/28 + */ +@Data +public class ReservationTransferNotifyResult implements Serializable, WxPayBaseNotifyV3Result { + private static final long serialVersionUID = 1L; + + /** + * 源数据 + */ + private OriginNotifyResponse rawData; + + /** + * 解密后的数据 + */ + private ReservationTransferNotifyResult.DecryptNotifyResult result; + + @Data + @NoArgsConstructor + public static class DecryptNotifyResult implements Serializable { + private static final long serialVersionUID = 1L; + + /** + * 【商户号】 微信支付分配的商户号 + */ + @SerializedName("mch_id") + private String mchId; + + /** + * 【商户预约批次单号】 商户系统内部的商家预约批次单号 + */ + @SerializedName("out_batch_no") + private String outBatchNo; + + /** + * 【微信预约批次单号】 微信预约批次单号,微信商家转账系统返回的唯一标识 + */ + @SerializedName("reservation_batch_no") + private String reservationBatchNo; + + /** + * 【批次状态】 + * ACCEPTED: 批次已受理 + * PROCESSING: 批次处理中 + * FINISHED: 批次处理完成 + * CLOSED: 批次已关闭 + */ + @SerializedName("batch_state") + private String batchState; + + /** + * 【转账总金额】 转账金额单位为"分" + */ + @SerializedName("total_amount") + private Integer totalAmount; + + /** + * 【转账总笔数】 转账总笔数 + */ + @SerializedName("total_num") + private Integer totalNum; + + /** + * 【转账成功金额】 转账成功金额单位为"分" + */ + @SerializedName("success_amount") + private Integer successAmount; + + /** + * 【转账成功笔数】 转账成功笔数 + */ + @SerializedName("success_num") + private Integer successNum; + + /** + * 【转账失败金额】 转账失败金额单位为"分" + */ + @SerializedName("fail_amount") + private Integer failAmount; + + /** + * 【转账失败笔数】 转账失败笔数 + */ + @SerializedName("fail_num") + private Integer failNum; + + /** + * 【批次创建时间】 批次受理成功时返回 + * 遵循rfc3339标准格式,格式为yyyy-MM-DDTHH:mm:ss+TIMEZONE + */ + @SerializedName("create_time") + private String createTime; + + /** + * 【批次更新时间】 批次最后更新时间 + * 遵循rfc3339标准格式,格式为yyyy-MM-DDTHH:mm:ss+TIMEZONE + */ + @SerializedName("update_time") + private String updateTime; + + /** + * 【转账明细列表】 + */ + @SerializedName("transfer_detail_list") + private List transferDetailList; + } + + /** + * 转账明细通知 + */ + @Data + @NoArgsConstructor + public static class TransferDetailNotify implements Serializable { + private static final long serialVersionUID = 1L; + + /** + * 【商户明细单号】 商户系统内部区分转账批次单下不同转账明细单的唯一标识 + */ + @SerializedName("out_detail_no") + private String outDetailNo; + + /** + * 【微信转账单号】 微信转账单号,微信商家转账系统返回的唯一标识 + */ + @SerializedName("transfer_bill_no") + private String transferBillNo; + + /** + * 【明细状态】 + * PROCESSING: 转账处理中 + * SUCCESS: 转账成功 + * FAIL: 转账失败 + */ + @SerializedName("detail_state") + private String detailState; + + /** + * 【收款用户OpenID】 商户AppID下,某用户的OpenID + */ + @SerializedName("openid") + private String openid; + + /** + * 【转账金额】 转账金额单位为"分" + */ + @SerializedName("transfer_amount") + private Integer transferAmount; + + /** + * 【失败原因】 转账失败原因 + */ + @SerializedName("fail_reason") + private String failReason; + } +} diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/transfer/TransferBillsCancelResult.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/transfer/TransferBillsCancelResult.java new file mode 100644 index 0000000000..9e59fecf73 --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/transfer/TransferBillsCancelResult.java @@ -0,0 +1,48 @@ +package com.github.binarywang.wxpay.bean.transfer; + +import com.google.gson.annotations.SerializedName; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + *
+ * 商家转账到零钱撤销转账接口
+ * 文档地址:https://pay.weixin.qq.com/doc/v3/merchant/4012716458
+ * 
+ * + * @author Nor + * @date 2025/1/17 + */ +@Data +@NoArgsConstructor +public class TransferBillsCancelResult implements Serializable { + private static final long serialVersionUID = -4935840810530008418L; + + /** + * 【商户单号】 商户系统内部的商家单号,要求此参数只能由数字、大小写字母组成,在商户系统内部唯一 + */ + @SerializedName("out_bill_no") + private String outBillNo; + + /** + * 【微信转账单号】 微信转账单号,微信商家转账系统返回的唯一标识 + */ + @SerializedName("transfer_bill_no") + private String transferBillNo; + + /** + * 【单据状态】 商家转账订单状态 + * 可选取值 + * CANCELING: 商户撤销请求受理成功,该笔转账正在撤销中 + * CANCELLED: 转账撤销完成 + */ + private String state; + + /** + * 【最后一次单据状态变更时间】 按照使用rfc3339所定义的格式,格式为yyyy-MM-DDThh:mm:ss+TIMEZONE + */ + @SerializedName("update_time") + private String updateTime; +} diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/transfer/TransferBillsGetResult.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/transfer/TransferBillsGetResult.java new file mode 100644 index 0000000000..2e24a4a3c6 --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/transfer/TransferBillsGetResult.java @@ -0,0 +1,103 @@ +package com.github.binarywang.wxpay.bean.transfer; + +import com.github.binarywang.wxpay.constant.WxPayConstants; +import com.google.gson.annotations.SerializedName; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + *
+ * 商家转账到零钱查询转账单接口
+ * 文档地址:https://pay.weixin.qq.com/doc/v3/merchant/4012716457 https://pay.weixin.qq.com/doc/v3/merchant/4012716437
+ * 
+ * + * @author Nor + * @date 2025/1/17 + */ +@Data +@NoArgsConstructor +public class TransferBillsGetResult implements Serializable { + private static final long serialVersionUID = -6376955113492371763L; + + /** + * 【商户号】 微信支付分配的商户号 + */ + @SerializedName("mch_id") + private String mchId; + + /** + * 【商户单号】 商户系统内部的商家单号,要求此参数只能由数字、大小写字母组成,在商户系统内部唯一 + */ + @SerializedName("out_bill_no") + private String outBillNo; + + /** + * 【商家转账订单号】 商家转账订单的主键,唯一定义此资源的标识 + */ + @SerializedName("transfer_bill_no") + private String transferBillNo; + + /** + * 【商户AppID】 申请商户号的AppID或商户号绑定的AppID(企业号corpid即为此AppID) + */ + private String appid; + + /** + * 【单据状态】 + * 可选取值 + * ACCEPTED: 转账已受理 + * PROCESSING: 转账处理中,转账结果尚未明确,如一直处于此状态,建议检查账户余额是否足够 + * WAIT_USER_CONFIRM: 待收款用户确认,可拉起微信收款确认页面进行收款确认 + * TRANSFERING: 转账结果尚未明确,可拉起微信收款确认页面再次重试确认收款 + * SUCCESS: 转账成功 + * FAIL: 转账失败 + * CANCELING: 商户撤销请求受理成功,该笔转账正在撤销中 + * CANCELLED: 转账撤销完成 + * + * @see WxPayConstants.TransformBillState + */ + private String state; + + /** + * 【转账金额】 转账金额单位为“分”。 + */ + @SerializedName("transfer_amount") + private String transferAmount; + + /** + * 【转账备注】 单条转账备注(微信用户会收到该备注),UTF8编码,最多允许32个字符 + */ + @SerializedName("transfer_remark") + private String transferRemark; + + /** + * 【失败原因】 订单已失败或者已退资金时,返回失败原因 + */ + @SerializedName("fail_reason") + private String failReason; + + /** + * 【收款用户OpenID】 商户AppID下,某用户的OpenID + */ + private String openid; + + /** + * 【收款用户姓名】 收款方真实姓名。支持标准RSA算法和国密算法,公钥由微信侧提供转账金额 >= 2,000元时,该笔明细必须填写若商户传入收款用户姓名,微信支付会校验用户OpenID与姓名是否一致,并提供电子回单 + */ + @SerializedName("user_name") + private String userName; + + /** + * 【单据创建时间】 单据受理成功时返回,按照使用rfc3339所定义的格式,格式为yyyy-MM-DDThh:mm:ss+TIMEZONE + */ + @SerializedName("create_time") + private String createTime; + + /** + * 【最后一次状态变更时间】 单据最后更新时间,按照使用rfc3339所定义的格式,格式为yyyy-MM-DDThh:mm:ss+TIMEZONE + */ + @SerializedName("update_time") + private String updateTime; +} diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/transfer/TransferBillsNotifyResult.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/transfer/TransferBillsNotifyResult.java new file mode 100644 index 0000000000..80709a1022 --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/transfer/TransferBillsNotifyResult.java @@ -0,0 +1,79 @@ +package com.github.binarywang.wxpay.bean.transfer; + +import com.github.binarywang.wxpay.bean.notify.OriginNotifyResponse; +import com.github.binarywang.wxpay.bean.notify.WxPayBaseNotifyV3Result; +import com.google.gson.annotations.SerializedName; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + *
+ *    商家转账到零钱接口将转账结果通知用户
+ *    文档地址:https://pay.weixin.qq.com/doc/v3/merchant/4012716434
+ *  
+ */ +@Data +public class TransferBillsNotifyResult implements Serializable, WxPayBaseNotifyV3Result { + /** + * 源数据 + */ + private OriginNotifyResponse rawData; + /** + * 解密后的数据 + */ + private TransferBillsNotifyResult.DecryptNotifyResult result; + + @Data + @NoArgsConstructor + public static class DecryptNotifyResult implements Serializable { + /** + * 商户号 + */ + @SerializedName(value = "mch_id") + private String mchId; + /** + * 商家批次单号 + */ + @SerializedName(value = "out_bill_no") + private String outBillNo; + /** + * 微信批次单号 + */ + @SerializedName(value = "transfer_bill_no") + private String transferBillNo; + /** + * 批次状态 + */ + @SerializedName(value = "state") + private String state; + /** + * 转账金额 + */ + @SerializedName(value = "transfer_amount") + private Integer transferAmount; + + /** + * 批次状态 + */ + @SerializedName(value = "openid") + private String openid; + + /** + * 单据创建时间 + */ + @SerializedName(value = "create_time") + private String createTime; + /** + * 最后一次状态变更时间 + */ + @SerializedName(value = "update_time") + private String updateTime; + /** + * 错误原因 + */ + @SerializedName(value = "fail_reason") + private String failReason; + } +} diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/transfer/TransferBillsRequest.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/transfer/TransferBillsRequest.java new file mode 100644 index 0000000000..230e564e4b --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/transfer/TransferBillsRequest.java @@ -0,0 +1,108 @@ +package com.github.binarywang.wxpay.bean.transfer; + +import com.github.binarywang.wxpay.v3.SpecEncrypt; +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; + +/** + * 发起商家转账API参数 + * + * @author allovine + * created on 2025/1/15 + **/ +@Data +@Builder(builderMethodName = "newBuilder") +@NoArgsConstructor +@AllArgsConstructor +public class TransferBillsRequest implements Serializable { + private static final long serialVersionUID = -2175582517588397437L; + + /** + * 直连商户的appid + */ + @SerializedName("appid") + private String appid; + + /** + * 商户系统内部的商家单号 + */ + @SerializedName("out_bill_no") + private String outBillNo; + + /** + * 转账场景ID + * 商户平台-产品中心-商家转账 申请 + * 佣金报酬 ID:1005 + */ + @SerializedName("transfer_scene_id") + private String transferSceneId; + + /** + * 用户在直连商户应用下的用户标示 + */ + @SerializedName("openid") + private String openid; + + /** + * 收款用户姓名 + */ + @SpecEncrypt + @SerializedName("user_name") + private String userName; + + /** + * 转账金额 + */ + @SerializedName("transfer_amount") + private Integer transferAmount; + + /** + * 转账备注 + */ + @SerializedName("transfer_remark") + private String transferRemark; + + /** + * 异步接收微信支付结果通知的回调地址,通知url必须为公网可访问的url,必须为https,不能携带参数 + */ + @SerializedName("notify_url") + private String notifyUrl; + + /** + * 用户收款感知 + */ + @SerializedName("user_recv_perception") + private String userRecvPerception; + + + /** + * 转账场景报备信息 + */ + @SerializedName("transfer_scene_report_infos") + private List transferSceneReportInfos; + + + @Data + @Builder(builderMethodName = "newBuilder") + @AllArgsConstructor + @NoArgsConstructor + public static class TransferSceneReportInfo { + /** + * 信息类型 + */ + @SerializedName("info_type") + private String infoType; + + /** + * 信息内容 + */ + @SerializedName("info_content") + private String infoContent; + } +} diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/transfer/TransferBillsResult.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/transfer/TransferBillsResult.java new file mode 100644 index 0000000000..78e0aec6ab --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/transfer/TransferBillsResult.java @@ -0,0 +1,58 @@ +package com.github.binarywang.wxpay.bean.transfer; + +import com.github.binarywang.wxpay.constant.WxPayConstants; +import com.google.gson.annotations.SerializedName; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * 商家转账结果 + * + * @author allovine + * created on 2025/1/15 + **/ +@Data +@NoArgsConstructor +public class TransferBillsResult implements Serializable { + private static final long serialVersionUID = -2175582517588397437L; + + /** + * 商户单号 + */ + @SerializedName("out_bill_no") + private String outBillNo; + + /** + * 微信转账单号 + */ + @SerializedName("transfer_bill_no") + private String transferBillNo; + + /** + * 单据创建时间 + */ + @SerializedName("create_time") + private String createTime; + + /** + * 单据状态 + * + * @see WxPayConstants.TransformBillState + */ + @SerializedName("state") + private String state; + + /** + * 失败原因 + */ + @SerializedName("fail_reason") + private String failReason; + + /** + * 跳转领取页面的package信息 + */ + @SerializedName("package_info") + private String packageInfo; +} diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/transfer/UserAuthorizationStatusResult.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/transfer/UserAuthorizationStatusResult.java new file mode 100644 index 0000000000..e0bab82150 --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/transfer/UserAuthorizationStatusResult.java @@ -0,0 +1,63 @@ +package com.github.binarywang.wxpay.bean.transfer; + +import com.google.gson.annotations.SerializedName; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + *
+ * 商户查询用户授权信息接口响应结果
+ * 商户通过此接口可查询用户是否对商户的商家转账场景进行了授权。
+ * 文档地址:https://pay.weixin.qq.com/doc/v3/merchant/4015901167
+ * 
+ * + * @author wanggang + * created on 2025/11/28 + */ +@Data +@NoArgsConstructor +public class UserAuthorizationStatusResult implements Serializable { + private static final long serialVersionUID = 1L; + + /** + * 【商户AppID】 商户在微信申请公众号或移动应用成功后分配的账号ID + */ + @SerializedName("appid") + private String appid; + + /** + * 【商户号】 微信支付分配的商户号 + */ + @SerializedName("mch_id") + private String mchId; + + /** + * 【用户标识】 用户在直连商户应用下的用户标识 + */ + @SerializedName("openid") + private String openid; + + /** + * 【授权状态】 用户授权状态 + * UNAUTHORIZED: 未授权 + * AUTHORIZED: 已授权 + */ + @SerializedName("authorization_state") + private String authorizationState; + + /** + * 【授权时间】 用户授权时间,遵循rfc3339标准格式 + * 格式为yyyy-MM-DDTHH:mm:ss+TIMEZONE + */ + @SerializedName("authorize_time") + private String authorizeTime; + + /** + * 【取消授权时间】 用户取消授权时间,遵循rfc3339标准格式 + * 格式为yyyy-MM-DDTHH:mm:ss+TIMEZONE + */ + @SerializedName("deauthorize_time") + private String deauthorizeTime; +} 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 293c52eac6..f4a1c3d008 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,18 +4,9 @@ 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 java.io.*; -import java.net.URL; -import java.nio.charset.StandardCharsets; -import java.security.KeyStore; -import java.security.PrivateKey; -import java.security.PublicKey; -import java.security.cert.Certificate; -import java.security.cert.X509Certificate; -import java.util.Optional; -import javax.net.ssl.SSLContext; import lombok.Data; import lombok.EqualsAndHashCode; import lombok.SneakyThrows; @@ -23,9 +14,34 @@ 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.config.Registry; +import org.apache.http.config.RegistryBuilder; +import org.apache.http.conn.socket.ConnectionSocketFactory; +import org.apache.http.conn.socket.PlainConnectionSocketFactory; +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; +import java.security.cert.Certificate; +import java.security.cert.X509Certificate; +import java.util.Base64; +import java.util.Optional; + /** * 微信支付配置 * @@ -39,11 +55,12 @@ public class WxPayConfig { private static final String DEFAULT_PAY_BASE_URL = "https://api.mch.weixin.qq.com"; private static final String PROBLEM_MSG = "证书文件【%s】有问题,请核实!"; private static final String NOT_FOUND_MSG = "证书文件【%s】不存在,请核实!"; + private static final String CERT_NAME_P12 = "p12证书"; /** * 微信支付接口请求地址域名部分. */ - private String payBaseUrl = DEFAULT_PAY_BASE_URL; + private String apiHostUrl = DEFAULT_PAY_BASE_URL; /** * http请求连接超时时间. @@ -183,11 +200,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; /** * 私钥信息 */ @@ -225,17 +263,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 @@ -259,7 +307,7 @@ public SSLContext initSSLContext() throws WxPayException { } try (InputStream inputStream = this.loadConfigInputStream(this.keyString, this.getKeyPath(), - this.keyContent, "p12证书")) { + this.keyContent, CERT_NAME_P12)) { KeyStore keystore = KeyStore.getInstance("PKCS12"); char[] partnerId2charArray = this.getMchId().toCharArray(); keystore.load(inputStream, partnerId2charArray); @@ -282,51 +330,61 @@ 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()) || StringUtils.isNotBlank(this.getPrivateCertString())) || this.getPrivateCertContent() != null) { 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.isNotBlank(this.getPublicKeyString()) || StringUtils.isNotBlank(this.getPublicKeyPath()) || this.publicKeyContent != null) { + if (StringUtils.isBlank(this.getPublicKeyId())) { + throw new WxPayException("请确保和publicKeyId配套使用"); + } try (InputStream pubInputStream = - this.loadConfigInputStream(this.getPublicKeyString(), this.getPublicKeyPath(), - this.publicKeyContent, "publicKeyPath")) { + this.loadConfigInputStream(this.getPublicKeyString(), this.getPublicKeyPath(), + this.publicKeyContent, "publicKeyPath")) { publicKey = PemUtils.loadPublicKey(pubInputStream); } } + // 加载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%覆盖 + if (publicKey == null) { + throw new WxPayException("完全公钥模式下,请确保公钥配置(publicKeyPath/publicKeyString/publicKeyContent)及publicKeyId已设置"); + } + 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() @@ -365,21 +423,71 @@ private WxPayHttpProxy getWxPayHttpProxy() { return null; } + /** + * 从指定参数加载输入流 + * + * @param configString 证书内容进行Base64加密后的字符串 + * @param configPath 证书路径 + * @param configContent 证书内容的字节数组 + * @param certName 证书的标识 + * @return 输入流 + * @throws WxPayException 异常 + */ private InputStream loadConfigInputStream(String configString, String configPath, byte[] configContent, - String fileName) throws WxPayException { - InputStream inputStream; + String certName) throws WxPayException { if (configContent != null) { - inputStream = new ByteArrayInputStream(configContent); - } else if (StringUtils.isNotEmpty(configString)) { - configContent = configString.getBytes(StandardCharsets.UTF_8); - inputStream = new ByteArrayInputStream(configContent); - } else { - if (StringUtils.isBlank(configPath)) { - throw new WxPayException("请确保证书文件地址【" + fileName + "】或者内容已配置"); + return new ByteArrayInputStream(configContent); + } + + if (StringUtils.isNotEmpty(configString)) { + // 判断是否为PEM格式的字符串(包含-----BEGIN和-----END标记) + if (isPemFormat(configString)) { + // PEM格式直接转为字节流,让PemUtils处理 + configContent = configString.getBytes(StandardCharsets.UTF_8); + } else { + // 尝试Base64解码 + try { + byte[] decoded = Base64.getDecoder().decode(configString); + // 检查解码后的内容是否为PEM格式(即用户传入的是base64编码的完整PEM文件) + String decodedString = new String(decoded, StandardCharsets.UTF_8); + if (isPemFormat(decodedString)) { + // 解码后是PEM格式,使用解码后的内容 + configContent = decoded; + } else { + // 解码后不是PEM格式,可能是: + // 1. p12证书的二进制内容 - 应该返回解码后的二进制数据 + // 2. 私钥/公钥的纯base64内容(不含PEM头尾) - 应该返回原始字符串,让PemUtils处理 + // 通过certName区分:p12证书使用解码后的数据,其他情况返回原始字符串 + if (CERT_NAME_P12.equals(certName)) { + configContent = decoded; + } else { + // 对于私钥/公钥/证书,返回原始字符串字节,让PemUtils处理base64解码 + configContent = configString.getBytes(StandardCharsets.UTF_8); + } + } + } catch (IllegalArgumentException e) { + // Base64解码失败,可能是格式不正确,抛出异常 + throw new WxPayException(String.format("【%s】的Base64格式不正确", certName), e); + } } - inputStream = this.loadConfigInputStream(configPath); + return new ByteArrayInputStream(configContent); } - return inputStream; + + if (StringUtils.isBlank(configPath)) { + throw new WxPayException(String.format("请确保【%s】的文件地址【%s】存在", certName, configPath)); + } + + return this.loadConfigInputStream(configPath); + } + + /** + * 判断字符串是否为PEM格式(包含-----BEGIN和-----END标记) + * + * @param content 要检查的字符串 + * @return 是否为PEM格式 + */ + private boolean isPemFormat(String content) { + return content != null && content.contains("-----BEGIN") && content.contains("-----END"); } @@ -451,7 +559,7 @@ private Object[] p12ToPem() { // 分解p12证书文件 try (InputStream inputStream = this.loadConfigInputStream(this.keyString, this.getKeyPath(), - this.keyContent, "p12证书")) { + this.keyContent, CERT_NAME_P12)) { KeyStore keyStore = KeyStore.getInstance("PKCS12"); keyStore.load(inputStream, key.toCharArray()); @@ -468,4 +576,124 @@ 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的连接池管理器 + SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory( + sslContext, + new DefaultHostnameVerifier() + ); + + Registry socketFactoryRegistry = RegistryBuilder + .create() + .register("https", sslsf) + .register("http", PlainConnectionSocketFactory.getSocketFactory()) + .build(); + PoolingHttpClientConnectionManager connectionManager = + new PoolingHttpClientConnectionManager(socketFactoryRegistry); + + // 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/constant/WxPayConstants.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/constant/WxPayConstants.java index 819cdfe731..ec9e14ff2d 100644 --- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/constant/WxPayConstants.java +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/constant/WxPayConstants.java @@ -6,6 +6,7 @@ import com.github.binarywang.wxpay.bean.order.WxPayNativeOrderResult; import com.github.binarywang.wxpay.bean.result.WxPayMicropayResult; import com.google.common.collect.Lists; +import lombok.experimental.UtilityClass; import org.apache.commons.lang3.time.FastDateFormat; import java.text.Format; @@ -353,4 +354,174 @@ public static class ReceiverType { public static final String PERSONAL_SUB_OPENID = "PERSONAL_SUB_OPENID"; } + /** + * 微信商户转账订单状态 + */ + @UtilityClass + public static class TransformBillState { + /** + * 转账已受理 + */ + public static final String ACCEPTED = "ACCEPTED"; + + /** + * 转账处理中,转账结果尚未明确,如一直处于此状态,建议检查账户余额是否足够 + */ + public static final String PROCESSING = "PROCESSING"; + + /** + * 待收款用户确认,可拉起微信收款确认页面进行收款确认 + */ + public static final String WAIT_USER_CONFIRM = "WAIT_USER_CONFIRM"; + + /** + * 转账结果尚未明确,可拉起微信收款确认页面再次重试确认收款 + */ + public static final String TRANSFERING = "TRANSFERING"; + + /** + * 转账成功 + */ + public static final String SUCCESS = "SUCCESS"; + + /** + * 转账失败 + */ + public static final String FAIL = "FAIL"; + + /** + * 商户撤销请求受理成功,该笔转账正在撤销中 + */ + public static final String CANCELING = "CANCELING"; + + /** + * 转账撤销完成 + */ + public static final String CANCELLED = "CANCELLED"; + + } + + + /** + * 用户授权状态 + * + * @see 商户查询用户授权信息 + */ + @UtilityClass + public static class AuthorizationState { + /** + * 未授权 + */ + public static final String UNAUTHORIZED = "UNAUTHORIZED"; + + /** + * 已授权 + */ + public static final String AUTHORIZED = "AUTHORIZED"; + } + + /** + * 预约转账批次状态 + * + * @see 批量预约商家转账 + */ + @UtilityClass + public static class ReservationBatchState { + /** + * 批次已受理 + */ + public static final String ACCEPTED = "ACCEPTED"; + + /** + * 批次处理中 + */ + public static final String PROCESSING = "PROCESSING"; + + /** + * 批次处理完成 + */ + public static final String FINISHED = "FINISHED"; + + /** + * 批次已关闭 + */ + public static final String CLOSED = "CLOSED"; + } + + /** + * 预约转账批次关闭原因 + * + * @see 预约转账批次单号查询 + */ + @UtilityClass + public static class ReservationBatchCloseReason { + /** + * 商户主动撤销 + */ + public static final String MERCHANT_REVOCATION = "MERCHANT_REVOCATION"; + + /** + * 系统超时关闭 + */ + public static final String OVERDUE_CLOSE = "OVERDUE_CLOSE"; + } + + /** + * 【转账场景ID】 该笔转账使用的转账场景,可前往“商户平台-产品中心-商家转账”中申请。 + */ + @UtilityClass + public static class TransformSceneId { + /** + * 现金营销 + */ + public static final String CASH_MARKETING = "1001"; + } + + /** + * 【运营工具转账场景ID】 运营工具专用转账场景,用于商户日常运营活动 + * + * @see 运营工具-商家转账API + */ + @UtilityClass + public static class OperationSceneId { + /** + * 运营工具现金营销 + */ + public static final String OPERATION_CASH_MARKETING = "2001"; + + /** + * 运营工具佣金报酬 + */ + public static final String OPERATION_COMMISSION = "2002"; + + /** + * 运营工具推广奖励 + */ + public static final String OPERATION_PROMOTION = "2003"; + } + + /** + * 用户收款感知 + * + * @see 官方文档 + */ + @UtilityClass + public static class UserRecvPerception { + /** + * 转账场景 现金营销 + * 场景介绍 向参与营销活动的用户发放现金奖励 + */ + public static class CASH_MARKETING { + /** + * 默认展示 + */ + public static final String ACTIVITY = "活动奖励"; + + /** + * 需在发起转账时,“用户收款感知”字段主动传入“现金奖励”才可展示 + */ + public static final String CASH = "现金奖励"; + } + + } } 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/BusinessOperationTransferExample.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/example/BusinessOperationTransferExample.java new file mode 100644 index 0000000000..d11738816b --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/example/BusinessOperationTransferExample.java @@ -0,0 +1,135 @@ +package com.github.binarywang.wxpay.example; + +import com.github.binarywang.wxpay.bean.transfer.*; +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.BusinessOperationTransferService; +import com.github.binarywang.wxpay.service.WxPayService; +import com.github.binarywang.wxpay.service.impl.WxPayServiceImpl; + +/** + * 运营工具-商家转账API使用示例 + * + * 微信支付为商户提供的运营工具转账能力,用于商户的日常运营活动中进行转账操作 + * + * @author WxJava Team + * @see 运营工具-商家转账API + */ +public class BusinessOperationTransferExample { + + private WxPayService wxPayService; + private BusinessOperationTransferService businessOperationTransferService; + + public void init() { + // 初始化配置 + WxPayConfig config = new WxPayConfig(); + config.setAppId("your_app_id"); + config.setMchId("your_mch_id"); + config.setMchKey("your_mch_key"); + config.setKeyPath("path_to_your_cert.p12"); + + // 初始化服务 + wxPayService = new WxPayServiceImpl(); + wxPayService.setConfig(config); + businessOperationTransferService = wxPayService.getBusinessOperationTransferService(); + } + + /** + * 发起运营工具转账示例 + */ + public void createOperationTransferExample() { + try { + // 构建转账请求 + BusinessOperationTransferRequest request = BusinessOperationTransferRequest.newBuilder() + .appid("your_app_id") // 应用ID + .outBillNo("OT" + System.currentTimeMillis()) // 商户转账单号 + .operationSceneId(WxPayConstants.OperationSceneId.OPERATION_CASH_MARKETING) // 运营工具转账场景ID + .openid("user_openid") // 用户openid + .userName("张三") // 用户姓名(可选) + .transferAmount(100) // 转账金额,单位分 + .transferRemark("运营活动奖励") // 转账备注 + .userRecvPerception(WxPayConstants.UserRecvPerception.CASH_MARKETING.CASH) // 用户收款感知 + .notifyUrl("https://your-domain.com/notify") // 回调通知地址 + .build(); + + // 发起转账 + BusinessOperationTransferResult result = businessOperationTransferService.createOperationTransfer(request); + + System.out.println("转账成功!"); + System.out.println("商户单号: " + result.getOutBillNo()); + System.out.println("微信转账单号: " + result.getTransferBillNo()); + System.out.println("转账状态: " + result.getTransferState()); + System.out.println("创建时间: " + result.getCreateTime()); + + } catch (WxPayException e) { + System.err.println("转账失败: " + e.getMessage()); + e.printStackTrace(); + } + } + + /** + * 通过商户单号查询转账结果示例 + */ + public void queryByOutBillNoExample() { + try { + String outBillNo = "OT1640995200000"; // 商户转账单号 + + BusinessOperationTransferQueryResult result = businessOperationTransferService + .queryOperationTransferByOutBillNo(outBillNo); + + System.out.println("查询成功!"); + System.out.println("商户单号: " + result.getOutBillNo()); + System.out.println("微信转账单号: " + result.getTransferBillNo()); + System.out.println("转账状态: " + result.getTransferState()); + System.out.println("转账金额: " + result.getTransferAmount() + "分"); + System.out.println("创建时间: " + result.getCreateTime()); + System.out.println("更新时间: " + result.getUpdateTime()); + + } catch (WxPayException e) { + System.err.println("查询失败: " + e.getMessage()); + e.printStackTrace(); + } + } + + /** + * 通过微信转账单号查询转账结果示例 + */ + public void queryByTransferBillNoExample() { + try { + String transferBillNo = "1040000071100999991182020050700019480001"; // 微信转账单号 + + BusinessOperationTransferQueryResult result = businessOperationTransferService + .queryOperationTransferByTransferBillNo(transferBillNo); + + System.out.println("查询成功!"); + System.out.println("商户单号: " + result.getOutBillNo()); + System.out.println("微信转账单号: " + result.getTransferBillNo()); + System.out.println("运营场景ID: " + result.getOperationSceneId()); + System.out.println("转账状态: " + result.getTransferState()); + + } catch (WxPayException e) { + System.err.println("查询失败: " + e.getMessage()); + e.printStackTrace(); + } + } + + /** + * 使用配置示例 + */ + public static void main(String[] args) { + BusinessOperationTransferExample example = new BusinessOperationTransferExample(); + + // 初始化配置 + example.init(); + + // 1. 发起运营工具转账 + example.createOperationTransferExample(); + + // 2. 查询转账结果 + // example.queryByOutBillNoExample(); + + // 3. 通过微信转账单号查询 + // example.queryByTransferBillNoExample(); + } +} \ No newline at end of file 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/BusinessOperationTransferService.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/BusinessOperationTransferService.java new file mode 100644 index 0000000000..195d3a8409 --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/BusinessOperationTransferService.java @@ -0,0 +1,82 @@ +package com.github.binarywang.wxpay.service; + +import com.github.binarywang.wxpay.bean.transfer.BusinessOperationTransferRequest; +import com.github.binarywang.wxpay.bean.transfer.BusinessOperationTransferResult; +import com.github.binarywang.wxpay.bean.transfer.BusinessOperationTransferQueryRequest; +import com.github.binarywang.wxpay.bean.transfer.BusinessOperationTransferQueryResult; +import com.github.binarywang.wxpay.exception.WxPayException; + +/** + * 运营工具-商家转账API + *

+ * 微信支付为商户提供的运营工具转账能力,用于商户的日常运营活动中进行转账操作 + * + * @author WxJava Team + * @see 运营工具-商家转账API + */ +public interface BusinessOperationTransferService { + + /** + *

+   * 发起运营工具商家转账
+   *
+   * 请求方式:POST(HTTPS)
+   * 请求地址:https://api.mch.weixin.qq.com/v3/fund-app/mch-transfer/transfer-bills
+   *
+   * 文档地址:运营工具-商家转账API
+   * 
+ * + * @param request 运营工具转账请求参数 + * @return BusinessOperationTransferResult 转账结果 + * @throws WxPayException 微信支付异常 + */ + BusinessOperationTransferResult createOperationTransfer(BusinessOperationTransferRequest request) throws WxPayException; + + /** + *
+   * 查询运营工具转账结果
+   *
+   * 请求方式:GET(HTTPS)
+   * 请求地址:https://api.mch.weixin.qq.com/v3/fund-app/mch-transfer/transfer-bills/out-bill-no/{out_bill_no}
+   *
+   * 文档地址:运营工具-商家转账API
+   * 
+ * + * @param request 查询请求参数 + * @return BusinessOperationTransferQueryResult 查询结果 + * @throws WxPayException 微信支付异常 + */ + BusinessOperationTransferQueryResult queryOperationTransfer(BusinessOperationTransferQueryRequest request) throws WxPayException; + + /** + *
+   * 通过商户单号查询运营工具转账结果
+   *
+   * 请求方式:GET(HTTPS)
+   * 请求地址:https://api.mch.weixin.qq.com/v3/fund-app/mch-transfer/transfer-bills/out-bill-no/{out_bill_no}
+   *
+   * 文档地址:运营工具-商家转账API
+   * 
+ * + * @param outBillNo 商户单号 + * @return BusinessOperationTransferQueryResult 查询结果 + * @throws WxPayException 微信支付异常 + */ + BusinessOperationTransferQueryResult queryOperationTransferByOutBillNo(String outBillNo) throws WxPayException; + + /** + *
+   * 通过微信转账单号查询运营工具转账结果
+   *
+   * 请求方式:GET(HTTPS)
+   * 请求地址:https://api.mch.weixin.qq.com/v3/fund-app/mch-transfer/transfer-bills/transfer-bill-no/{transfer_bill_no}
+   *
+   * 文档地址:运营工具-商家转账API
+   * 
+ * + * @param transferBillNo 微信转账单号 + * @return BusinessOperationTransferQueryResult 查询结果 + * @throws WxPayException 微信支付异常 + */ + BusinessOperationTransferQueryResult queryOperationTransferByTransferBillNo(String transferBillNo) throws WxPayException; +} \ No newline at end of file diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/ComplaintService.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/ComplaintService.java index 66de1458a3..6fc1367cf4 100644 --- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/ComplaintService.java +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/ComplaintService.java @@ -152,7 +152,7 @@ public interface ComplaintService { /** *
    * 商户上传反馈图片API
-   * 文档详见: ...
+   * 文档详见: ...
    * 接口链接:https://api.mch.weixin.qq.com/v3/merchant-service/images/upload
    * 
* @@ -165,7 +165,7 @@ public interface ComplaintService { /** *
    * 商户上传反馈图片API
-   * 文档详见: ...
+   * 文档详见: ...
    * 接口链接:https://api.mch.weixin.qq.com/v3/merchant-service/images/upload
    * 
* diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/EcommerceService.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/EcommerceService.java index 2dbb2906c3..b630ce1785 100644 --- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/EcommerceService.java +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/EcommerceService.java @@ -417,10 +417,23 @@ public interface EcommerceService { */ RefundNotifyResult parseRefundNotifyResult(String notifyData, SignatureHeader header) throws WxPayException; + /** + *
+   * 提现状态变更通知回调数据处理
+   * 文档地址: https://pay.weixin.qq.com/doc/v3/partner/4013049135
+   * 
+ * + * @param notifyData 通知数据 + * @param header 通知头部数据,不传则表示不校验头 + * @return 解密后通知数据 withdraw notify result + * @throws WxPayException the wx pay exception + */ + WithdrawNotifyResult parseWithdrawNotifyResult(String notifyData, SignatureHeader header) throws WxPayException; + /** *
    * 二级商户账户余额提现API
-   * 文档地址: https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/ecommerce/fund/chapter3_2.shtml
+   * 文档地址: https://pay.weixin.qq.com/doc/v3/partner/4012476652
    * 
* * @param request 提现请求 @@ -432,7 +445,7 @@ public interface EcommerceService { /** *
    * 电商平台提现API
-   * 文档地址: https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/ecommerce/fund/chapter3_5.shtml
+   * 文档地址: https://pay.weixin.qq.com/doc/v3/partner/4012476670
    * 
* * @param request 提现请求 @@ -466,6 +479,43 @@ public interface EcommerceService { */ SpWithdrawStatusResult querySpWithdrawByOutRequestNo(String outRequestNo) throws WxPayException; + /** + *
+   * 平台查询预约提现状态(根据微信支付预约提现单号查询)
+   * 文档地址: https://pay.weixin.qq.com/doc/v3/partner/4012476674
+   * 
+ * + * @param withdrawId 微信支付提现单号 + * @return 返回数据 return sp withdraw status result + * @throws WxPayException the wx pay exception + */ + SpWithdrawStatusResult querySpWithdrawByWithdrawId(String withdrawId) throws WxPayException; + + /** + *
+   * 二级商户按日终余额预约提现
+   * 文档地址: https://pay.weixin.qq.com/doc/v3/partner/4013328143
+   * 
+ * + * @param request 提现请求 + * @return 返回数据 return day-end balance withdraw result + * @throws WxPayException the wx pay exception + */ + SubDayEndBalanceWithdrawResult subDayEndBalanceWithdraw(SubDayEndBalanceWithdrawRequest request) throws WxPayException; + + /** + *
+   * 查询二级商户按日终余额预约提现状态
+   * 文档地址: https://pay.weixin.qq.com/doc/v3/partner/4013328163
+   * 
+ * + * @param subMchid 二级商户号 + * @param outRequestNo 商户提现单号 + * @return 返回数据 return day-end balance withdraw status result + * @throws WxPayException the wx pay exception + */ + SubDayEndBalanceWithdrawStatusResult querySubDayEndBalanceWithdraw(String subMchid, String outRequestNo) throws WxPayException; + /** *
    * 修改结算账号API
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/SubscriptionBillingService.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/SubscriptionBillingService.java
new file mode 100644
index 0000000000..e662e4dd4f
--- /dev/null
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/SubscriptionBillingService.java
@@ -0,0 +1,105 @@
+package com.github.binarywang.wxpay.service;
+
+import com.github.binarywang.wxpay.bean.subscriptionbilling.*;
+import com.github.binarywang.wxpay.exception.WxPayException;
+
+/**
+ * 微信支付-预约扣费服务 (连续包月功能)
+ * 
+ *   微信支付预约扣费功能,支持商户在用户授权的情况下,
+ *   按照约定的时间和金额,自动从用户的支付账户中扣取费用。
+ *   主要用于连续包月、订阅服务等场景。
+ *   
+ *   文档详见: https://pay.weixin.qq.com/doc/v3/merchant/4012161105
+ * 
+ * + * @author Binary Wang + * created on 2024-08-31 + */ +public interface SubscriptionBillingService { + + /** + * 预约扣费 + *
+   *   商户可以通过该接口预约未来某个时间点的扣费。
+   *   适用于连续包月、订阅服务等场景。
+   *   
+   *   文档详见: https://pay.weixin.qq.com/doc/v3/merchant/4012161105
+   *   请求URL: https://api.mch.weixin.qq.com/v3/subscription-billing/schedule
+   *   请求方式: POST
+   *   是否需要证书: 是
+   * 
+ * + * @param request 预约扣费请求参数 + * @return 预约扣费结果 + * @throws WxPayException 微信支付异常 + */ + SubscriptionScheduleResult scheduleSubscription(SubscriptionScheduleRequest request) throws WxPayException; + + /** + * 查询预约扣费 + *
+   *   商户可以通过该接口查询已预约的扣费信息。
+   *   
+   *   文档详见: https://pay.weixin.qq.com/doc/v3/merchant/4012161105
+   *   请求URL: https://api.mch.weixin.qq.com/v3/subscription-billing/schedule/{subscription_id}
+   *   请求方式: GET
+   * 
+ * + * @param subscriptionId 预约扣费ID + * @return 预约扣费查询结果 + * @throws WxPayException 微信支付异常 + */ + SubscriptionQueryResult querySubscription(String subscriptionId) throws WxPayException; + + /** + * 取消预约扣费 + *
+   *   商户可以通过该接口取消已预约的扣费。
+   *   
+   *   文档详见: https://pay.weixin.qq.com/doc/v3/merchant/4012161105
+   *   请求URL: https://api.mch.weixin.qq.com/v3/subscription-billing/schedule/{subscription_id}/cancel
+   *   请求方式: POST
+   *   是否需要证书: 是
+   * 
+ * + * @param request 取消预约扣费请求参数 + * @return 取消预约扣费结果 + * @throws WxPayException 微信支付异常 + */ + SubscriptionCancelResult cancelSubscription(SubscriptionCancelRequest request) throws WxPayException; + + /** + * 立即扣费 + *
+   *   商户可以通过该接口立即执行扣费操作。
+   *   通常用于补扣失败的费用或者特殊情况下的即时扣费。
+   *   
+   *   文档详见: https://pay.weixin.qq.com/doc/v3/merchant/4012161105
+   *   请求URL: https://api.mch.weixin.qq.com/v3/subscription-billing/instant-billing
+   *   请求方式: POST
+   *   是否需要证书: 是
+   * 
+ * + * @param request 立即扣费请求参数 + * @return 立即扣费结果 + * @throws WxPayException 微信支付异常 + */ + SubscriptionInstantBillingResult instantBilling(SubscriptionInstantBillingRequest request) throws WxPayException; + + /** + * 查询扣费记录 + *
+   *   商户可以通过该接口查询扣费记录。
+   *   
+   *   文档详见: https://pay.weixin.qq.com/doc/v3/merchant/4012161105
+   *   请求URL: https://api.mch.weixin.qq.com/v3/subscription-billing/transactions
+   *   请求方式: GET
+   * 
+ * + * @param request 查询扣费记录请求参数 + * @return 扣费记录查询结果 + * @throws WxPayException 微信支付异常 + */ + SubscriptionTransactionQueryResult queryTransactions(SubscriptionTransactionQueryRequest request) throws WxPayException; +} \ No newline at end of file diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/TransferService.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/TransferService.java index ebf746214d..e48e327505 100644 --- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/TransferService.java +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/TransferService.java @@ -111,4 +111,201 @@ public interface TransferService { */ TransferBatchDetailResult transferBatchesOutBatchNoDetail(String outBatchNo, String outDetailNo) throws WxPayException; + /** + *
+   *
+   * 2025.1.15 开始新接口 发起商家转账API
+   *
+   * 请求方式:POST(HTTPS)
+   * 请求地址:请求地址
+   *
+   * 文档地址:发起商家转账API
+   * 
+ * + * @param request 转账请求参数 + * @return TransferBillsResult 转账结果 + * @throws WxPayException . + */ + TransferBillsResult transferBills(TransferBillsRequest request) throws WxPayException; + + /** + *
+   *
+   * 2025.1.15 开始新接口 撤销转账API
+   *
+   * 请求方式:POST(HTTPS)
+   * 请求地址:请求地址
+   *
+   * 文档地址:商户撤销转账API
+   * 
+ * + * @param outBillNo 【商户单号】 商户系统内部的商家单号,要求此参数只能由数字、大小写字母组成,在商户系统内部唯一 + * @return TransformBillsGetResult 转账单 + * @throws WxPayException . + */ + TransferBillsCancelResult transformBillsCancel(String outBillNo) throws WxPayException; + + /** + *
+   *
+   * 2025.1.15 开始新接口 发起商家转账API
+   *
+   * 请求方式:GET(HTTPS)
+   * 请求地址:请求地址
+   *
+   * 文档地址:商户单号查询转账单API
+   * 
+ * + * @param outBillNo 【商户单号】 商户系统内部的商家单号,要求此参数只能由数字、大小写字母组成,在商户系统内部唯一 + * @return TransformBillsGetResult 转账单 + * @throws WxPayException . + */ + TransferBillsGetResult getBillsByOutBillNo(String outBillNo) throws WxPayException; + + /** + *
+   *
+   * 2025.1.15 开始新接口 微信单号查询转账单API
+   *
+   * 请求方式:GET(HTTPS)
+   * 请求地址:请求地址
+   *
+   * 文档地址:商户单号查询转账单API
+   * 
+ * + * @param transferBillNo 【微信转账单号】 微信转账单号,微信商家转账系统返回的唯一标识 + * @return TransformBillsGetResult 转账单 + * @throws WxPayException . + */ + TransferBillsGetResult getBillsByTransferBillNo(String transferBillNo) throws WxPayException; + + /** + * 2025.1.15 开始新接口 解析商家转账结果 + * 详见 + * + * @param notifyData 通知数据 + * @param header 通知头部数据,不传则表示不校验头 + * @return the wx transfer notify result + * @throws WxPayException the wx pay exception + */ + TransferBillsNotifyResult parseTransferBillsNotifyResult(String notifyData, SignatureHeader header) throws WxPayException; + + // ===================== 用户授权免确认模式相关接口 ===================== + + /** + *
+   * 商户查询用户授权信息接口
+   *
+   * 商户通过此接口可查询用户是否对商户的商家转账场景进行了授权。
+   *
+   * 请求方式:GET(HTTPS)
+   * 请求地址:请求地址
+   *
+   * 文档地址:商户查询用户授权信息
+   * 
+ * + * @param openid 用户在直连商户应用下的用户标识 + * @param transferSceneId 转账场景ID + * @return UserAuthorizationStatusResult 用户授权信息 + * @throws WxPayException . + */ + UserAuthorizationStatusResult getUserAuthorizationStatus(String openid, String transferSceneId) throws WxPayException; + + /** + *
+   * 批量预约商家转账接口
+   *
+   * 商户可以通过批量预约接口一次发起批量转账请求,最多可以同时向50个用户发起转账。
+   * 批量预约接口适用于用户已授权免确认的场景,在转账时无需用户确认即可完成转账。
+   *
+   * 请求方式:POST(HTTPS)
+   * 请求地址:请求地址
+   *
+   * 文档地址:批量预约商家转账
+   * 
+ * + * @param request 批量预约商家转账请求参数 + * @return ReservationTransferBatchResult 批量预约商家转账结果 + * @throws WxPayException . + */ + ReservationTransferBatchResult reservationTransferBatch(ReservationTransferBatchRequest request) throws WxPayException; + + /** + *
+   * 商户预约批次单号查询批次单接口
+   *
+   * 通过商户预约批次单号查询批量预约商家转账批次单基本信息。
+   *
+   * 请求方式:GET(HTTPS)
+   * 请求地址:请求地址
+   *
+   * 文档地址:商户预约批次单号查询批次单
+   * 
+ * + * @param outBatchNo 商户预约批次单号 + * @param needQueryDetail 是否需要查询明细 + * @param offset 分页偏移量 + * @param limit 分页大小 + * @param detailState 明细状态(PROCESSING/SUCCESS/FAIL) + * @return ReservationTransferBatchGetResult 批量预约商家转账批次查询结果 + * @throws WxPayException . + */ + ReservationTransferBatchGetResult getReservationTransferBatchByOutBatchNo(String outBatchNo, Boolean needQueryDetail, + Integer offset, Integer limit, String detailState) throws WxPayException; + + /** + *
+   * 微信预约批次单号查询批次单接口
+   *
+   * 通过微信预约批次单号查询批量预约商家转账批次单基本信息。
+   *
+   * 请求方式:GET(HTTPS)
+   * 请求地址:请求地址
+   *
+   * 文档地址:微信预约批次单号查询批次单
+   * 
+ * + * @param reservationBatchNo 微信预约批次单号 + * @param needQueryDetail 是否需要查询明细 + * @param offset 分页偏移量 + * @param limit 分页大小 + * @param detailState 明细状态(PROCESSING/SUCCESS/FAIL) + * @return ReservationTransferBatchGetResult 批量预约商家转账批次查询结果 + * @throws WxPayException . + */ + ReservationTransferBatchGetResult getReservationTransferBatchByReservationBatchNo(String reservationBatchNo, Boolean needQueryDetail, + Integer offset, Integer limit, String detailState) throws WxPayException; + + /** + *
+   * 解析预约商家转账通知回调结果
+   *
+   * 预约批次单中的明细单在转账成功或转账失败时,微信会把相关结果信息发送给商户。
+   *
+   * 文档地址:预约商家转账通知
+   * 
+ * + * @param notifyData 通知数据 + * @param header 通知头部数据,不传则表示不校验头 + * @return ReservationTransferNotifyResult 预约商家转账通知结果 + * @throws WxPayException the wx pay exception + */ + ReservationTransferNotifyResult parseReservationTransferNotifyResult(String notifyData, SignatureHeader header) throws WxPayException; + + /** + *
+   * 关闭预约商家转账批次接口
+   *
+   * 商户可以通过此接口关闭预约商家转账批次单。关闭后,该批次内所有未成功的转账将被取消。
+   *
+   * 请求方式:POST(HTTPS)
+   * 请求地址:请求地址
+   *
+   * 文档地址:关闭预约商家转账批次
+   * 
+ * + * @param outBatchNo 商户预约批次单号 + * @throws WxPayException . + */ + void closeReservationTransferBatch(String outBatchNo) throws WxPayException; } diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/WxDepositService.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/WxDepositService.java new file mode 100644 index 0000000000..cb0bc3b062 --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/WxDepositService.java @@ -0,0 +1,90 @@ +package com.github.binarywang.wxpay.service; + +import com.github.binarywang.wxpay.bean.request.WxDepositConsumeRequest; +import com.github.binarywang.wxpay.bean.request.WxDepositOrderQueryRequest; +import com.github.binarywang.wxpay.bean.request.WxDepositRefundRequest; +import com.github.binarywang.wxpay.bean.request.WxDepositUnfreezeRequest; +import com.github.binarywang.wxpay.bean.request.WxDepositUnifiedOrderRequest; +import com.github.binarywang.wxpay.bean.result.WxDepositConsumeResult; +import com.github.binarywang.wxpay.bean.result.WxDepositOrderQueryResult; +import com.github.binarywang.wxpay.bean.result.WxDepositRefundResult; +import com.github.binarywang.wxpay.bean.result.WxDepositUnfreezeResult; +import com.github.binarywang.wxpay.bean.result.WxDepositUnifiedOrderResult; +import com.github.binarywang.wxpay.exception.WxPayException; + +/** + *
+ *   微信押金支付相关接口.
+ *   https://pay.weixin.qq.com/wiki/doc/api/deposit_sl.php?chapter=27_7&index=1
+ * 
+ * + * @author Binary Wang + * created on 2024-09-24 + */ +public interface WxDepositService { + + /** + *
+   *   押金下单
+   *   详见:https://pay.weixin.qq.com/wiki/doc/api/deposit_sl.php?chapter=27_7&index=2
+   *   用于商户发起押金支付,支持JSAPI、NATIVE、APP等支付方式
+   * 
+ * + * @param request 押金下单请求对象 + * @return wx deposit unified order result + * @throws WxPayException wx pay exception + */ + WxDepositUnifiedOrderResult unifiedOrder(WxDepositUnifiedOrderRequest request) throws WxPayException; + + /** + *
+   *   查询押金订单
+   *   详见:https://pay.weixin.qq.com/wiki/doc/api/deposit_sl.php?chapter=27_7&index=3
+   *   通过商户订单号或微信订单号查询押金订单状态
+   * 
+ * + * @param request 查询押金订单请求对象 + * @return wx deposit order query result + * @throws WxPayException wx pay exception + */ + WxDepositOrderQueryResult queryOrder(WxDepositOrderQueryRequest request) throws WxPayException; + + /** + *
+   *   押金消费
+   *   详见:https://pay.weixin.qq.com/wiki/doc/api/deposit_sl.php?chapter=27_7&index=4
+   *   用于对已支付的押金进行消费扣减
+   * 
+ * + * @param request 押金消费请求对象 + * @return wx deposit consume result + * @throws WxPayException wx pay exception + */ + WxDepositConsumeResult consume(WxDepositConsumeRequest request) throws WxPayException; + + /** + *
+   *   押金撤销
+   *   详见:https://pay.weixin.qq.com/wiki/doc/api/deposit_sl.php?chapter=27_7&index=5
+   *   用于对已支付的押金进行撤销退还
+   * 
+ * + * @param request 押金撤销请求对象 + * @return wx deposit unfreeze result + * @throws WxPayException wx pay exception + */ + WxDepositUnfreezeResult unfreeze(WxDepositUnfreezeRequest request) throws WxPayException; + + /** + *
+   *   押金退款
+   *   详见:https://pay.weixin.qq.com/wiki/doc/api/deposit_sl.php?chapter=27_7&index=6
+   *   用于对已消费的押金进行退款
+   * 
+ * + * @param request 押金退款请求对象 + * @return wx deposit refund result + * @throws WxPayException wx pay exception + */ + WxDepositRefundResult refund(WxDepositRefundRequest request) throws WxPayException; +} \ 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 57c2937c62..93da0d1332 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,8 @@ 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; import com.github.binarywang.wxpay.exception.WxPayException; @@ -38,16 +40,18 @@ public interface WxPayService { * Map里 加入新的 {@link WxPayConfig},适用于动态添加新的微信商户配置. * * @param mchId 商户id + * @param appId 微信应用id * @param wxPayConfig 新的微信配置 */ - void addConfig(String mchId, WxPayConfig wxPayConfig); + void addConfig(String mchId, String appId, WxPayConfig wxPayConfig); /** - * 从 Map中 移除 {@link String mchId} 所对应的 {@link WxPayConfig},适用于动态移除微信商户配置. + * 从 Map中 移除 {@link String mchId} 和 {@link String appId} 所对应的 {@link WxPayConfig},适用于动态移除微信商户配置. * * @param mchId 对应商户的标识 + * @param appId 微信应用id */ - void removeConfig(String mchId); + void removeConfig(String mchId, String appId); /** * 注入多个 {@link WxPayConfig} 的实现. 并为每个 {@link WxPayConfig} 赋予不同的 {@link String mchId} 值 @@ -69,17 +73,19 @@ public interface WxPayService { * 进行相应的商户切换. * * @param mchId 商户标识 + * @param appId 微信应用id * @return 切换是否成功 boolean */ - boolean switchover(String mchId); + boolean switchover(String mchId, String appId); /** * 进行相应的商户切换. * * @param mchId 商户标识 + * @param appId 微信应用id * @return 切换成功 ,则返回当前对象,方便链式调用,否则抛出异常 */ - WxPayService switchoverTo(String mchId); + WxPayService switchoverTo(String mchId, String appId); /** * 发送post请求,得到响应字节数组. @@ -103,6 +109,19 @@ public interface WxPayService { */ String post(String url, String requestStr, boolean useKey) throws WxPayException; + + /** + * 发送post请求,得到响应字符串. + * + * @param url 请求地址 + * @param requestStr 请求信息 + * @param useKey 是否使用证书 + * @param mimeType Content-Type请求头 + * @return 返回请求结果字符串 string + * @throws WxPayException the wx pay exception + */ + String post(String url, String requestStr, boolean useKey, String mimeType) throws WxPayException; + /** * 发送post请求,得到响应字符串. * @@ -210,6 +229,13 @@ public interface WxPayService { */ WxEntrustPapService getWxEntrustPapService(); + /** + * 获取微信押金支付服务类 + * + * @return deposit service + */ + WxDepositService getWxDepositService(); + /** * 获取批量转账到零钱服务类. * @@ -311,6 +337,13 @@ public interface WxPayService { */ BrandMerchantTransferService getBrandMerchantTransferService(); + /** + * 获取微信支付预约扣费服务类 (连续包月功能) + * + * @return the subscription billing service + */ + SubscriptionBillingService getSubscriptionBillingService(); + /** * 设置企业付款服务类,允许开发者自定义实现类. * @@ -616,10 +649,10 @@ public interface WxPayService { /** * 调用统一下单接口,并组装生成支付所需参数对象. * - * @param 请使用{@link com.github.binarywang.wxpay.bean.result.WxPayUnifiedOrderV3Result}里的内部类或字段 + * @param 请使用{@link WxPayUnifiedOrderV3Result}里的内部类或字段 * @param tradeType the trade type * @param request 统一下单请求参数 - * @return 返回 {@link com.github.binarywang.wxpay.bean.result.WxPayUnifiedOrderV3Result}里的内部类或字段 + * @return 返回 {@link WxPayUnifiedOrderV3Result}里的内部类或字段 * @throws WxPayException the wx pay exception */ T createOrderV3(TradeTypeEnum tradeType, WxPayUnifiedOrderV3Request request) throws WxPayException; @@ -627,14 +660,25 @@ public interface WxPayService { /** * 服务商模式调用统一下单接口,并组装生成支付所需参数对象. * - * @param 请使用{@link com.github.binarywang.wxpay.bean.result.WxPayUnifiedOrderV3Result}里的内部类或字段 + * @param 请使用{@link WxPayUnifiedOrderV3Result}里的内部类或字段 * @param tradeType the trade type * @param request 统一下单请求参数 - * @return 返回 {@link com.github.binarywang.wxpay.bean.result.WxPayUnifiedOrderV3Result}里的内部类或字段 + * @return 返回 {@link WxPayUnifiedOrderV3Result}里的内部类或字段 * @throws WxPayException the wx pay exception */ 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; + /** * 在发起微信支付前,需要调用统一下单接口,获取"预支付交易会话标识" * @@ -655,6 +699,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支付).
@@ -785,6 +839,32 @@ public interface WxPayService {
    */
   WxPayRefundV3Result refundV3(WxPayRefundV3Request request) throws WxPayException;
 
+  /**
+   * 
+   * 微信支付-服务商申请退款.
+   * 应用场景
+   * 当交易发生之后一年内,由于买家或者卖家的原因需要退款时,卖家可以通过退款接口将支付金额退还给买家,微信支付将在收到退款请求并且验证成功之后,将支付款按原路退还至买家账号上。
+   *
+   * 注意:
+   * 1、交易时间超过一年的订单无法提交退款
+   * 2、微信支付退款支持单笔交易分多次退款(不超50次),多次退款需要提交原支付订单的商户订单号和设置不同的退款单号。申请退款总金额不能超过订单金额。 一笔退款失败后重新提交,请不要更换退款单号,请使用原商户退款单号
+   * 3、错误或无效请求频率限制:6qps,即每秒钟异常或错误的退款申请请求不超过6次
+   * 4、每个支付订单的部分退款次数不能超过50次
+   * 5、如果同一个用户有多笔退款,建议分不同批次进行退款,避免并发退款导致退款失败
+   * 6、申请退款接口的返回仅代表业务的受理情况,具体退款是否成功,需要通过退款查询接口获取结果
+   * 7、一个月之前的订单申请退款频率限制为:5000/min
+   *
+   * 详见 https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter4_1_9.shtml
+   * 接口地址
+   * https://api.mch.weixin.qq.com/v3/refund/domestic/refunds
+   * 
+ * + * @param request 请求对象 + * @return 退款操作结果 wx pay refund result + * @throws WxPayException the wx pay exception + */ + WxPayRefundV3Result partnerRefundV3(WxPayPartnerRefundV3Request request) throws WxPayException; + /** *
    * 微信支付-查询退款.
@@ -991,6 +1071,17 @@ WxPayRefundQueryResult refundQuery(String transactionId, String outTradeNo, Stri
    */
   WxPayTransferBatchesNotifyV3Result parseTransferBatchesNotifyV3Result(String notifyData, SignatureHeader header) throws WxPayException;
 
+  /**
+   * 解析商家转账批次回调通知
+   * https://pay.weixin.qq.com/doc/v3/merchant/4012712115
+   *
+   * @param notifyData
+   * @param header
+   * @return
+   * @throws WxPayException
+   */
+  TransferBillsNotifyResult parseTransferBillsNotifyV3Result(String notifyData, SignatureHeader header) throws WxPayException;
+
   /**
    * 解析服务商模式退款结果通知
    * 详见https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter4_1_11.shtml
@@ -1419,6 +1510,7 @@ WxPayRefundQueryResult refundQuery(String transactionId, String outTradeNo, Stri
    * 是否需要证书: 否
    * 请求方式: POST
    * 文档地址:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=23_1
+   * 注意: 微信暂不支持api v3
    * 
* * @return the sandbox sign key @@ -1594,6 +1686,13 @@ WxPayRefundQueryResult refundQuery(String transactionId, String outTradeNo, Stri */ TransferService getTransferService(); + /** + * 获取运营工具-商家转账服务类 + * + * @return the business operation transfer service + */ + BusinessOperationTransferService getBusinessOperationTransferService(); + /** * 获取服务商支付分服务类 * @@ -1603,7 +1702,8 @@ WxPayRefundQueryResult refundQuery(String transactionId, String outTradeNo, Stri /** * 获取服务商直股份签约计划服务类 - * @return the partner pay score sign plan service + * + * @return the partner pay score sign plan service */ PartnerPayScoreSignPlanService getPartnerPayScoreSignPlanService(); } 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 c9fc1e7bd2..ba3dc37144 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,9 +11,10 @@ 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; -import com.github.binarywang.wxpay.constant.WxPayConstants; import com.github.binarywang.wxpay.constant.WxPayConstants.SignType; import com.github.binarywang.wxpay.constant.WxPayConstants.TradeType; import com.github.binarywang.wxpay.exception.WxPayException; @@ -30,11 +31,11 @@ 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 org.apache.http.entity.ContentType; import java.io.File; import java.io.IOException; @@ -45,6 +46,7 @@ import java.nio.file.Paths; import java.security.GeneralSecurityException; import java.util.*; +import java.util.concurrent.ConcurrentHashMap; import java.util.zip.ZipException; import static com.github.binarywang.wxpay.constant.WxPayConstants.QUERY_COMMENT_DATE_FORMAT; @@ -58,14 +60,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 @@ -102,6 +103,9 @@ public abstract class BaseWxPayServiceImpl implements WxPayService { @Getter private final WxEntrustPapService wxEntrustPapService = new WxEntrustPapServiceImpl(this); + @Getter + private final WxDepositService wxDepositService = new WxDepositServiceImpl(this); + @Getter private final PartnerTransferService partnerTransferService = new PartnerTransferServiceImpl(this); @@ -121,7 +125,7 @@ public abstract class BaseWxPayServiceImpl implements WxPayService { private final PartnerPayScoreService partnerPayScoreService = new PartnerPayScoreServiceImpl(this); @Getter - private final PartnerPayScoreSignPlanService partnerPayScoreSignPlanService=new PartnerPayScoreSignPlanServiceImpl(this); + private final PartnerPayScoreSignPlanService partnerPayScoreSignPlanService = new PartnerPayScoreSignPlanServiceImpl(this); @Getter private final MerchantTransferService merchantTransferService = new MerchantTransferServiceImpl(this); @@ -129,7 +133,13 @@ public abstract class BaseWxPayServiceImpl implements WxPayService { @Getter private final BrandMerchantTransferService brandMerchantTransferService = new BrandMerchantTransferServiceImpl(this); - protected Map configMap = new HashMap<>(); + @Getter + private final SubscriptionBillingService subscriptionBillingService = new SubscriptionBillingServiceImpl(this); + + @Getter + private final BusinessOperationTransferService businessOperationTransferService = new BusinessOperationTransferServiceImpl(this); + + protected Map configMap = new ConcurrentHashMap<>(); @Override public WxPayConfig getConfig() { @@ -142,38 +152,37 @@ public WxPayConfig getConfig() { @Override public void setConfig(WxPayConfig config) { - final String defaultMchId = config.getMchId(); - this.setMultiConfig(ImmutableMap.of(defaultMchId, config), defaultMchId); + final String defaultKey = this.getConfigKey(config.getMchId(), config.getAppId()); + this.setMultiConfig(ImmutableMap.of(defaultKey, config), defaultKey); } @Override - public void addConfig(String mchId, WxPayConfig wxPayConfig) { + public void addConfig(String mchId, String appId, WxPayConfig wxPayConfig) { synchronized (this) { if (this.configMap == null) { this.setConfig(wxPayConfig); } else { - WxPayConfigHolder.set(mchId); - this.configMap.put(mchId, wxPayConfig); + String configKey = this.getConfigKey(mchId, appId); + WxPayConfigHolder.set(configKey); + this.configMap.put(configKey, wxPayConfig); } } } @Override - public void removeConfig(String mchId) { + public void removeConfig(String mchId, String appId) { synchronized (this) { - if (this.configMap.size() == 1) { - this.configMap.remove(mchId); - log.warn("已删除最后一个商户号配置:{},须立即使用setConfig或setMultiConfig添加配置", mchId); + String configKey = this.getConfigKey(mchId, appId); + this.configMap.remove(configKey); + if (this.configMap.isEmpty()) { + log.warn("已删除最后一个商户号配置:mchId[{}],appid[{}],须立即使用setConfig或setMultiConfig添加配置", mchId, appId); return; } - if (WxPayConfigHolder.get().equals(mchId)) { - this.configMap.remove(mchId); - final String defaultMpId = this.configMap.keySet().iterator().next(); - WxPayConfigHolder.set(defaultMpId); - log.warn("已删除默认商户号配置,商户号【{}】被设为默认配置", defaultMpId); - return; + if (WxPayConfigHolder.get().equals(configKey)) { + final String nextConfigKey = this.configMap.keySet().iterator().next(); + WxPayConfigHolder.set(nextConfigKey); + log.warn("已删除默认商户号配置,商户号【{}】被设为默认配置", nextConfigKey); } - this.configMap.remove(mchId); } } @@ -183,28 +192,34 @@ public void setMultiConfig(Map wxPayConfigs) { } @Override - public void setMultiConfig(Map wxPayConfigs, String defaultMchId) { + public void setMultiConfig(Map wxPayConfigs, String defaultConfigKey) { this.configMap = Maps.newHashMap(wxPayConfigs); - WxPayConfigHolder.set(defaultMchId); + WxPayConfigHolder.set(defaultConfigKey); } @Override - public boolean switchover(String mchId) { - if (this.configMap.containsKey(mchId)) { - WxPayConfigHolder.set(mchId); + public boolean switchover(String mchId, String appId) { + String configKey = this.getConfigKey(mchId, appId); + if (this.configMap.containsKey(configKey)) { + WxPayConfigHolder.set(configKey); return true; } - log.error("无法找到对应【{}】的商户号配置信息,请核实!", mchId); + log.error("无法找到对应mchId=【{}】,appId=【{}】的商户号配置信息,请核实!", mchId, appId); return false; } @Override - public WxPayService switchoverTo(String mchId) { - if (this.configMap.containsKey(mchId)) { - WxPayConfigHolder.set(mchId); + public WxPayService switchoverTo(String mchId, String appId) { + String configKey = this.getConfigKey(mchId, appId); + if (this.configMap.containsKey(configKey)) { + WxPayConfigHolder.set(configKey); return this; } - throw new WxRuntimeException(String.format("无法找到对应【%s】的商户号配置信息,请核实!", mchId)); + throw new WxRuntimeException(String.format("无法找到对应mchId=【%s】,appId=【%s】的商户号配置信息,请核实!", mchId, appId)); + } + + public String getConfigKey(String mchId, String appId) { + return mchId + "_" + appId; } @Override @@ -213,9 +228,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 @@ -245,7 +260,17 @@ 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); + } + + @Override + public WxPayRefundV3Result partnerRefundV3(WxPayPartnerRefundV3Request request) throws WxPayException { + if (StringUtils.isBlank(request.getSubMchid())) { + request.setSubMchid(this.getConfig().getSubMchId()); + } + String url = String.format("%s/v3/refund/domestic/refunds", this.getPayBaseUrl()); + String response = this.postV3WithWechatpaySerial(url, GSON.toJson(request)); return GSON.fromJson(response, WxPayRefundV3Result.class); } @@ -288,21 +313,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 url = String.format("%s/v3/refund/domestic/refunds/%s?sub_mchid=%s", this.getPayBaseUrl(), request.getOutRefundNo(), request.getSubMchid()); + String response = this.getV3WithWechatPaySerial(url); return GSON.fromJson(response, WxPayRefundQueryV3Result.class); } @@ -317,13 +342,13 @@ public WxPayOrderNotifyResult parseOrderNotifyResult(String xmlData, String sign log.debug("微信支付异步通知请求参数:{}", xmlData); WxPayOrderNotifyResult result = WxPayOrderNotifyResult.fromXML(xmlData); if (signType == null) { + this.switchover(result.getMchId(), result.getAppid()); if (result.getSignType() != null) { // 如果解析的通知对象中signType有值,则使用它进行验签 signType = result.getSignType(); - } else if (configMap.get(result.getMchId()).getSignType() != null) { + } else if (this.getConfig().getSignType() != null) { // 如果配置中signType有值,则使用它进行验签 - signType = configMap.get(result.getMchId()).getSignType(); - this.switchover(result.getMchId()); + signType = this.getConfig().getSignType(); } } @@ -346,7 +371,7 @@ public WxPayOrderNotifyResult parseOrderNotifyResult(String xmlData, String sign */ private boolean verifyNotifySign(SignatureHeader header, String data) throws WxSignTestException { String wxPaySign = header.getSignature(); - if(wxPaySign.startsWith("WECHATPAY/SIGNTEST/")){ + if (wxPaySign.startsWith("WECHATPAY/SIGNTEST/")) { throw new WxSignTestException("微信支付签名探测流量"); } String beforeSign = String.format("%s\n%s\n%s\n", @@ -420,7 +445,7 @@ public WxPayRefundNotifyResult parseRefundNotifyResult(String xmlData) throws Wx WxPayRefundNotifyResult result; if (XmlConfig.fastMode) { result = BaseWxPayResult.fromXML(xmlData, WxPayRefundNotifyResult.class); - this.switchover(result.getMchId()); + this.switchover(result.getMchId(), result.getAppid()); result.decryptReqInfo(this.getConfig().getMchKey()); } else { result = WxPayRefundNotifyResult.fromXML(xmlData, this.getConfig().getMchKey()); @@ -442,6 +467,11 @@ public WxPayTransferBatchesNotifyV3Result parseTransferBatchesNotifyV3Result(Str return this.baseParseOrderNotifyV3Result(notifyData, header, WxPayTransferBatchesNotifyV3Result.class, WxPayTransferBatchesNotifyV3Result.DecryptNotifyResult.class); } + @Override + public TransferBillsNotifyResult parseTransferBillsNotifyV3Result(String notifyData, SignatureHeader header) throws WxPayException { + return this.baseParseOrderNotifyV3Result(notifyData, header, TransferBillsNotifyResult.class, TransferBillsNotifyResult.DecryptNotifyResult.class); + } + @Override public WxPayPartnerRefundNotifyV3Result parsePartnerRefundNotifyV3Result(String notifyData, SignatureHeader header) throws WxPayException { return this.baseParseOrderNotifyV3Result(notifyData, header, WxPayPartnerRefundNotifyV3Result.class, WxPayPartnerRefundNotifyV3Result.DecryptNotifyResult.class); @@ -452,7 +482,7 @@ public WxScanPayNotifyResult parseScanPayNotifyResult(String xmlData, @Deprecate try { log.debug("扫码支付回调通知请求参数:{}", xmlData); WxScanPayNotifyResult result = BaseWxPayResult.fromXML(xmlData, WxScanPayNotifyResult.class); - this.switchover(result.getMchId()); + this.switchover(result.getMchId(), result.getAppid()); log.debug("扫码支付回调通知解析后的对象:{}", result); result.checkResult(this, this.getConfig().getSignType(), false); return result; @@ -512,7 +542,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); } @@ -537,14 +567,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); } @@ -598,7 +628,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 @@ -610,13 +640,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 @@ -737,6 +767,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())) { @@ -760,7 +798,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); } @@ -777,7 +815,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); } @@ -790,14 +850,14 @@ 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); } @Override public T combineTransactions(TradeTypeEnum tradeType, CombineTransactionsRequest request) throws WxPayException { CombineTransactionsResult result = this.combine(tradeType, request); - return result.getPayInfo(tradeType, request.getCombineAppid(), request.getCombineAppid(), this.getConfig().getPrivateKey()); + return result.getPayInfo(tradeType, request.getCombineAppid(), request.getCombineMchid(), this.getConfig().getPrivateKey()); } @Override @@ -1103,7 +1163,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); } @@ -1115,7 +1175,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); } @@ -1144,7 +1204,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); } @@ -1170,7 +1230,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); } @@ -1222,7 +1282,7 @@ public String getSandboxSignKey() throws WxPayException { request.checkAndSign(this.getConfig()); String url = "https://api.mch.weixin.qq.com/xdc/apiv2getsignkey/sign/getsignkey"; - String responseContent = this.post(url, request.toXML(), false); + String responseContent = this.post(url, request.toXML(), false, ContentType.APPLICATION_XML.getMimeType()); WxPaySandboxSignKeyResult result = BaseWxPayResult.fromXML(responseContent, WxPaySandboxSignKeyResult.class); result.checkResult(this, request.getSignType(), true); return result.getSandboxSignKey(); @@ -1300,7 +1360,7 @@ public String queryComment(WxPayQueryCommentRequest request) throws WxPayExcepti @Override public WxPayFaceAuthInfoResult getWxPayFaceAuthInfo(WxPayFaceAuthInfoRequest request) throws WxPayException { if (StringUtils.isEmpty(request.getSignType())) { - request.setSignType(WxPayConstants.SignType.MD5); + request.setSignType(SignType.MD5); } request.checkAndSign(this.getConfig()); @@ -1374,4 +1434,9 @@ public BankService getBankService() { public TransferService getTransferService() { return transferService; } + + @Override + public BusinessOperationTransferService getBusinessOperationTransferService() { + return businessOperationTransferService; + } } 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/BusinessOperationTransferServiceImpl.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/BusinessOperationTransferServiceImpl.java new file mode 100644 index 0000000000..098db127e3 --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/BusinessOperationTransferServiceImpl.java @@ -0,0 +1,74 @@ +package com.github.binarywang.wxpay.service.impl; + +import com.github.binarywang.wxpay.bean.transfer.*; +import com.github.binarywang.wxpay.exception.WxPayException; +import com.github.binarywang.wxpay.service.BusinessOperationTransferService; +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; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; + +import java.security.cert.X509Certificate; + +/** + * 运营工具-商家转账API实现 + * + * @author WxJava Team + * @see 运营工具-商家转账API + */ +@Slf4j +@RequiredArgsConstructor +public class BusinessOperationTransferServiceImpl implements BusinessOperationTransferService { + + private static final Gson GSON = new GsonBuilder().create(); + private final WxPayService wxPayService; + + @Override + public BusinessOperationTransferResult createOperationTransfer(BusinessOperationTransferRequest request) throws WxPayException { + // 设置默认appid + if (StringUtils.isEmpty(request.getAppid())) { + request.setAppid(this.wxPayService.getConfig().getAppId()); + } + + String url = String.format("%s/v3/fund-app/mch-transfer/transfer-bills", this.wxPayService.getPayBaseUrl()); + + // 如果传入了用户姓名,需要进行RSA加密 + if (StringUtils.isNotEmpty(request.getUserName())) { + X509Certificate validCertificate = this.wxPayService.getConfig().getVerifier().getValidCertificate(); + RsaCryptoUtil.encryptFields(request, validCertificate); + } + + String response = wxPayService.postV3WithWechatpaySerial(url, GSON.toJson(request)); + return GSON.fromJson(response, BusinessOperationTransferResult.class); + } + + @Override + public BusinessOperationTransferQueryResult queryOperationTransfer(BusinessOperationTransferQueryRequest request) throws WxPayException { + if (StringUtils.isNotEmpty(request.getOutBillNo())) { + return queryOperationTransferByOutBillNo(request.getOutBillNo()); + } else if (StringUtils.isNotEmpty(request.getTransferBillNo())) { + return queryOperationTransferByTransferBillNo(request.getTransferBillNo()); + } else { + throw new WxPayException("商户单号(out_bill_no)和微信转账单号(transfer_bill_no)必须提供其中一个"); + } + } + + @Override + public BusinessOperationTransferQueryResult queryOperationTransferByOutBillNo(String outBillNo) throws WxPayException { + String url = String.format("%s/v3/fund-app/mch-transfer/transfer-bills/out-bill-no/%s", + this.wxPayService.getPayBaseUrl(), outBillNo); + String response = wxPayService.getV3(url); + return GSON.fromJson(response, BusinessOperationTransferQueryResult.class); + } + + @Override + public BusinessOperationTransferQueryResult queryOperationTransferByTransferBillNo(String transferBillNo) throws WxPayException { + String url = String.format("%s/v3/fund-app/mch-transfer/transfer-bills/transfer-bill-no/%s", + this.wxPayService.getPayBaseUrl(), transferBillNo); + String response = wxPayService.getV3(url); + return GSON.fromJson(response, BusinessOperationTransferQueryResult.class); + } +} \ No newline at end of file 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..171535c992 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); @@ -337,6 +337,27 @@ public RefundNotifyResult parseRefundNotifyResult(String notifyData, SignatureHe } } + @Override + public WithdrawNotifyResult parseWithdrawNotifyResult(String notifyData, SignatureHeader header) throws WxPayException { + if (Objects.nonNull(header) && !this.verifyNotifySign(header, notifyData)) { + throw new WxPayException("非法请求,头部信息验证失败"); + } + NotifyResponse response = GSON.fromJson(notifyData, NotifyResponse.class); + NotifyResponse.Resource resource = response.getResource(); + String cipherText = resource.getCiphertext(); + String associatedData = resource.getAssociatedData(); + String nonce = resource.getNonce(); + String apiV3Key = this.payService.getConfig().getApiV3Key(); + try { + String result = AesUtils.decryptToString(associatedData, nonce, cipherText, apiV3Key); + WithdrawNotifyResult notifyResult = GSON.fromJson(result, WithdrawNotifyResult.class); + notifyResult.setRawData(response); + return notifyResult; + } catch (GeneralSecurityException | IOException e) { + throw new WxPayException("解析报文异常!", e); + } + } + @Override public SubWithdrawResult subWithdraw(SubWithdrawRequest request) throws WxPayException { String url = String.format("%s/v3/ecommerce/fund/withdraw", this.payService.getPayBaseUrl()); @@ -365,6 +386,27 @@ public SpWithdrawStatusResult querySpWithdrawByOutRequestNo(String outRequestNo) return GSON.fromJson(response, SpWithdrawStatusResult.class); } + @Override + public SpWithdrawStatusResult querySpWithdrawByWithdrawId(String withdrawId) throws WxPayException { + String url = String.format("%s/v3/merchant/fund/withdraw/withdraw-id/%s", this.payService.getPayBaseUrl(), withdrawId); + String response = this.payService.getV3(url); + return GSON.fromJson(response, SpWithdrawStatusResult.class); + } + + @Override + public SubDayEndBalanceWithdrawResult subDayEndBalanceWithdraw(SubDayEndBalanceWithdrawRequest request) throws WxPayException { + String url = String.format("%s/v3/ecommerce/fund/balance-withdraw", this.payService.getPayBaseUrl()); + String response = this.payService.postV3(url, GSON.toJson(request)); + return GSON.fromJson(response, SubDayEndBalanceWithdrawResult.class); + } + + @Override + public SubDayEndBalanceWithdrawStatusResult querySubDayEndBalanceWithdraw(String subMchid, String outRequestNo) throws WxPayException { + String url = String.format("%s/v3/ecommerce/fund/balance-withdraw/out-request-no/%s?sub_mchid=%s", this.payService.getPayBaseUrl(), outRequestNo, subMchid); + String response = this.payService.getV3(url); + return GSON.fromJson(response, SubDayEndBalanceWithdrawStatusResult.class); + } + @Override public void modifySettlement(String subMchid, SettlementRequest request) throws WxPayException { String url = String.format("%s/v3/apply4sub/sub_merchants/%s/modify-settlement", this.payService.getPayBaseUrl(), subMchid); @@ -489,7 +531,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/PayScoreServiceImpl.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/PayScoreServiceImpl.java index 249cfa3f56..ee92c6611a 100644 --- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/PayScoreServiceImpl.java +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/PayScoreServiceImpl.java @@ -230,7 +230,7 @@ public WxPayScoreResult modifyServiceOrder(WxPayScoreRequest request) throws WxP if(Strings.isNullOrEmpty(request.getAppid())){ request.setAppid(config.getAppId()); } - if(Strings.isNullOrEmpty(config.getServiceId())){ + if(Strings.isNullOrEmpty(request.getServiceId())){ request.setServiceId(config.getServiceId()); } request.setOutOrderNo(null); 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/SubscriptionBillingServiceImpl.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/SubscriptionBillingServiceImpl.java new file mode 100644 index 0000000000..45c1a9f0d2 --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/SubscriptionBillingServiceImpl.java @@ -0,0 +1,91 @@ +package com.github.binarywang.wxpay.service.impl; + +import com.github.binarywang.wxpay.bean.subscriptionbilling.*; +import com.github.binarywang.wxpay.exception.WxPayException; +import com.github.binarywang.wxpay.service.SubscriptionBillingService; +import com.github.binarywang.wxpay.service.WxPayService; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +/** + * 微信支付-预约扣费服务实现 (连续包月功能) + *
+ *   微信支付预约扣费功能,支持商户在用户授权的情况下,
+ *   按照约定的时间和金额,自动从用户的支付账户中扣取费用。
+ *   主要用于连续包月、订阅服务等场景。
+ *   
+ *   文档详见: https://pay.weixin.qq.com/doc/v3/merchant/4012161105
+ * 
+ * + * @author Binary Wang + * created on 2024-08-31 + */ +@Slf4j +@RequiredArgsConstructor +public class SubscriptionBillingServiceImpl implements SubscriptionBillingService { + + private static final Gson GSON = new GsonBuilder().create(); + private final WxPayService payService; + + @Override + public SubscriptionScheduleResult scheduleSubscription(SubscriptionScheduleRequest request) throws WxPayException { + String url = String.format("%s/v3/subscription-billing/schedule", this.payService.getPayBaseUrl()); + String response = this.payService.postV3(url, GSON.toJson(request)); + return GSON.fromJson(response, SubscriptionScheduleResult.class); + } + + @Override + public SubscriptionQueryResult querySubscription(String subscriptionId) throws WxPayException { + String url = String.format("%s/v3/subscription-billing/schedule/%s", this.payService.getPayBaseUrl(), subscriptionId); + String response = this.payService.getV3(url); + return GSON.fromJson(response, SubscriptionQueryResult.class); + } + + @Override + public SubscriptionCancelResult cancelSubscription(SubscriptionCancelRequest request) throws WxPayException { + String url = String.format("%s/v3/subscription-billing/schedule/%s/cancel", + this.payService.getPayBaseUrl(), request.getSubscriptionId()); + String response = this.payService.postV3(url, GSON.toJson(request)); + return GSON.fromJson(response, SubscriptionCancelResult.class); + } + + @Override + public SubscriptionInstantBillingResult instantBilling(SubscriptionInstantBillingRequest request) throws WxPayException { + String url = String.format("%s/v3/subscription-billing/instant-billing", this.payService.getPayBaseUrl()); + String response = this.payService.postV3(url, GSON.toJson(request)); + return GSON.fromJson(response, SubscriptionInstantBillingResult.class); + } + + @Override + public SubscriptionTransactionQueryResult queryTransactions(SubscriptionTransactionQueryRequest request) throws WxPayException { + String url = String.format("%s/v3/subscription-billing/transactions", this.payService.getPayBaseUrl()); + + StringBuilder queryString = new StringBuilder(); + if (request.getOpenid() != null) { + queryString.append("openid=").append(request.getOpenid()).append("&"); + } + if (request.getBeginTime() != null) { + queryString.append("begin_time=").append(request.getBeginTime()).append("&"); + } + if (request.getEndTime() != null) { + queryString.append("end_time=").append(request.getEndTime()).append("&"); + } + if (request.getLimit() != null) { + queryString.append("limit=").append(request.getLimit()).append("&"); + } + if (request.getOffset() != null) { + queryString.append("offset=").append(request.getOffset()).append("&"); + } + + if (queryString.length() > 0) { + // Remove trailing & + queryString.setLength(queryString.length() - 1); + url += "?" + queryString.toString(); + } + + String response = this.payService.getV3(url); + return GSON.fromJson(response, SubscriptionTransactionQueryResult.class); + } +} \ No newline at end of file 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 e62dc9c053..fe05ab89ad 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 @@ -48,8 +48,7 @@ public QueryTransferBatchesResult transferBatchesBatchId(QueryTransferBatchesReq if (request.getNeedQueryDetail()) { url = String.format("%s/v3/transfer/batches/batch-id/%s?need_query_detail=true&offset=%s&limit=%s&detail_status=%s", this.payService.getPayBaseUrl(), request.getBatchId(), request.getOffset(), request.getLimit(), request.getDetailStatus()); - } - else { + } else { url = String.format("%s/v3/transfer/batches/batch-id/%s?need_query_detail=false", this.payService.getPayBaseUrl(), request.getBatchId()); } @@ -70,8 +69,7 @@ public QueryTransferBatchesResult transferBatchesOutBatchNo(QueryTransferBatches if (request.getNeedQueryDetail()) { url = String.format("%s/v3/transfer/batches/out-batch-no/%s?need_query_detail=true&offset=%s&limit=%s&detail_status=%s", this.payService.getPayBaseUrl(), request.getOutBatchNo(), request.getOffset(), request.getLimit(), request.getDetailStatus()); - } - else { + } else { url = String.format("%s/v3/transfer/batches/out-batch-no/%s?need_query_detail=false", this.payService.getPayBaseUrl(), request.getOutBatchNo()); } @@ -85,4 +83,126 @@ public TransferBatchDetailResult transferBatchesOutBatchNoDetail(String outBatch String result = this.payService.getV3(url); return GSON.fromJson(result, TransferBatchDetailResult.class); } + + @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().isEmpty()) { + X509Certificate validCertificate = this.payService.getConfig().getVerifier().getValidCertificate(); + RsaCryptoUtil.encryptFields(request, validCertificate); + } + String result = this.payService.postV3WithWechatpaySerial(url, GSON.toJson(request)); + return GSON.fromJson(result, TransferBillsResult.class); + } + + @Override + public TransferBillsCancelResult transformBillsCancel(String outBillNo) throws WxPayException { + String url = String.format("%s/v3/fund-app/mch-transfer/transfer-bills/out-bill-no/%s/cancel", + this.payService.getPayBaseUrl(), outBillNo); + String result = this.payService.postV3(url, ""); + + return GSON.fromJson(result, TransferBillsCancelResult.class); + } + + @Override + public TransferBillsGetResult getBillsByOutBillNo(String outBillNo) throws WxPayException { + String url = String.format("%s/v3/fund-app/mch-transfer/transfer-bills/out-bill-no/%s", + this.payService.getPayBaseUrl(), outBillNo); + String result = this.payService.getV3(url); + return GSON.fromJson(result, TransferBillsGetResult.class); + } + + @Override + public TransferBillsGetResult getBillsByTransferBillNo(String transferBillNo) throws WxPayException { + String url = String.format("%s/v3/fund-app/mch-transfer/transfer-bills/transfer-bill-no/%s", + this.payService.getPayBaseUrl(), transferBillNo); + String result = this.payService.getV3(url); + return GSON.fromJson(result, TransferBillsGetResult.class); + } + + @Override + public TransferBillsNotifyResult parseTransferBillsNotifyResult(String notifyData, SignatureHeader header) throws WxPayException { + return this.payService.baseParseOrderNotifyV3Result(notifyData, header, TransferBillsNotifyResult.class, TransferBillsNotifyResult.DecryptNotifyResult.class); + } + + // ===================== 用户授权免确认模式相关接口实现 ===================== + + @Override + public UserAuthorizationStatusResult getUserAuthorizationStatus(String openid, String transferSceneId) throws WxPayException { + String url = String.format("%s/v3/fund-app/mch-transfer/authorization/openid/%s?transfer_scene_id=%s", + this.payService.getPayBaseUrl(), openid, transferSceneId); + String result = this.payService.getV3(url); + return GSON.fromJson(result, UserAuthorizationStatusResult.class); + } + + @Override + public ReservationTransferBatchResult reservationTransferBatch(ReservationTransferBatchRequest request) throws WxPayException { + String url = String.format("%s/v3/fund-app/mch-transfer/reservation/transfer-batches", this.payService.getPayBaseUrl()); + List transferDetailList = request.getTransferDetailList(); + if (transferDetailList != null && !transferDetailList.isEmpty()) { + X509Certificate validCertificate = this.payService.getConfig().getVerifier().getValidCertificate(); + for (ReservationTransferBatchRequest.TransferDetail detail : transferDetailList) { + if (detail.getUserName() != null && !detail.getUserName().isEmpty()) { + RsaCryptoUtil.encryptFields(detail, validCertificate); + } + } + } + String result = this.payService.postV3WithWechatpaySerial(url, GSON.toJson(request)); + return GSON.fromJson(result, ReservationTransferBatchResult.class); + } + + @Override + public ReservationTransferBatchGetResult getReservationTransferBatchByOutBatchNo(String outBatchNo, Boolean needQueryDetail, + Integer offset, Integer limit, String detailState) throws WxPayException { + String url = buildReservationBatchQueryUrl("out-batch-no", outBatchNo, needQueryDetail, offset, limit, detailState); + String result = this.payService.getV3(url); + return GSON.fromJson(result, ReservationTransferBatchGetResult.class); + } + + @Override + public ReservationTransferBatchGetResult getReservationTransferBatchByReservationBatchNo(String reservationBatchNo, Boolean needQueryDetail, + Integer offset, Integer limit, String detailState) throws WxPayException { + String url = buildReservationBatchQueryUrl("reservation-batch-no", reservationBatchNo, needQueryDetail, offset, limit, detailState); + String result = this.payService.getV3(url); + return GSON.fromJson(result, ReservationTransferBatchGetResult.class); + } + + private String buildReservationBatchQueryUrl(String batchNoType, String batchNo, Boolean needQueryDetail, + Integer offset, Integer limit, String detailState) { + StringBuilder url = new StringBuilder(); + url.append(this.payService.getPayBaseUrl()) + .append("/v3/fund-app/mch-transfer/reservation/transfer-batches/") + .append(batchNoType).append("/").append(batchNo); + + boolean hasParams = false; + if (needQueryDetail != null) { + url.append("?need_query_detail=").append(needQueryDetail); + hasParams = true; + } + if (offset != null) { + url.append(hasParams ? "&" : "?").append("offset=").append(offset); + hasParams = true; + } + if (limit != null) { + url.append(hasParams ? "&" : "?").append("limit=").append(limit); + hasParams = true; + } + if (detailState != null && !detailState.isEmpty()) { + url.append(hasParams ? "&" : "?").append("detail_state=").append(detailState); + } + return url.toString(); + } + + @Override + public ReservationTransferNotifyResult parseReservationTransferNotifyResult(String notifyData, SignatureHeader header) throws WxPayException { + return this.payService.baseParseOrderNotifyV3Result(notifyData, header, ReservationTransferNotifyResult.class, + ReservationTransferNotifyResult.DecryptNotifyResult.class); + } + + @Override + public void closeReservationTransferBatch(String outBatchNo) throws WxPayException { + String url = String.format("%s/v3/fund-app/mch-transfer/reservation/transfer-batches/out-batch-no/%s/close", + this.payService.getPayBaseUrl(), outBatchNo); + this.payService.postV3(url, ""); + } } diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/WxDepositServiceImpl.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/WxDepositServiceImpl.java new file mode 100644 index 0000000000..70bc5a5162 --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/WxDepositServiceImpl.java @@ -0,0 +1,84 @@ +package com.github.binarywang.wxpay.service.impl; + +import com.github.binarywang.wxpay.bean.request.*; +import com.github.binarywang.wxpay.bean.result.*; +import com.github.binarywang.wxpay.exception.WxPayException; +import com.github.binarywang.wxpay.service.WxDepositService; +import com.github.binarywang.wxpay.service.WxPayService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +/** + *
+ *   微信押金支付服务实现
+ * 
+ * + * @author Binary Wang + * created on 2024-09-24 + */ +@Slf4j +@RequiredArgsConstructor +public class WxDepositServiceImpl implements WxDepositService { + + private final WxPayService payService; + + @Override + public WxDepositUnifiedOrderResult unifiedOrder(WxDepositUnifiedOrderRequest request) throws WxPayException { + request.checkAndSign(payService.getConfig()); + + String url = payService.getPayBaseUrl() + "/pay/depositpay"; + String responseContent = payService.post(url, request.toXML(), false); + WxDepositUnifiedOrderResult result = BaseWxPayResult.fromXML(responseContent, WxDepositUnifiedOrderResult.class); + result.checkResult(payService, request.getSignType(), true); + + return result; + } + + @Override + public WxDepositOrderQueryResult queryOrder(WxDepositOrderQueryRequest request) throws WxPayException { + request.checkAndSign(payService.getConfig()); + + String url = payService.getPayBaseUrl() + "/pay/depositorderquery"; + String responseContent = payService.post(url, request.toXML(), false); + WxDepositOrderQueryResult result = BaseWxPayResult.fromXML(responseContent, WxDepositOrderQueryResult.class); + result.checkResult(payService, request.getSignType(), true); + + return result; + } + + @Override + public WxDepositConsumeResult consume(WxDepositConsumeRequest request) throws WxPayException { + request.checkAndSign(payService.getConfig()); + + String url = payService.getPayBaseUrl() + "/pay/depositconsume"; + String responseContent = payService.post(url, request.toXML(), false); + WxDepositConsumeResult result = BaseWxPayResult.fromXML(responseContent, WxDepositConsumeResult.class); + result.checkResult(payService, request.getSignType(), true); + + return result; + } + + @Override + public WxDepositUnfreezeResult unfreeze(WxDepositUnfreezeRequest request) throws WxPayException { + request.checkAndSign(payService.getConfig()); + + String url = payService.getPayBaseUrl() + "/pay/depositreverse"; + String responseContent = payService.post(url, request.toXML(), false); + WxDepositUnfreezeResult result = BaseWxPayResult.fromXML(responseContent, WxDepositUnfreezeResult.class); + result.checkResult(payService, request.getSignType(), true); + + return result; + } + + @Override + public WxDepositRefundResult refund(WxDepositRefundRequest request) throws WxPayException { + request.checkAndSign(payService.getConfig()); + + String url = payService.getPayBaseUrl() + "/pay/depositrefund"; + String responseContent = payService.post(url, request.toXML(), true); + WxDepositRefundResult result = BaseWxPayResult.fromXML(responseContent, WxDepositRefundResult.class); + result.checkResult(payService, request.getSignType(), true); + + return result; + } +} \ No newline at end of file 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 e2b6d43fa1..96454e5c08 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 @@ -1,10 +1,13 @@ 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.*; @@ -38,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); } @@ -70,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())); } @@ -94,53 +95,41 @@ 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)) { - //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); + public String post(String url, String requestStr, boolean useKey, String mimeType) throws WxPayException { + try { + HttpPost httpPost = this.createHttpPost(url, requestStr, mimeType); + 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(); } - - //有错误提示信息返回 - 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 { - httpPost.releaseConnection(); + 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 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()); + public String postV3(String url, String requestStr) throws WxPayException { + HttpPost httpPost = this.createHttpPost(url, requestStr); + this.configureRequest(httpPost); + return this.requestV3(url, requestStr, httpPost); + } - httpPatch.addHeader(ACCEPT, APPLICATION_JSON); - httpPatch.addHeader(CONTENT_TYPE, APPLICATION_JSON); - try (CloseableHttpResponse response = httpClient.execute(httpPatch)) { + 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方法有可能会没有返回值的情况 @@ -150,7 +139,7 @@ public String patchV3(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; } @@ -158,21 +147,25 @@ public String patchV3(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 { - httpPatch.releaseConnection(); + 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 { - CloseableHttpClient httpClient = this.createApiV3HttpClient(); HttpPost httpPost = this.createHttpPost(url, requestStr); - httpPost.addHeader(ACCEPT, APPLICATION_JSON); - httpPost.addHeader(CONTENT_TYPE, APPLICATION_JSON); - String serialNumber = getConfig().getVerifier().getValidCertificate().getSerialNumber().toString(16).toUpperCase(); - 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(); @@ -183,7 +176,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; } @@ -191,8 +184,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(); @@ -206,12 +198,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 成功 @@ -223,7 +210,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; } @@ -231,7 +218,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(); @@ -240,27 +227,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 = getConfig().getVerifier().getValidCertificate().getSerialNumber().toString(16).toUpperCase(); - 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(); @@ -268,7 +252,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(); } @@ -278,7 +262,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(); @@ -288,21 +272,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) { @@ -311,11 +307,35 @@ 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)); } + private static StringEntity createEntry(String requestStr, String mimeType) { + return new StringEntity(requestStr, ContentType.create(mimeType, 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) { @@ -347,7 +367,20 @@ 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()) + .setConnectTimeout(this.getConfig().getHttpConnectionTimeout()) + .setSocketTimeout(this.getConfig().getHttpTimeout()) + .build()); + + return httpPost; + } + + private HttpPost createHttpPost(String url, String requestStr, String mimeType) throws WxPayException { + HttpPost httpPost = new HttpPost(url); + httpPost.setEntity(createEntry(requestStr, mimeType)); httpPost.setConfig(RequestConfig.custom() .setConnectionRequestTimeout(this.getConfig().getHttpConnectionTimeout()) @@ -368,9 +401,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(); @@ -380,4 +412,22 @@ private WxPayException convertException(JsonObject jsonObject) { 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/WxPayServiceHttpComponentsImpl.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/WxPayServiceHttpComponentsImpl.java new file mode 100644 index 0000000000..5c21a06a8e --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/WxPayServiceHttpComponentsImpl.java @@ -0,0 +1,411 @@ +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 post(String url, String requestStr, boolean useKey, String mimeType) throws WxPayException { + try { + HttpClientBuilder httpClientBuilder = this.createHttpClientBuilder(useKey); + HttpPost httpPost = this.createHttpPost(url, requestStr, mimeType); + 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 static StringEntity createEntry(String requestStr, String mimeType) { + return new StringEntity(requestStr, ContentType.create(mimeType, 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 HttpPost createHttpPost(String url, String requestStr, String mimeType) throws WxPayException { + HttpPost httpPost = new HttpPost(url); + httpPost.setEntity(createEntry(requestStr, mimeType)); + + 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..6c8bcec899 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,31 @@ 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); + } + } + + @Override + public String post(String url, String requestStr, boolean useKey, String mimeType) throws WxPayException { + try { + HttpRequest request = this.buildHttpRequest(url, requestStr, useKey, mimeType); + String responseString = this.getResponseString(request.send()); + + 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) { + log.error("\n【请求地址】:{}\n【请求数据】:{}\n【异常信息】:{}", url, requestStr, e.getMessage()); wxApiData.set(new WxPayApiData(url, requestStr, null, e.getMessage())); throw new WxPayException(e.getMessage(), e); } @@ -144,11 +164,45 @@ private HttpRequest buildHttpRequest(String url, String requestStr, boolean useK return request; } + private HttpRequest buildHttpRequest(String url, String requestStr, boolean useKey, String mimeType) throws WxPayException { + HttpRequest request = HttpRequest + .post(url) + .contentType(mimeType) + .timeout(this.getConfig().getHttpTimeout()) + .connectionTimeout(this.getConfig().getHttpConnectionTimeout()) + .bodyText(requestStr); + + if (useKey) { + SSLContext sslContext = this.getConfig().getSslContext(); + if (null == sslContext) { + sslContext = this.getConfig().initSSLContext(); + } + final SSLSocketHttpConnectionProvider provider = new SSLSocketHttpConnectionProvider(sslContext); + request.withConnectionProvider(provider); + } + + if (StringUtils.isNotBlank(this.getConfig().getHttpProxyHost()) && this.getConfig().getHttpProxyPort() > 0) { + if (StringUtils.isEmpty(this.getConfig().getHttpProxyUsername())) { + this.getConfig().setHttpProxyUsername("whatever"); + } + + ProxyInfo httpProxy = new ProxyInfo(ProxyType.HTTP, this.getConfig().getHttpProxyHost(), this.getConfig().getHttpProxyPort(), + this.getConfig().getHttpProxyUsername(), this.getConfig().getHttpProxyPassword()); + HttpConnectionProvider provider = request.connectionProvider(); + if (null == provider) { + provider = new SocketHttpConnectionProvider(); + } + provider.useProxy(httpProxy); + request.withConnectionProvider(provider); + } + return request; + } + 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..62ad61ce19 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,23 @@ 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) { + // 如果序列号不为空且不包含"PUB_KEY_ID"且有证书验证器,先尝试证书验证 + if (serialNumber != null && !serialNumber.contains("PUB_KEY_ID") && this.certificateVerifier != null) { + try { + if (this.certificateVerifier.verify(serialNumber, message, signature)) { + return true; + } + } catch (Exception e) { + // 证书验证失败,继续尝试公钥验证 + } + } + // 使用公钥验证(兜底方案,适用于公钥转账等场景) 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/main/java/com/github/binarywang/wxpay/v3/util/SignUtils.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/v3/util/SignUtils.java index 7065d06383..37a1ef2efd 100644 --- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/v3/util/SignUtils.java +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/v3/util/SignUtils.java @@ -33,6 +33,19 @@ public static String genRandomStr() { return genRandomStr(32); } + private static volatile Random random; + + private static Random getRandom() { + if (random == null) { + synchronized (SignUtils.class) { + if (random == null) { + random = new Random(); + } + } + } + return random; + } + /** * 生成随机字符串 * @@ -41,10 +54,10 @@ public static String genRandomStr() { */ public static String genRandomStr(int length) { String base = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; - Random random = new Random(); + Random r = getRandom(); StringBuilder sb = new StringBuilder(); for (int i = 0; i < length; i++) { - int number = random.nextInt(base.length()); + int number = r.nextInt(base.length()); sb.append(base.charAt(number)); } return sb.toString(); 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/WxPayOrderNotifyUnknownFieldTest.java b/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/bean/notify/WxPayOrderNotifyUnknownFieldTest.java new file mode 100644 index 0000000000..be35523ec4 --- /dev/null +++ b/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/bean/notify/WxPayOrderNotifyUnknownFieldTest.java @@ -0,0 +1,104 @@ +package com.github.binarywang.wxpay.bean.notify; + +import com.github.binarywang.wxpay.constant.WxPayConstants; +import org.apache.commons.codec.digest.DigestUtils; +import org.testng.Assert; +import org.testng.annotations.Test; + +import java.util.*; + +/** + * 测试当微信支付回调 XML 包含未在 Java Bean 中定义的字段时,签名验证是否正常。 + *

+ * 问题背景:当微信返回的 XML 包含某些未在 WxPayOrderNotifyResult 中定义的字段时, + * 这些字段会被微信服务器用于签名计算。如果 toMap() 方法丢失了这些字段, + * 则签名验证会失败,抛出 "参数格式校验错误!" 异常。 + *

+ *

+ * 解决方案:修改 WxPayOrderNotifyResult.toMap() 方法,使用父类的 toMap() 方法 + * 直接从原始 XML 解析所有字段,而不是使用 SignUtils.xmlBean2Map(this)。 + *

+ * + * @see Issue #3750 + */ +public class WxPayOrderNotifyUnknownFieldTest { + + private static final String MCH_KEY = "testmchkey1234567890123456789012"; + private static final List NO_SIGN_PARAMS = Arrays.asList("sign", "key", "xmlString", "xmlDoc", "couponList"); + + @Test + public void testSignatureWithUnknownField() throws Exception { + // 创建一个测试用的 XML,包含一个未知字段 (未在 WxPayOrderNotifyResult 中定义) + Map params = new LinkedHashMap<>(); + params.put("appid", "wx58ff40508696691f"); + params.put("bank_type", "ICBC_DEBIT"); + params.put("cash_fee", "1"); + params.put("fee_type", "CNY"); + params.put("is_subscribe", "N"); + params.put("mch_id", "1545462911"); + params.put("nonce_str", "1761723102373"); + params.put("openid", "o1gdd16CZCi6yYvkn6j9EB_1TObM"); + params.put("out_trade_no", "20251029153140"); + params.put("result_code", "SUCCESS"); + params.put("return_code", "SUCCESS"); + params.put("time_end", "20251029153852"); + params.put("total_fee", "1"); + params.put("trade_type", "JSAPI"); + params.put("transaction_id", "4200002882220251029816273963B"); + // 添加一个未知字段 + params.put("unknown_field", "unknown_value"); + + // 计算正确的签名 (包含未知字段) + String correctSign = createSign(params, WxPayConstants.SignType.MD5, MCH_KEY); + params.put("sign", correctSign); + + // 创建 XML + StringBuilder xmlBuilder = new StringBuilder(""); + for (Map.Entry entry : params.entrySet()) { + xmlBuilder.append("<").append(entry.getKey()).append(">") + .append(entry.getValue()) + .append(""); + } + xmlBuilder.append(""); + String xml = xmlBuilder.toString(); + + System.out.println("测试 XML (包含未知字段 unknown_field):"); + System.out.println(xml); + System.out.println("正确的签名 (包含未知字段计算): " + correctSign); + + // 解析 XML + WxPayOrderNotifyResult result = WxPayOrderNotifyResult.fromXML(xml); + Map beanMap = result.toMap(); + + System.out.println("\ntoMap() 结果:"); + TreeMap sortedMap = new TreeMap<>(beanMap); + for (Map.Entry entry : sortedMap.entrySet()) { + System.out.println(" " + entry.getKey() + " = " + entry.getValue()); + } + + // 检查 unknown_field 是否存在 + boolean hasUnknownField = beanMap.containsKey("unknown_field"); + System.out.println("\ntoMap() 是否包含 unknown_field: " + hasUnknownField); + + // 验证签名 + String verifySign = createSign(beanMap, WxPayConstants.SignType.MD5, MCH_KEY); + System.out.println("原始签名: " + result.getSign()); + System.out.println("计算签名: " + verifySign); + + // 这个测试验证修复后 toMap() 能正确包含所有字段 + Assert.assertTrue(hasUnknownField, "toMap() 应该包含 unknown_field"); + Assert.assertEquals(verifySign, result.getSign(), "签名应该匹配"); + } + + private static String createSign(Map params, String signType, String signKey) { + StringBuilder toSign = new StringBuilder(); + for (String key : new TreeMap<>(params).keySet()) { + String value = params.get(key); + if (value != null && !value.isEmpty() && !NO_SIGN_PARAMS.contains(key)) { + toSign.append(key).append("=").append(value).append("&"); + } + } + toSign.append("key=").append(signKey); + return DigestUtils.md5Hex(toSign.toString()).toUpperCase(); + } +} 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/config/WxPayConfigPrivateKeyTest.java b/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/config/WxPayConfigPrivateKeyTest.java new file mode 100644 index 0000000000..927e0c4125 --- /dev/null +++ b/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/config/WxPayConfigPrivateKeyTest.java @@ -0,0 +1,116 @@ +package com.github.binarywang.wxpay.config; + +import com.github.binarywang.wxpay.exception.WxPayException; +import org.testng.annotations.Test; + +import static org.testng.Assert.*; + +/** + * Test cases for private key format handling in WxPayConfig + */ +public class WxPayConfigPrivateKeyTest { + + @Test + public void testPrivateKeyStringFormat_PemFormat() { + WxPayConfig config = new WxPayConfig(); + + // Set minimal required configuration + config.setMchId("1234567890"); + config.setApiV3Key("test-api-v3-key-32-characters-long"); + config.setCertSerialNo("test-serial-number"); + + // Test with PEM format private key string that would previously fail + String pemKey = "-----BEGIN PRIVATE KEY-----\n" + + "MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC2pK3buBufh8Vo\n" + + "X4sfYbZ5CcPeGMnVQTGmj0b6\n" + + "-----END PRIVATE KEY-----"; + + config.setPrivateKeyString(pemKey); + + // This should not throw a "无效的密钥格式" exception immediately + // The actual key validation will happen during HTTP client initialization + // but at least the format parsing should not fail + + try { + // Try to initialize API V3 HTTP client - this might fail for other reasons + // (like invalid key content) but should not fail due to format parsing + config.initApiV3HttpClient(); + // If we get here without InvalidKeySpecException, the format detection worked + } catch (WxPayException e) { + // Check that it's not the specific "无效的密钥格式" error from PemUtils + if (e.getCause() != null && + e.getCause().getMessage() != null && + e.getCause().getMessage().contains("无效的密钥格式")) { + fail("Private key format detection failed - PEM format was not handled correctly: " + e.getMessage()); + } + // Other exceptions are acceptable for this test since we're using a dummy key + } catch (Exception e) { + // Check for the specific InvalidKeySpecException that indicates format problems + if (e.getCause() != null && + e.getCause().getMessage() != null && + e.getCause().getMessage().contains("无效的密钥格式")) { + fail("Private key format detection failed - PEM format was not handled correctly: " + e.getMessage()); + } + // Other exceptions are acceptable for this test since we're using a dummy key + } + } + + @Test + public void testPrivateKeyStringFormat_EmptyString() { + WxPayConfig config = new WxPayConfig(); + + // Test with empty string - should not cause format errors + config.setPrivateKeyString(""); + + // This should handle empty strings gracefully + // No assertion needed, just ensuring no exceptions during object creation + assertNotNull(config); + } + + @Test + public void testPrivateKeyStringFormat_NullString() { + WxPayConfig config = new WxPayConfig(); + + // Test with null string - should not cause format errors + config.setPrivateKeyString(null); + + // This should handle null strings gracefully + assertNotNull(config); + } + + @Test + public void testPrivateCertStringFormat_PemFormat() { + WxPayConfig config = new WxPayConfig(); + + // Set minimal required configuration + config.setMchId("1234567890"); + config.setApiV3Key("test-api-v3-key-32-characters-long"); + + // Test with PEM format certificate string that would previously fail + String pemCert = "-----BEGIN CERTIFICATE-----\n" + + "MIICdTCCAd4CAQAwDQYJKoZIhvcNAQEFBQAwRTELMAkGA1UEBhMCQVUxEzARBgNV\n" + + "BAsKClRlc3QgQ2VydCBEYXRhMRswGQYDVQQDDBJUZXN0IENlcnRpZmljYXRlQ0Ew\n" + + "-----END CERTIFICATE-----"; + + config.setPrivateCertString(pemCert); + + // This should not throw a format parsing exception immediately + // The actual certificate validation will happen during HTTP client initialization + // but at least the format parsing should not fail + + try { + // Try to initialize API V3 HTTP client - this might fail for other reasons + // (like invalid cert content) but should not fail due to format parsing + config.initApiV3HttpClient(); + // If we get here without Base64 decoding issues, the format detection worked + } catch (Exception e) { + // Check that it's not the specific Base64 decoding error + if (e.getCause() != null && + e.getCause().getMessage() != null && + e.getCause().getMessage().contains("Illegal base64 character")) { + fail("Certificate format detection failed - PEM format was not handled correctly: " + e.getMessage()); + } + // Other exceptions are acceptable for this test since we're using a dummy cert + } + } +} \ No newline at end of file diff --git a/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/service/BusinessOperationTransferServiceTest.java b/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/service/BusinessOperationTransferServiceTest.java new file mode 100644 index 0000000000..4107be4347 --- /dev/null +++ b/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/service/BusinessOperationTransferServiceTest.java @@ -0,0 +1,93 @@ +package com.github.binarywang.wxpay.service; + +import com.github.binarywang.wxpay.bean.transfer.*; +import com.github.binarywang.wxpay.config.WxPayConfig; +import com.github.binarywang.wxpay.constant.WxPayConstants; +import com.github.binarywang.wxpay.service.impl.WxPayServiceImpl; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * 运营工具-商家转账API测试 + * + * @author WxJava Team + */ +public class BusinessOperationTransferServiceTest { + + private WxPayService wxPayService; + + @BeforeClass + public void setup() { + WxPayConfig config = new WxPayConfig(); + config.setAppId("test_app_id"); + config.setMchId("test_mch_id"); + + wxPayService = new WxPayServiceImpl(); + wxPayService.setConfig(config); + } + + @Test + public void testServiceInitialization() { + BusinessOperationTransferService service = this.wxPayService.getBusinessOperationTransferService(); + assertThat(service).isNotNull(); + } + + @Test + public void testRequestBuilder() { + BusinessOperationTransferRequest request = BusinessOperationTransferRequest.newBuilder() + .appid("test_app_id") + .outBillNo("OT" + System.currentTimeMillis()) + .operationSceneId(WxPayConstants.OperationSceneId.OPERATION_CASH_MARKETING) + .openid("test_openid") + .transferAmount(100) + .transferRemark("测试转账") + .userRecvPerception(WxPayConstants.UserRecvPerception.CASH_MARKETING.CASH) + .build(); + + assertThat(request.getAppid()).isEqualTo("test_app_id"); + assertThat(request.getOperationSceneId()).isEqualTo(WxPayConstants.OperationSceneId.OPERATION_CASH_MARKETING); + assertThat(request.getTransferAmount()).isEqualTo(100); + assertThat(request.getTransferRemark()).isEqualTo("测试转账"); + } + + @Test + public void testQueryRequestBuilder() { + BusinessOperationTransferQueryRequest request = BusinessOperationTransferQueryRequest.newBuilder() + .outBillNo("OT123456789") + .appid("test_app_id") + .build(); + + assertThat(request.getOutBillNo()).isEqualTo("OT123456789"); + assertThat(request.getAppid()).isEqualTo("test_app_id"); + } + + @Test + public void testConstants() { + // 测试运营工具转账场景ID常量 + assertThat(WxPayConstants.OperationSceneId.OPERATION_CASH_MARKETING).isEqualTo("2001"); + assertThat(WxPayConstants.OperationSceneId.OPERATION_COMMISSION).isEqualTo("2002"); + assertThat(WxPayConstants.OperationSceneId.OPERATION_PROMOTION).isEqualTo("2003"); + } + + @Test + public void testResultClasses() { + // 测试结果类的基本功能 + BusinessOperationTransferResult result = new BusinessOperationTransferResult(); + result.setOutBillNo("test_out_bill_no"); + result.setTransferBillNo("test_transfer_bill_no"); + result.setTransferState("SUCCESS"); + + assertThat(result.getOutBillNo()).isEqualTo("test_out_bill_no"); + assertThat(result.getTransferBillNo()).isEqualTo("test_transfer_bill_no"); + assertThat(result.getTransferState()).isEqualTo("SUCCESS"); + + BusinessOperationTransferQueryResult queryResult = new BusinessOperationTransferQueryResult(); + queryResult.setOperationSceneId("2001"); + queryResult.setTransferAmount(100); + + assertThat(queryResult.getOperationSceneId()).isEqualTo("2001"); + assertThat(queryResult.getTransferAmount()).isEqualTo(100); + } +} \ 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..bd24f188d0 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 @@ -33,6 +33,7 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.math.BigDecimal; +import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.util.Calendar; @@ -936,7 +937,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); @@ -976,4 +977,44 @@ public void testCreatePartnerOrderV3() throws WxPayException { WxPayUnifiedOrderV3Result.JsapiResult result = payService.createPartnerOrderV3(TradeTypeEnum.JSAPI, request); System.out.println(result); } + + @Test + public void test_certSerialNoExtractedFromPrivateCertContentOrPrivateCertString() throws Exception { + WxPayConfig wxPayConfig = new WxPayConfig(); + //服务商的参数 + wxPayConfig.setMchId("xxx"); + wxPayConfig.setAppId("xxx"); + wxPayConfig.setApiV3Key("xxx"); + wxPayConfig.setPrivateKeyContent("xxx".getBytes(StandardCharsets.UTF_8)); + wxPayConfig.setPrivateCertContent("xxx".getBytes(StandardCharsets.UTF_8) + ); + wxPayConfig.setPublicKeyId("xxx"); + wxPayConfig.setPublicKeyContent("xxx".getBytes(StandardCharsets.UTF_8)); + //创建支付服务 + WxPayService wxPayService = new WxPayServiceImpl(); + wxPayService.setConfig(wxPayConfig); + + String outTradeNo = RandomUtils.getRandomStr(); + String notifyUrl = "https://api.qq.com/"; + System.out.println("outTradeNo = " + outTradeNo); + WxPayUnifiedOrderV3Request request = new WxPayUnifiedOrderV3Request(); + request.setOutTradeNo(outTradeNo); + request.setNotifyUrl(notifyUrl); + request.setDescription("test"); + + WxPayUnifiedOrderV3Request.Payer payer = new WxPayUnifiedOrderV3Request.Payer(); + payer.setOpenid("xxx"); + request.setPayer(payer); + + //构建金额信息 + WxPayUnifiedOrderV3Request.Amount amount = new WxPayUnifiedOrderV3Request.Amount(); + //设置币种信息 + amount.setCurrency(WxPayConstants.CurrencyType.CNY); + //设置金额 + amount.setTotal(BaseWxPayRequest.yuan2Fen(BigDecimal.ONE)); + request.setAmount(amount); + + wxPayService.createOrderV3(TradeTypeEnum.JSAPI, 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/SubscriptionBillingServiceImplTest.java b/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/service/impl/SubscriptionBillingServiceImplTest.java new file mode 100644 index 0000000000..21143a47d1 --- /dev/null +++ b/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/service/impl/SubscriptionBillingServiceImplTest.java @@ -0,0 +1,144 @@ +package com.github.binarywang.wxpay.service.impl; + +import com.github.binarywang.wxpay.bean.subscriptionbilling.*; +import com.github.binarywang.wxpay.service.SubscriptionBillingService; +import com.github.binarywang.wxpay.service.WxPayService; +import com.github.binarywang.wxpay.testbase.ApiTestModule; +import com.google.inject.Inject; +import org.testng.annotations.Guice; +import org.testng.annotations.Test; + +/** + * 微信支付预约扣费服务测试类 + *

+ * 注意:由于预约扣费功能需要用户授权和实际的签约关系, + * 这些测试主要用于验证接口调用的正确性,而不是功能的完整性。 + * 实际测试需要在具有有效签约关系的环境中进行。 + *

+ * + * @author Binary Wang + */ +@Test(enabled = false) // 默认关闭,需要实际环境配置才能测试 +@Guice(modules = ApiTestModule.class) +public class SubscriptionBillingServiceImplTest { + + @Inject + private WxPayService wxPayService; + + @Test + public void testScheduleSubscription() { + try { + SubscriptionBillingService service = this.wxPayService.getSubscriptionBillingService(); + + SubscriptionScheduleRequest request = new SubscriptionScheduleRequest(); + request.setOutTradeNo("test_subscription_" + System.currentTimeMillis()); + request.setOpenid("test_openid"); + request.setDescription("测试预约扣费"); + request.setScheduleTime("2024-09-01T10:00:00+08:00"); + + SubscriptionAmount amount = new SubscriptionAmount(); + amount.setTotal(100); // 1元,单位分 + amount.setCurrency("CNY"); + request.setAmount(amount); + + BillingPlan billingPlan = new BillingPlan(); + billingPlan.setPlanType("MONTHLY"); + billingPlan.setPeriod(1); + billingPlan.setTotalCount(12); + request.setBillingPlan(billingPlan); + + SubscriptionScheduleResult result = service.scheduleSubscription(request); + + System.out.println("预约扣费结果:" + result.toString()); + assert result.getSubscriptionId() != null; + assert "SCHEDULED".equals(result.getStatus()); + + } catch (Exception e) { + // 预期会因为测试环境没有有效的签约关系而失败 + System.out.println("预约扣费测试异常(预期):" + e.getMessage()); + } + } + + @Test + public void testQuerySubscription() { + try { + SubscriptionBillingService service = this.wxPayService.getSubscriptionBillingService(); + SubscriptionQueryResult result = service.querySubscription("test_subscription_id"); + + System.out.println("查询预约扣费结果:" + result.toString()); + + } catch (Exception e) { + // 预期会因为测试数据不存在而失败 + System.out.println("查询预约扣费测试异常(预期):" + e.getMessage()); + } + } + + @Test + public void testCancelSubscription() { + try { + SubscriptionBillingService service = this.wxPayService.getSubscriptionBillingService(); + + SubscriptionCancelRequest request = new SubscriptionCancelRequest(); + request.setSubscriptionId("test_subscription_id"); + request.setCancelReason("测试取消"); + + SubscriptionCancelResult result = service.cancelSubscription(request); + + System.out.println("取消预约扣费结果:" + result.toString()); + assert "CANCELLED".equals(result.getStatus()); + + } catch (Exception e) { + // 预期会因为测试数据不存在而失败 + System.out.println("取消预约扣费测试异常(预期):" + e.getMessage()); + } + } + + @Test + public void testInstantBilling() { + try { + SubscriptionBillingService service = this.wxPayService.getSubscriptionBillingService(); + + SubscriptionInstantBillingRequest request = new SubscriptionInstantBillingRequest(); + request.setOutTradeNo("test_instant_" + System.currentTimeMillis()); + request.setOpenid("test_openid"); + request.setDescription("测试立即扣费"); + + SubscriptionAmount amount = new SubscriptionAmount(); + amount.setTotal(100); // 1元,单位分 + amount.setCurrency("CNY"); + request.setAmount(amount); + + SubscriptionInstantBillingResult result = service.instantBilling(request); + + System.out.println("立即扣费结果:" + result.toString()); + assert result.getTransactionId() != null; + + } catch (Exception e) { + // 预期会因为测试环境没有有效的签约关系而失败 + System.out.println("立即扣费测试异常(预期):" + e.getMessage()); + } + } + + @Test + public void testQueryTransactions() { + try { + SubscriptionBillingService service = this.wxPayService.getSubscriptionBillingService(); + + SubscriptionTransactionQueryRequest request = new SubscriptionTransactionQueryRequest(); + request.setOpenid("test_openid"); + request.setBeginTime("2024-08-01T00:00:00+08:00"); + request.setEndTime("2024-08-31T23:59:59+08:00"); + request.setLimit(20); + request.setOffset(0); + + SubscriptionTransactionQueryResult result = service.queryTransactions(request); + + System.out.println("查询扣费记录结果:" + result.toString()); + assert result.getTotalCount() != null; + + } catch (Exception e) { + // 预期会因为测试环境数据问题而失败 + System.out.println("查询扣费记录测试异常(预期):" + e.getMessage()); + } + } +} \ No newline at end of file diff --git a/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/service/impl/TransferServiceImplTest.java b/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/service/impl/TransferServiceImplTest.java index 7f89bd4721..10c2a5da66 100644 --- a/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/service/impl/TransferServiceImplTest.java +++ b/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/service/impl/TransferServiceImplTest.java @@ -2,6 +2,7 @@ import com.github.binarywang.wxpay.bean.transfer.QueryTransferBatchesRequest; import com.github.binarywang.wxpay.bean.transfer.TransferBatchesRequest; +import com.github.binarywang.wxpay.bean.transfer.TransferBillsRequest; import com.github.binarywang.wxpay.exception.WxPayException; import com.github.binarywang.wxpay.service.WxPayService; import com.github.binarywang.wxpay.testbase.ApiTestModule; @@ -73,4 +74,32 @@ public void testTransferBatchesOutBatchNo() throws WxPayException { public void testTransferBatchesOutBatchNoDetail() throws WxPayException { log.info("商家明细单号查询明细单:{}", this.payService.getTransferService().transferBatchesOutBatchNoDetail("1655447999520", "1655447989156")); } + + @Test + public void testTransferBills() throws WxPayException { + TransferBillsRequest transferBillsRequest = TransferBillsRequest.newBuilder() + .appid("wxf636efh5xxxxx") + .outBillNo("1655447989156") + .transferSceneId("1005") + .transferAmount(100) + .transferRemark("测试转账") + .openid("oX_7Jzr9gSZz4X_Xc9-_7HGf8XzI") + .userName("测试用户").build(); + log.info("发起商家转账:{}", this.payService.getTransferService().transferBills(transferBillsRequest)); + } + + @Test + public void testTransformBillsCancel() throws WxPayException { + log.info("撤销商家转账:{}", this.payService.getTransferService().transformBillsCancel("123456")); + } + + @Test + public void testGetBillsByOutBillNo() throws WxPayException { + log.info("商户单号查询转账单:{}", this.payService.getTransferService().getBillsByOutBillNo("123456")); + } + + @Test + public void testGetBillsByTransferBillNo() throws WxPayException { + log.info("微信单号查询转账单:{}", this.payService.getTransferService().getBillsByTransferBillNo("123456")); + } } diff --git a/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/service/impl/WxDepositServiceTest.java b/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/service/impl/WxDepositServiceTest.java new file mode 100644 index 0000000000..1bbf347211 --- /dev/null +++ b/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/service/impl/WxDepositServiceTest.java @@ -0,0 +1,135 @@ +package com.github.binarywang.wxpay.service.impl; + +import com.github.binarywang.wxpay.bean.request.*; +import com.github.binarywang.wxpay.bean.result.*; +import com.github.binarywang.wxpay.exception.WxPayException; +import com.github.binarywang.wxpay.service.WxPayService; +import com.github.binarywang.wxpay.testbase.ApiTestModule; +import com.google.inject.Inject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.testng.annotations.Guice; +import org.testng.annotations.Test; + +/** + *
+ *   微信押金支付测试
+ * 
+ * + * @author Binary Wang + * created on 2024-09-24 + */ +@Test +@Guice(modules = ApiTestModule.class) +public class WxDepositServiceTest { + + private final Logger logger = LoggerFactory.getLogger(this.getClass()); + + @Inject + private WxPayService payService; + + /** + * 测试押金下单 + */ + @Test + public void testUnifiedOrder() throws WxPayException { + WxDepositUnifiedOrderRequest request = WxDepositUnifiedOrderRequest.newBuilder() + .body("共享单车押金") + .outTradeNo("D" + System.currentTimeMillis()) + .totalFee(99) + .spbillCreateIp("192.168.1.1") + .notifyUrl("https://example.com/wxpay/notify") + .tradeType("JSAPI") + .openid("test_openid_123") + .build(); + + try { + WxDepositUnifiedOrderResult result = this.payService.getWxDepositService().unifiedOrder(request); + logger.info("押金下单结果: {}", result); + } catch (WxPayException e) { + logger.error("押金下单失败", e); + // For demo purposes, just log the error - tests need proper WeChat credentials to run + } + } + + /** + * 测试查询押金订单 + */ + @Test + public void testQueryOrder() throws WxPayException { + WxDepositOrderQueryRequest request = WxDepositOrderQueryRequest.newBuilder() + .outTradeNo("D1695559200000") + .build(); + + try { + WxDepositOrderQueryResult result = this.payService.getWxDepositService().queryOrder(request); + logger.info("押金订单查询结果: {}", result); + } catch (WxPayException e) { + logger.error("押金订单查询失败", e); + // For demo purposes, just log the error - tests need proper WeChat credentials to run + } + } + + /** + * 测试押金消费 + */ + @Test + public void testConsume() throws WxPayException { + WxDepositConsumeRequest request = WxDepositConsumeRequest.newBuilder() + .transactionId("1217752501201407033233368018") + .outTradeNo("C" + System.currentTimeMillis()) + .consumeFee(10) + .consumeDesc("单车使用费") + .build(); + + try { + WxDepositConsumeResult result = this.payService.getWxDepositService().consume(request); + logger.info("押金消费结果: {}", result); + } catch (WxPayException e) { + logger.error("押金消费失败", e); + // For demo purposes, just log the error - tests need proper WeChat credentials to run + } + } + + /** + * 测试押金撤销 + */ + @Test + public void testUnfreeze() throws WxPayException { + WxDepositUnfreezeRequest request = WxDepositUnfreezeRequest.newBuilder() + .transactionId("1217752501201407033233368018") + .outTradeNo("U" + System.currentTimeMillis()) + .unfreezeFee(99) + .unfreezeDesc("用户主动取消") + .build(); + + try { + WxDepositUnfreezeResult result = this.payService.getWxDepositService().unfreeze(request); + logger.info("押金撤销结果: {}", result); + } catch (WxPayException e) { + logger.error("押金撤销失败", e); + // For demo purposes, just log the error - tests need proper WeChat credentials to run + } + } + + /** + * 测试押金退款 + */ + @Test + public void testRefund() throws WxPayException { + WxDepositRefundRequest request = WxDepositRefundRequest.newBuilder() + .transactionId("1217752501201407033233368018") + .outRefundNo("R" + System.currentTimeMillis()) + .refundFee(50) + .refundDesc("部分退款") + .build(); + + try { + WxDepositRefundResult result = this.payService.getWxDepositService().refund(request); + logger.info("押金退款结果: {}", result); + } catch (WxPayException e) { + logger.error("押金退款失败", e); + // For demo purposes, just log the error - tests need proper WeChat credentials to run + } + } +} \ 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 31a0c21dc0..5135dea3c8 100644 --- a/weixin-java-qidian/pom.xml +++ b/weixin-java-qidian/pom.xml @@ -7,7 +7,7 @@ com.github.binarywang wx-java - 4.7.0 + 4.7.9.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..8c2d69896e 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) { @@ -82,11 +80,11 @@ public void initHttp() { clientBuilder.proxy(getRequestHttpProxy().getProxy()); // 设置授权 - clientBuilder.authenticator(new Authenticator() { + clientBuilder.proxyAuthenticator(new Authenticator() { @Override public Request authenticate(Route route, Response response) throws IOException { String credential = Credentials.basic(httpProxy.getProxyUsername(), httpProxy.getProxyPassword()); - return response.request().newBuilder().header("Authorization", credential).build(); + return response.request().newBuilder().header("Proxy-Authorization", credential).build(); } }); httpClient = clientBuilder.build(); 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(); diff --git a/weixin-java-qidian/src/main/java/me/chanjar/weixin/qidian/util/json/WxQidianGsonBuilder.java b/weixin-java-qidian/src/main/java/me/chanjar/weixin/qidian/util/json/WxQidianGsonBuilder.java index bdce6bbedd..2e38b20220 100644 --- a/weixin-java-qidian/src/main/java/me/chanjar/weixin/qidian/util/json/WxQidianGsonBuilder.java +++ b/weixin-java-qidian/src/main/java/me/chanjar/weixin/qidian/util/json/WxQidianGsonBuilder.java @@ -1,7 +1,12 @@ package me.chanjar.weixin.qidian.util.json; +import com.google.gson.ExclusionStrategy; +import com.google.gson.FieldAttributes; import com.google.gson.Gson; import com.google.gson.GsonBuilder; +import me.chanjar.weixin.common.util.http.apache.ApacheHttpClientBuilder; + +import java.io.File; /** * @author someone @@ -12,6 +17,18 @@ public class WxQidianGsonBuilder { static { INSTANCE.disableHtmlEscaping(); + + INSTANCE.setExclusionStrategies(new ExclusionStrategy() { + @Override + public boolean shouldSkipField(FieldAttributes fieldAttributes) { + return false; + } + + @Override + public boolean shouldSkipClass(Class aClass) { + return aClass == File.class || aClass == ApacheHttpClientBuilder.class; + } + }); } public static Gson create() {