diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 0000000000..435b7250ba --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,11 @@ +version: 2.1 +jobs: + build: + docker: + - image: cimg/openjdk:11.0 + steps: + - checkout + - run: + name: Build + command: mvn -B -DskipTests clean package -Dcheckstyle.skip=true + diff --git a/.gitee/ISSUE_TEMPLATE.md b/.gitee/ISSUE_TEMPLATE.md index cdae693d35..a0b60ba750 100644 --- a/.gitee/ISSUE_TEMPLATE.md +++ b/.gitee/ISSUE_TEMPLATE.md @@ -1,4 +1,4 @@ -强烈建议大家到 `github` 相关页面提交问题,方便统一查询管理,具体页面地址:https://github.com/Wechat-Group/WxJava/issues +强烈建议大家到 `github` 相关页面提交问题,方便统一查询管理,具体页面地址:https://github.com/binarywang/WxJava/issues 当然如果必须在这里提问,请务必按以下格式填写,谢谢配合~ diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index ca05962438..ced7d6de0c 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -1,6 +1,6 @@ --- name: Bug报告 -about: 如果发现Bug,请告诉我们,我们会尽快修复 +about: 本项目仅对最新版本进行维护,使用老版本出现问题的盆友,请先升级到最新版本,升级完后如果发现bug依然存在,请继续填写此issue。 title: '' labels: '' assignees: '' @@ -8,17 +8,21 @@ assignees: '' --- # 提问前,请确保阅读过项目首页说明以及wiki开发文档相关内容,尤其是常见问题部分。完成内容后,请务必移除包括本句在内的无用内容,以免影响阅读,否则直接关闭,谢谢合作~ -# 另外如果确认属于bug,而且已明确如何修复,请参考贡献指南直接提交PR,省的浪费时间在这里描述问题,非常感谢配合 + +## 另外如果确认属于bug,而且已明确如何修复,请参考贡献指南直接提交PR,省的浪费时间在这里描述问题,非常感谢配合 ### 简要描述 -__简单概括描述下你所遇到的问题。__ +__请简单概括描述下你所遇到的问题。__ ### 模块版本情况 * WxJava 模块名: -* WxJava 版本号: +* WxJava 版本号:(旧版本不予支持,谢谢配合) ### 详细描述 __尽量详细描述。请不要使用截图,尽量使用文字描述,代码直接贴上来,日志则请附在后面所示区域。__ ### 日志 -__将日志放在 [pastebin](https://paste.ubuntu.com/) 或者其他地方,并将其url地址贴在这里__ +__如果日志不多,直接使用md代码引用格式贴在此处,否则如果太长,请将日志放在 [pastebin](https://paste.ubuntu.com/) 或者其他地方,然后将其url地址贴在这里__ +``` +日志请写于此处 +``` diff --git a/.github/agents/my-agent.agent.md b/.github/agents/my-agent.agent.md new file mode 100644 index 0000000000..0c8481288a --- /dev/null +++ b/.github/agents/my-agent.agent.md @@ -0,0 +1,14 @@ +--- +# Fill in the fields below to create a basic custom agent for your repository. +# The Copilot CLI can be used for local testing: https://gh.io/customagents/cli +# To make this agent available, merge this file into the default repository branch. +# For format details, see: https://gh.io/customagents/config + +name: 全部用中文 +description: 需要用中文,包括PR标题和分析总结过程 +--- + +# My Agent + +1、请使用中文输出思考过程和总结,包括PR标题,提交commit信息也要使用中文; +2、生成代码时需要提供必要的单元测试代码。 diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 0000000000..cad29d96d9 --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,202 @@ +# Copilot Instruction +请始终使用中文生成 Pull Request 的标题、描述和提交信息 + + +# WxJava - 微信 Java SDK 开发说明 + +WxJava 是一个支持多种微信平台的完整 Java SDK,包含公众号、小程序、微信支付、企业微信、开放平台、视频号、企点等多种功能模块。 + +**请始终优先参考本说明,只有在遇到与此内容不一致的意外信息时,才退而使用搜索或 bash 命令。** + +## 高效开发指南 + +### 前置条件与环境准备 +- **Java 要求**:JDK 8+(项目最低目标为 Java 8) +- **Maven**:推荐 Maven 3.6+(已验证 Maven 3.9.11) +- **IDE**:推荐使用 IntelliJ IDEA(项目针对 IDEA 优化) + +### 引导、构建与校验 +克隆仓库后按顺序执行以下命令: + +```bash +# 1. 基础编译(请勿中断 - 约需 4-5 分钟) +mvn clean compile -DskipTests=true --no-transfer-progress +# 超时时间:建议设置 8 分钟以上。实际时间:约 4 分钟 + +# 2. 完整打包(请勿中断 - 约需 2-3 分钟) +mvn clean package -DskipTests=true --no-transfer-progress +# 超时时间:建议设置 5 分钟以上。实际时间:约 2 分钟 + +# 3. 代码质量校验(请勿中断 - 约需 45-60 秒) +mvn checkstyle:check --no-transfer-progress +# 超时时间:建议设置 3 分钟以上。实际时间:约 50 秒 +``` + +重要时间说明: +- 绝对不要中断任意 Maven 构建命令 +- 编译阶段耗时最长(约 4 分钟),原因是项目包含 34 个模块 +- 后续构建会更快,因为存在增量编译 +- 始终使用 `--no-transfer-progress` 以减少日志噪音 + +### 测试结构 +- **测试框架**:TestNG(非 JUnit) +- **测试文件**:共有 298 个测试文件 +- **默认行为**:pom.xml 中默认禁用测试(`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/stale.yml b/.github/stale.yml new file mode 100644 index 0000000000..e556fa9854 --- /dev/null +++ b/.github/stale.yml @@ -0,0 +1,17 @@ +# Number of days of inactivity before an issue becomes stale +daysUntilStale: 60 +# Number of days of inactivity before a stale issue is closed +daysUntilClose: 7 +# Issues with these labels will never be considered stale +exemptLabels: + - pinned + - security +# Label to use when marking an issue as stale +staleLabel: stale +# Comment to post when marking an issue as stale. Set to `false` to disable +markComment: > + This issue has been automatically marked as stale because it has not had + recent activity. It will be closed if no further activity occurs. Thank you + for your contributions. +# Comment to post when closing a stale issue. Set to `false` to disable +closeComment: false diff --git a/.github/workflows/maven-publish.yml b/.github/workflows/maven-publish.yml new file mode 100644 index 0000000000..a12c20b112 --- /dev/null +++ b/.github/workflows/maven-publish.yml @@ -0,0 +1,109 @@ +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 test 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 + elif [[ "$COMMIT_MSG" =~ ^:bookmark:\ 发布\ ([0-9]+\.[0-9]+\.[0-9]+)\ 正式版本 ]]; then + VERSION="${BASH_REMATCH[1]}" + TAG="v${VERSION}" + IS_RELEASE="true" + echo "Matched formal 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/.gitignore b/.gitignore index db0804163d..6a5b5f7519 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ +.bash +.history + *.class test-output @@ -50,3 +53,4 @@ sonar-project.properties # STS .factorypath +*.zip diff --git a/.travis.yml b/.travis.yml index 2b128c8a08..99850df729 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,5 @@ language: java - jdk: - openjdk8 script: "mvn clean package -DskipTests=true -Dcheckstyle.skip=true" @@ -15,4 +14,4 @@ cache: notifications: email: - - binarywang@vip.qq.com + - a@binarywang.com diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 75db09403b..0b16b4779e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,8 +1,8 @@ # 代码贡献指南 -1. 首先非常欢迎和感谢对本项目发起Pull Request的同学。 -1. **特别提示:请务必在develop分支提交PR,master分支目前仅是正式版的代码,即发布正式版本后才会从develop分支进行合并。** +1. 首先非常欢迎和感谢对本项目发起 `Pull Request` 的热心小伙伴们。 +1. **特别提示:请务必在 `develop` 分支提交 `PR`,`release` 分支目前仅是正式版的代码,即发布正式版本后才会从 `develop` 分支进行合并。** 1. 本项目代码风格为使用2个空格代表一个Tab,因此在提交代码时请注意一下,否则很容易在IDE格式化代码后与原代码产生大量diff,这样会给其他人阅读代码带来极大的困扰。 -1. 为了便于设置,本项目引入editorconfig支持,请使用Eclipse的同学在贡献代码前安装相关插件,而IntelliJ IDEA新版本自带支持,如果没有可自行安装插件。 +1. 为了便于设置,本项目引入`editorconfig`支持,请使用Eclipse的同学在贡献代码前安装相关插件,而`IntelliJ IDEA`新版本自带支持,如果没有可自行安装插件。 1. **提交代码前,请检查代码是否已经格式化,并且保证新增加或者修改的方法都有完整的参数说明,而public方法必须拥有相应的单元测试并通过测试。** 1. 本项目可以采用两种方式接受代码贡献: - 第一种就是基于[Git Flow](https://www.atlassian.com/git/tutorials/comparing-workflows/gitflow-workflow)开发流程,因此在发起Pull Request的时候请选择develop分支,详细步骤参考后文,推荐使用此种方式贡献代码。 @@ -24,11 +24,11 @@ $ #do some change on the content $ git commit -am "Fix issue #1: change something" $ git push ``` -* 在 GitHub 网站上提交 Pull Request。 +* 在 `GitHub` 或 `Gitee` 网站上提交 `Pull Request`。 * 定期使用项目仓库内容更新自己仓库内容。 ```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/LICENSE b/LICENSE index 0c8a80022e..7783de532a 100644 --- a/LICENSE +++ b/LICENSE @@ -37,7 +37,7 @@ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION You must give any other recipients of the Work or Derivative Works a copy of this License; and You must cause any modified files to carry prominent notices stating that You changed the files; and You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and -If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. +If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. diff --git a/README.md b/README.md index 6d862fe418..080b831d1e 100644 --- a/README.md +++ b/README.md @@ -1,87 +1,162 @@ -## WxJava - 微信开发 Java SDK(开发工具包) [![LICENSE](https://img.shields.io/badge/License-Anti%20996-blue.svg)](https://github.com/996icu/996.ICU/blob/master/LICENSE) [![Badge](https://img.shields.io/badge/Link-996.icu-red.svg)](https://996.icu/#/zh_CN) [![Badge](https://img.shields.io/badge/Link-京东内购福利-red.svg)](https://mp.weixin.qq.com/s/dfwatgMgARaBjh421Todyg) - -[![码云Gitee](https://gitee.com/binary/weixin-java-tools/badge/star.svg?theme=blue)](https://gitee.com/binary/weixin-java-tools) -[![Github](http://github-svg-buttons.herokuapp.com/star.svg?user=Wechat-Group&repo=WxJava&style=flat&background=1081C1)](https://github.com/Wechat-Group/WxJava) -[![GitHub release](https://img.shields.io/github/release/Wechat-Group/WxJava.svg)](https://github.com/Wechat-Group/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://travis-ci.org/Wechat-Group/WxJava.svg?branch=develop)](https://travis-ci.org/Wechat-Group/WxJava) -[![使用IntelliJ IDEA开发维护](https://img.shields.io/badge/IntelliJ%20IDEA-提供支持-blue.svg)](https://www.jetbrains.com/?from=WxJava-weixin-java-tools) +## 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) + +[![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) -#### 支持包括微信支付、开放平台、公众号、企业微信/企业号、小程序等微信功能的后端开发。 +
+ + Featured|HelloGitHub + + + binarywang%2FWxJava | 趋势转变 + +
- - - - + + + +
+### 微信`Java`开发工具包,支持包括微信支付、开放平台、公众号、企业微信、视频号、小程序等微信功能模块的后端开发。 +
+ 特别赞助 + + + + + + + + + + - - - - - - -
+ + ccflow + +
+ + 计全支付Jeepay,开源支付系统 + + + + Mall4j + +
- + mp qrcode - - - - - - - - - - - - - - - - -
+
+ + diboot低代码开发平台 + + + + ad + +
+ ### 重要信息 -1. **2019-11-25 发布 [【3.6.0正式版】](https://github.com/Wechat-Group/WxJava/releases)**! -1. 新手重要提示:本项目仅是一个SDK开发工具包,未提供Web实现,建议使用 `maven` 或 `gradle` 引用本项目即可使用本SDK提供的各种功能,详情可参考 **[【Demo项目】](demo.md)** 或本项目中的部分单元测试代码;另外微信开发新手请务必阅读[【开发文档 Wiki 首页】](https://github.com/Wechat-Group/WxJava/wiki)的常见问题部分,可以少走很多弯路,节省不少时间。 -1. 技术交流群:想获得QQ群/微信群/钉钉企业群等信息的同学,请使用微信扫描上面的微信公众号二维码关注 `WxJava` 后点击相关菜单即可获取加入方式,同时也可以在微信中搜索 `weixin-java-tools` 或 `WxJava` 后选择正确的公众号进行关注,该公众号会及时通知SDK相关更新信息,并不定期分享微信Java开发相关技术知识; -1. 付费QQ群:(**注意:刚入群会有5分钟禁言,稍等片刻即可正常发言**) [![加入QQ群](https://img.shields.io/badge/QQ群-343954419-blue.svg)](http://shang.qq.com/wpa/qunwpa?idkey=731dc3e7ea31ebe25376cc1a791445468612c63fd0e9e05399b088ec81fd9e15) 或 [![加入QQ群](https://img.shields.io/badge/QQ群-343954419-blue.svg)](http://jq.qq.com/?_wv=1027&k=40lRskK),或者请自行搜索群号`343954419`进行添加;当然由于某种原因无法入群的,可关注公众号后获取其他群的加入方式; -1. 钉钉企业群:[请点击链接申请加入](https://h5.dingtalk.com/inviteColleague/index.html#/invite/9ed100cc4a/E1DF918E32E398D191E7FE61FE0552A6) 或者 [用手机钉钉APP扫码](https://gitee.com/binary/weixin-java-tools/raw/master/images/qrcodes/ding.jpg) 申请加入。 -1. 微信开发新手或者Java开发新手在群内提问或新开Issue提问前,请先阅读[【提问的智慧】](https://github.com/ryanhanwu/How-To-Ask-Questions-The-Smart-Way/blob/master/README-zh_CN.md),并确保已查阅过 [【开发文档Wiki】](https://github.com/wechat-group/WxJava/wiki) ,避免浪费大家的宝贵时间; -1. 寻求帮助时需贴代码或大长串异常信息的,请利用 http://paste.ubuntu.com +1. [`WxJava` 荣获 `GitCode` 2024年度十大开源社区奖项](https://mp.weixin.qq.com/s/wM_UlMsDm3IZ1CPPDvcvQw)。 +2. 项目合作洽谈请联系微信`binary0000`(在微信里自行搜索并添加好友,请注明来意,如有关于SDK问题需讨论请参考下文入群讨论,不要加此微信)。 +3. **2026-01-03 发布 [【4.8.0正式版】](https://mp.weixin.qq.com/s/mJoFtGc25pXCn3uZRh6Q-w)**! +5. 贡献源码可以参考视频:[【贡献源码全过程(上集)】](https://mp.weixin.qq.com/s/3xUZSATWwHR_gZZm207h7Q)、[【贡献源码全过程(下集)】](https://mp.weixin.qq.com/s/nyzJwVVoYSJ4hSbwyvTx9A) ,友情提供:[程序员小山与Bug](https://space.bilibili.com/473631007) +6. 新手重要提示:本项目仅是一个SDK开发工具包,未提供Web实现,建议使用 `maven` 或 `gradle` 引用本项目即可使用本SDK提供的各种功能,详情可参考 **[【Demo项目】](demo.md)** 或本项目中的部分单元测试代码; +7. 微信开发新手请务必阅读【开发文档】([Gitee Wiki](https://gitee.com/binary/weixin-java-tools/wikis/Home) 或者 [Github Wiki](https://github.com/binarywang/WxJava/wiki))的常见问题部分,可以少走很多弯路,节省不少时间。 +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 -------------------------------- ### 其他说明 1. **阅读源码的同学请注意,本SDK为简化代码编译时加入了`lombok`支持,如果不了解`lombok`的话,请先学习下相关知识,比如可以阅读[此文章](https://mp.weixin.qq.com/s/cUc-bUcprycADfNepnSwZQ);** -1. 如有新功能需求,发现BUG,或者由于微信官方接口调整导致的代码问题,可以直接在[【Issues】](https://github.com/Wechat-Group/WxJava/issues)页提出issue,便于讨论追踪问题; -1. 如果需要贡献代码,请务必在提交PR之前先仔细阅读[【代码贡献指南】](CONTRIBUTING.md),谢谢理解配合; -1. 本SDK要求的最低JDK版本是1.7,还在使用JDK6的用户请参考[【此项目】]( https://github.com/binarywang/weixin-java-tools-for-jdk6) ,而其他更早的JDK版本则需要自己改造实现。 -1. [开源中国本项目的首页](https://www.oschina.net/p/weixin-java-tools-new),欢迎大家积极留言评分 🙂 -1. SDK开发文档请查阅 [【开发文档Wiki】](https://github.com/wechat-group/WxJava/wiki),部分文档可能未能及时更新,如有发现,可以及时上报或者自行修改。 -1. **如果本开发工具包对您有所帮助,欢迎对我们的努力进行肯定,可以直接前往[【托管于码云的项目首页】](http://gitee.com/binary/weixin-java-tools),在页尾部分找到“捐助”按钮进行打赏,多多益善 😄。非常感谢各位打赏和捐助的同学!** -1. 各个模块的Javadoc可以在线查看:[weixin-java-miniapp](http://binary.ac.cn/weixin-java-miniapp-javadoc/)、[weixin-java-pay](http://binary.ac.cn/weixin-java-pay-javadoc/)、[weixin-java-mp](http://binary.ac.cn/weixin-java-mp-javadoc/)、[weixin-java-common](http://binary.ac.cn/weixin-java-common-javadoc/)、[weixin-java-cp](http://binary.ac.cn/weixin-java-cp-javadoc/)、[weixin-java-open](http://binary.ac.cn/weixin-java-open-javadoc/) -1. 本SDK项目在以下代码托管网站同步更新: +2. 如有新功能需求,发现BUG,或者由于微信官方接口调整导致的代码问题,可以直接在[【Issues】](https://github.com/binarywang/WxJava/issues)页提出issue,便于讨论追踪问题; +3. 如果需要贡献代码,请务必在提交PR之前先仔细阅读[【代码贡献指南】](CONTRIBUTING.md),谢谢理解配合; +4. 目前本`SDK`最新版本要求的`JDK`最低版本是`8`,使用`7`的同学可以使用`WxJava` `3.8.0`及以前版本,而还在使用`JDK`6的用户请参考[【此项目】]( https://github.com/binarywang/weixin-java-tools-for-jdk6) ,而其他更早的JDK版本则需要自己改造实现。 +5. [本项目在开源中国的页面](https://www.oschina.net/p/weixin-java-tools-new),欢迎大家积极留言评分 🙂 +6. SDK开发文档请查阅 [【开发文档Wiki】](https://github.com/binarywang/WxJava/wiki),部分文档可能未能及时更新,如有发现,可以及时上报或者自行修改。 +7. **如果本开发工具包对您有所帮助,欢迎对我们的努力进行肯定,可以直接前往[【托管于码云的项目首页】](http://gitee.com/binary/weixin-java-tools),在页尾部分找到“捐助”按钮进行打赏,多多益善 😄。非常感谢各位打赏和捐助的同学!** +8. 各个模块的Javadoc可以在线查看:[weixin-java-miniapp](http://binary.ac.cn/weixin-java-miniapp-javadoc/)、[weixin-java-pay](http://binary.ac.cn/weixin-java-pay-javadoc/)、[weixin-java-mp](http://binary.ac.cn/weixin-java-mp-javadoc/)、[weixin-java-common](http://binary.ac.cn/weixin-java-common-javadoc/)、[weixin-java-cp](http://binary.ac.cn/weixin-java-cp-javadoc/)、[weixin-java-open](http://binary.ac.cn/weixin-java-open-javadoc/) +9. 本SDK项目在以下代码托管网站同步更新: * 码云:https://gitee.com/binary/weixin-java-tools -* GitHub:https://github.com/wechat-group/WxJava +* GitHub:https://github.com/binarywang/WxJava --------------------------------- ### 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 (不同模块参考下文) - 3.6.0 + 4.8.0 ``` - 微信小程序:`weixin-java-miniapp` - 微信支付:`weixin-java-pay` - 微信开放平台:`weixin-java-open` - - 公众号(包括订阅号和服务号):`weixin-java-mp` - - 企业号/企业微信:`weixin-java-cp` + - 微信公众号:`weixin-java-mp` + - 企业微信:`weixin-java-cp` + - 微信视频号/微信小店:`weixin-java-channel` + +**注意**: +- **移动应用开发**:如果你的移动应用(iOS/Android App)需要接入微信登录、分享等功能: + - 微信登录(网页授权):使用 `weixin-java-open` 模块,在服务端处理 OAuth 授权 + - 微信支付:使用 `weixin-java-pay` 模块 + - 客户端集成:需使用微信官方提供的移动端SDK(iOS/Android),本项目为服务端SDK +- **微信开放平台**(`weixin-java-open`)主要用于第三方平台,代公众号或小程序进行开发和管理 + + +--------------------------------- +### HTTP 客户端支持 + +本项目同时支持多种 HTTP 客户端实现,默认推荐使用 **Apache HttpClient 5.x**(最新稳定版本)。 + +#### 支持的 HTTP 客户端类型 + +| HTTP 客户端 | 说明 | 配置值 | 推荐程度 | +|------------|------|--------|---------| +| Apache HttpClient 5.x | Apache HttpComponents Client 5.x,最新版本 | `HttpComponents` | ⭐⭐⭐⭐⭐ 推荐 | +| Apache HttpClient 4.x | Apache HttpClient 4.x,向后兼容 | `HttpClient` | ⭐⭐⭐⭐ 兼容 | +| OkHttp | Square OkHttp 客户端 | `OkHttp` | ⭐⭐⭐ 可选 | +| Jodd-http | Jodd 轻量级 HTTP 客户端 | `JoddHttp` | ⭐⭐ 可选 | + +#### 配置方式 + +**Spring Boot 配置示例:** + +```properties +# 使用 HttpClient 5.x(推荐,MP/MiniApp/CP/Channel/QiDian 模块默认) +wx.mp.config-storage.http-client-type=HttpComponents + +# 使用 HttpClient 4.x(兼容模式) +wx.mp.config-storage.http-client-type=HttpClient + +# 使用 OkHttp +wx.mp.config-storage.http-client-type=OkHttp + +# 使用 Jodd-http +wx.mp.config-storage.http-client-type=JoddHttp +``` + +**注意**:如果使用 Multi-Starter(如 `wx-java-mp-multi-spring-boot-starter`),枚举值需使用大写下划线格式: +```properties +# Multi-Starter 配置格式 +wx.mp.config-storage.http-client-type=HTTP_COMPONENTS # 注意使用大写下划线 +``` + +**注意事项:** +1. **MP、MiniApp、Channel、QiDian 模块**已完整支持 HttpClient 5.x,默认推荐使用 +2. **CP 模块**的支持情况取决于具体使用的 Starter 版本,请参考对应模块文档 +3. 如需使用 OkHttp 或 Jodd-http,需在项目中添加对应的依赖(scope为provided) +4. HttpClient 4.x 和 HttpClient 5.x 可以共存,按需配置即可 --------------------------------- @@ -90,26 +165,34 @@
点此展开查看 -1. 本项目定为大约每两个月发布一次正式版(同时 `develop` 分支代码合并进入 `master` 分支),版本号格式为 `X.X.0`(如`2.1.0`,`2.2.0`等),遇到重大问题需修复会及时提交新版本,欢迎大家随时提交Pull Request; -1. BUG修复和新特性一般会先发布成小版本作为临时测试版本(如`3.1.8.B`,即尾号不为0,并添加B,以区别于正式版),代码仅存在于 `develop` 分支中; -1. 目前最新版本号为 [![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) +
---------------------------------- -### 使用案例 -完整案例登记列表,请[【访问这里】](https://github.com/Wechat-Group/weixin-java-tools/issues/729)查看,欢迎登记更多的案例。 +### 应用案例 +完整案例登记列表,请[【访问这里】](https://github.com/binarywang/WxJava/issues/729)查看,欢迎登记更多的案例。 -以下为部分案例列表: +
+以下为节选的部分案例, 点此展开查看 #### 开源项目: - 基于微信公众号的签到、抽奖、发送弹幕程序:https://github.com/workcheng/weiya -- XxPay聚合支付:https://github.com/jmdhappy/xxpay-master +- Jeepay 支付系统:https://gitee.com/jeequan/jeepay - 微同商城:https://gitee.com/fuyang_lipengjun/platform - 微信点餐系统:https://github.com/sqmax/springboot-project - 专注批量推送的小而美的工具:https://github.com/rememberber/WePush - yshop意象商城系统:https://gitee.com/guchengwuyue/yshopmall +- wx-manage(微信公众号管理项目):https://github.com/niefy/wx-manage +- 基于若依开发的微信公众号管理系统:https://gitee.com/joolun/JooLun-wx +- SAAS微信小程序电商:https://gitee.com/wei-it/weiit-saas +- mall4j 电商商城系统:https://gitee.com/gz-yami/mall4j #### 小程序: - (京东)友家铺子,友家铺子店长版,京粉精选 @@ -121,6 +204,14 @@ - 民医台 - 来一团商家版 - 史必达(史丹利) +- 嘀嗒云印 +- 维沃吼吼 +- 王朝社区(比亚迪新能源社区) +- 极吼吼手机上门回收换新 +- 未来信封 +- 5G惠享 +- 生菜wordpress转小程序 +- 丽日购 #### 公众号: - 中国电信上海网厅(sh_189) @@ -132,42 +223,35 @@ - 光环云社群 - 手机排队 - [全民约跑健身便利店](http://www.oneminsport.com/) -- [洽洽食品](https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket=gQFM8TwAAAAAAAAAAS5odHRwOi8vd2VpeGluLnFxLmNvbS9xLzAycDRPOXBZbVZib2UxMDAwME0wN2gAAgRIu4RbAwQAAAAA)、[洽洽合伙人](https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket=gQFP8jwAAAAAAAAAAS5odHRwOi8vd2VpeGluLnFxLmNvbS9xLzAyOUpJaU5VcXBlWTAxMDAwME0wN1oAAgSau4RbAwQAAAAA) - 民医台 - YshopMall - 好行景区直通车以及全国40多个公众号 +- 我奥篮球公众号 +- 未来信封官微 +- 银川智云问诊 +- 5G惠享 -#### 企业号/企业微信: -- 洽洽企业号 +#### 企业微信: - HTC企业微信 - 掌上史丹利 +- 药店益 #### 其他: - 高善人力资源 -- [小猪餐餐](http://www.xzcancan.com/) -- [餐饮系统](http://canyin.daydao.com) +- 小猪餐餐 +- 餐饮系统 - 微信公众号管理系统:http://demo.joolun.com - 锐捷网络:Saleslink +
+ ---------------------------------- ### 贡献者列表 -特别感谢参与贡献的所有同学,所有贡献者列表请在[此处](https://github.com/Wechat-Group/WxJava/graphs/contributors)查看,欢迎大家继续踊跃贡献代码! -
-点击此处展开查看贡献次数最多的几位同学 - -1. [chanjarster (Daniel Qian)](http://github.com/chanjarster) -1. [binarywang (Binary Wang)](http://github.com/binarywang) -1. [mgcnrx11](http://github.com/mgcnrx11) -1. [007gzs](http://github.com/007gzs) -1. [aimilin6688 (Jonk)](http://github.com/aimilin6688) -1. [kakotor](http://github.com/kakotor) -1. [kareanyi (MillerLin)](http://github.com/kareanyi) -1. [tianmu](http://github.com/tianmu) -1. [rememberber (周波)](http://github.com/rememberber) -1. [charmingoh (Charming)](http://github.com/charmingoh) +特别感谢参与贡献的所有同学,所有贡献者列表请在[此处](https://github.com/binarywang/WxJava/graphs/contributors)查看,欢迎大家继续踊跃贡献代码! -
+ + + ### GitHub Stargazers over time - -[![Stargazers over time](https://starchart.cc/Wechat-Group/WxJava.svg)](https://starchart.cc/Wechat-Group/WxJava) +[![Star History Chart](https://api.star-history.com/svg?repos=binarywang/WxJava&type=Date)](https://star-history.com/#binarywang/WxJava&Date) diff --git a/demo.md b/demo.md index 2e29bb2089..d6b55b89e2 100644 --- a/demo.md +++ b/demo.md @@ -1,5 +1,5 @@ - ## `Demo` 项目 + ### 说明 1. 在码云和 `GitHub` 上均可访问,会尽量保持同步,请根据自己情况选用。 1. 一般来说,`Github`上的版本应该是最新的,但也有可能没及时同步,此种情况下请以 `Github` 上的版本为准,有问题也请在 `Github` 对应项目 `Issues` 页面提问)。 @@ -7,15 +7,19 @@ ### `Spring Boot Starter` 实现 - 微信支付:[点击查看使用方法](https://github.com/Wechat-Group/WxJava/tree/master/spring-boot-starters/wx-java-pay-spring-boot-starter) + - [使用该 `starter` 实现的微信支付`Demo`](https://github.com/binarywang/wx-java-pay-demo) - 微信公众号:[点击查看使用方法](https://github.com/Wechat-Group/WxJava/tree/master/spring-boot-starters/wx-java-mp-spring-boot-starter) + - [使用该 `starter` 实现的公众号 `Demo`](https://github.com/binarywang/wx-java-mp-demo) - 微信小程序:[点击查看使用方法](https://github.com/Wechat-Group/WxJava/tree/master/spring-boot-starters/wx-java-miniapp-spring-boot-starter) + - [使用该 `starter` 实现的小程序 `Demo`](https://github.com/binarywang/wx-java-miniapp-demo) ### Demo 列表 -1. 微信支付 Demo:[GitHub](http://github.com/binarywang/weixin-java-pay-demo)、[码云](http://gitee.com/binary/weixin-java-pay-demo) [![Build Status](https://travis-ci.org/binarywang/weixin-java-pay-demo.svg?branch=master)](https://travis-ci.org/binarywang/weixin-java-pay-demo) -1. 企业号/企业微信 Demo:[GitHub](http://github.com/binarywang/weixin-java-cp-demo)、[码云](http://gitee.com/binary/weixin-java-cp-demo) [![Build Status](https://travis-ci.org/binarywang/weixin-java-cp-demo.svg?branch=master)](https://travis-ci.org/binarywang/weixin-java-cp-demo) -1. 微信小程序 Demo:[GitHub](http://github.com/binarywang/weixin-java-miniapp-demo)、[码云](http://gitee.com/binary/weixin-java-miniapp-demo) [![Build Status](https://travis-ci.org/binarywang/weixin-java-miniapp-demo.svg?branch=master)](https://travis-ci.org/binarywang/weixin-java-miniapp-demo) -1. 开放平台 Demo:[GitHub](http://github.com/Wechat-Group/weixin-java-open-demo)、[码云](http://gitee.com/binary/weixin-java-open-demo) [![Build Status](https://travis-ci.org/Wechat-Group/weixin-java-open-demo.svg?branch=master)](https://travis-ci.org/Wechat-Group/weixin-java-open-demo) -1. 公众号 Demo: - - 使用 `Spring MVC` 实现的公众号 Demo:[GitHub](http://github.com/binarywang/weixin-java-mp-demo-springmvc)、[码云](https://gitee.com/binary/weixin-java-mp-demo) [![Build Status](https://travis-ci.org/binarywang/weixin-java-mp-demo-springmvc.svg?branch=master)](https://travis-ci.org/binarywang/weixin-java-mp-demo-springmvc) - - 使用 `Spring Boot` 实现的公众号 Demo(支持多公众号):[GitHub](http://github.com/binarywang/weixin-java-mp-demo-springboot)、[码云](http://gitee.com/binary/weixin-java-mp-demo-springboot) [![Build Status](https://travis-ci.org/binarywang/weixin-java-mp-demo-springboot.svg?branch=master)](https://travis-ci.org/binarywang/weixin-java-mp-demo-springboot) - - 含公众号和部分微信支付代码的 Demo:[GitHub](http://github.com/Wechat-Group/weixin-java-demo-springmvc)、[码云](http://gitee.com/binary/weixin-java-tools-springmvc) [![Build Status](https://travis-ci.org/Wechat-Group/weixin-java-demo-springmvc.svg?branch=master)](https://travis-ci.org/Wechat-Group/weixin-java-demo-springmvc) +1. 微信支付 Demo:[GitHub](http://github.com/binarywang/weixin-java-pay-demo)、[码云](http://gitee.com/binary/weixin-java-pay-demo) [![Build Status](https://app.travis-ci.com/binarywang/weixin-java-pay-demo.svg?branch=master)](https://app.travis-ci.com/binarywang/weixin-java-pay-demo) +1. 企业号/企业微信 Demo:[GitHub](http://github.com/binarywang/weixin-java-cp-demo)、[码云](http://gitee.com/binary/weixin-java-cp-demo) [![Build Status](https://app.travis-ci.com/binarywang/weixin-java-cp-demo.svg?branch=master)](https://app.travis-ci.com/binarywang/weixin-java-cp-demo) +1. 微信小程序 Demo:[GitHub](http://github.com/binarywang/weixin-java-miniapp-demo)、[码云](http://gitee.com/binary/weixin-java-miniapp-demo) [![Build Status](https://app.travis-ci.com/binarywang/weixin-java-miniapp-demo.svg?branch=master)](https://app.travis-ci.com/binarywang/weixin-java-miniapp-demo) +1. 开放平台 Demo:[GitHub](http://github.com/Wechat-Group/weixin-java-open-demo)、[码云](http://gitee.com/binary/weixin-java-open-demo) [![Build Status](https://app.travis-ci.com/Wechat-Group/weixin-java-open-demo.svg?branch=master)](https://app.travis-ci.com/Wechat-Group/weixin-java-open-demo) +1. 微信公众号 Demo: + - 使用 `Spring MVC` 实现的公众号 Demo:[GitHub](http://github.com/binarywang/weixin-java-mp-demo-springmvc)、[码云](https://gitee.com/binary/weixin-java-mp-demo) [![Build Status](https://app.travis-ci.com/binarywang/weixin-java-mp-demo-springmvc.svg?branch=master)](https://app.travis-ci.com/binarywang/weixin-java-mp-demo-springmvc) + - 使用 `Spring Boot` 实现的公众号 Demo(支持多公众号):[GitHub](http://github.com/binarywang/weixin-java-mp-demo)、[码云](http://gitee.com/binary/weixin-java-mp-demo-springboot) [![Build Status](https://app.travis-ci.com/binarywang/weixin-java-mp-demo.svg?branch=master)](https://app.travis-ci.com/binarywang/weixin-java-mp-demo) + - 含公众号和部分微信支付代码的 Demo:[GitHub](http://github.com/Wechat-Group/weixin-java-demo-springmvc)、[码云](http://gitee.com/binary/weixin-java-tools-springmvc) [![Build Status](https://app.travis-ci.com/Wechat-Group/weixin-java-demo-springmvc.svg?branch=master)](https://app.travis-ci.com/Wechat-Group/weixin-java-demo-springmvc) + diff --git a/docs/HTTPCLIENT_UPGRADE_GUIDE.md b/docs/HTTPCLIENT_UPGRADE_GUIDE.md new file mode 100644 index 0000000000..5cabb10674 --- /dev/null +++ b/docs/HTTPCLIENT_UPGRADE_GUIDE.md @@ -0,0 +1,199 @@ +# HttpClient 升级指南 + +## 概述 + +从 WxJava 4.7.x 版本开始,项目开始支持并推荐使用 **Apache HttpClient 5.x**(HttpComponents Client 5),同时保持对 HttpClient 4.x 的向后兼容。 + +## 为什么升级? + +1. **Apache HttpClient 5.x 是最新稳定版本**:提供更好的性能和更多的功能 +2. **HttpClient 4.x 已经进入维护模式**:不再积极开发新功能 +3. **更好的安全性**:HttpClient 5.x 包含最新的安全更新和改进 +4. **向前兼容**:为未来的开发做好准备 + +## 支持的 HTTP 客户端 + +| HTTP 客户端 | 版本 | 配置值 | 状态 | 说明 | +|------------|------|--------|------|------| +| Apache HttpClient 5.x | 5.5 | `HttpComponents` | ⭐ 推荐 | 最新稳定版本 | +| Apache HttpClient 4.x | 4.5.13 | `HttpClient` | ✅ 支持 | 向后兼容 | +| OkHttp | 4.12.0 | `OkHttp` | ✅ 支持 | 需自行添加依赖 | +| Jodd-http | 6.3.0 | `JoddHttp` | ✅ 支持 | 需自行添加依赖 | + +## 模块支持情况 + +| 模块 | HttpClient 5.x 支持 | 默认客户端 | +|------|-------------------|-----------| +| weixin-java-mp(公众号) | ✅ 是 | HttpComponents (5.x) | +| weixin-java-cp(企业微信) | ⚠️ 视集成方式而定 | 参考对应 starter 配置 | +| weixin-java-channel(视频号) | ✅ 是 | HttpComponents (5.x) | +| weixin-java-qidian(企点) | ✅ 是 | HttpComponents (5.x) | +| weixin-java-miniapp(小程序) | ✅ 是 | HttpComponents (5.x) | +| weixin-java-pay(支付) | ✅ 是 | HttpComponents (5.x) | +| weixin-java-open(开放平台) | ✅ 是 | HttpComponents (5.x) | + +**注意**: +- **weixin-java-cp 模块**的支持情况取决于具体使用的 Starter 版本,请参考对应模块文档。 + +## 对现有项目的影响 + +### 对新项目 +- **无需任何修改**,直接使用最新版本即可 +- 支持 HttpClient 5.x 的模块会自动使用 HttpComponents (5.x) + +### 对现有项目 +- **向后兼容**:不需要修改任何代码 +- 如果希望继续使用 HttpClient 4.x,只需在配置中显式指定,pay 模块会自动包含 httpclient4 依赖(因为某些接口必须使用 httpclient4) + 其他模块(mp、miniapp、cp、open、channel、qidian)如果需要使用 httpclient4,必须显式在项目中添加 httpclient4 依赖 + +## 迁移步骤 + +### 1. 更新 WxJava 版本 + +在 `pom.xml` 中更新版本: + +```xml + + com.github.binarywang + weixin-java-mp + 最新版本 + +``` + +### 2. 检查配置(可选) + +#### Spring Boot 项目 + +在 `application.properties` 或 `application.yml` 中: + +```properties +# 使用 HttpClient 5.x(推荐,无需配置,已经是默认值) +wx.mp.config-storage.http-client-type=HttpComponents + +# 或者继续使用 HttpClient 4.x +wx.mp.config-storage.http-client-type=HttpClient +``` + +#### 纯 Java 项目 + +```java +// 使用 HttpClient 5.x(推荐) +WxMpService wxMpService = new WxMpServiceHttpComponentsImpl(); + +// 或者继续使用 HttpClient 4.x +WxMpService wxMpService = new WxMpServiceHttpClientImpl(); +``` + +### 3. 测试应用 + +升级后,建议进行全面测试以确保一切正常工作。 + +## 常见问题 + +### Q: 升级后会不会破坏现有代码? +A: 不会。项目保持完全向后兼容,HttpClient 4.x 的所有实现都保持不变。 + +### Q: 我需要修改代码吗? +A: 大多数情况下不需要。如果希望继续使用 HttpClient 4.x,只需在配置中指定 `http-client-type=HttpClient` ,并引入 HttpClient 4.x 依赖即可。 + +### Q: 我可以在同一个项目中同时使用两个版本吗? +A: 可以。不同的模块可以配置使用不同的 HTTP 客户端。例如,MP 模块使用 HttpClient 5.x,pay 模块部分接口仍使用 HttpClient 4.x,但也可以按需配置为 HttpClient 5.x。 + +### Q: 如何排除不需要的依赖? +A: 如果只想使用一个版本,可以在 `pom.xml` 中排除另一个: + +```xml + + com.github.binarywang + weixin-java-mp + 最新版本 + + + + org.apache.httpcomponents + httpclient + + + org.apache.httpcomponents + httpmime + + + +``` + +## 配置参考 + +### Spring Boot 完整配置示例 + +```properties +# 公众号配置 +wx.mp.app-id=your_app_id +wx.mp.secret=your_secret +wx.mp.token=your_token +wx.mp.aes-key=your_aes_key + +# HTTP 客户端配置 +wx.mp.config-storage.http-client-type=HttpComponents # HttpComponents, HttpClient, OkHttp, JoddHttp + +# HTTP 代理配置(可选) +wx.mp.config-storage.http-proxy-host=proxy.example.com +wx.mp.config-storage.http-proxy-port=8080 +wx.mp.config-storage.http-proxy-username=proxy_user +wx.mp.config-storage.http-proxy-password=proxy_pass + +# 超时配置(可选) +wx.mp.config-storage.connection-timeout=5000 +wx.mp.config-storage.so-timeout=5000 +wx.mp.config-storage.connection-request-timeout=5000 +``` + +## 技术细节 + +### HttpClient 4.x 与 5.x 的主要区别 + +1. **包名变更**: + - HttpClient 4.x: `org.apache.http.*` + - HttpClient 5.x: `org.apache.hc.client5.*`, `org.apache.hc.core5.*` + +2. **API 改进**: + - HttpClient 5.x 提供更现代的 API 设计 + - 更好的异步支持 + - 改进的连接池管理 + +3. **性能优化**: + - HttpClient 5.x 包含多项性能优化 + - 更好的资源管理 + +### 项目中的实现 + +WxJava 项目通过策略模式支持多种 HTTP 客户端: + +``` +weixin-java-common/ +├── util/http/ +│ ├── apache/ # HttpClient 4.x 实现 +│ ├── hc/ # HttpClient 5.x (HttpComponents) 实现 +│ ├── okhttp/ # OkHttp 实现 +│ └── jodd/ # Jodd-http 实现 +``` + +每个模块都有对应的 Service 实现类: +- `*ServiceHttpClientImpl` - 使用 HttpClient 4.x +- `*ServiceHttpComponentsImpl` - 使用 HttpClient 5.x +- `*ServiceOkHttpImpl` - 使用 OkHttp +- `*ServiceJoddHttpImpl` - 使用 Jodd-http + +## 反馈与支持 + +如果在升级过程中遇到问题,请: + +1. 查看 [项目 Wiki](https://github.com/binarywang/WxJava/wiki) +2. 在 [GitHub Issues](https://github.com/binarywang/WxJava/issues) 中搜索或提交问题 +3. 加入技术交流群(见 README.md) + +## 总结 + +- ✅ **推荐使用 HttpClient 5.x**:性能更好,功能更强 +- ✅ **向后兼容**:可以继续使用 HttpClient 4.x +- ✅ **灵活配置**:支持多种 HTTP 客户端,按需选择 +- ✅ **平滑迁移**:无需修改代码,仅需配置,若不使用 HttpClient 5.x ,引入其他依赖即可 diff --git a/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..835ff7d518 --- /dev/null +++ b/docs/NEW_TRANSFER_API_SUPPORT.md @@ -0,0 +1,154 @@ +# 微信支付新版商户转账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 +✅ **授权模式** - 支持免确认收款授权模式 + +### 3. 收款授权模式支持 + +**新增功能:免确认收款授权模式** + +- **需确认收款授权模式**(默认):用户需要手动确认才能收款 +- **免确认收款授权模式**:用户授权后,收款无需确认,转账直接到账 + +#### 使用方法 + +```java +// 免确认授权模式 - 提升用户体验 +TransferBillsRequest request = TransferBillsRequest.newBuilder() + .receiptAuthorizationMode(WxPayConstants.ReceiptAuthorizationMode.NO_CONFIRM_RECEIPT_AUTHORIZATION) + // 其他参数... + .build(); + +// 需确认授权模式(默认) +TransferBillsRequest request2 = TransferBillsRequest.newBuilder() + .receiptAuthorizationMode(WxPayConstants.ReceiptAuthorizationMode.CONFIRM_RECEIPT_AUTHORIZATION) + // 其他参数... + .build(); +``` + +## 快速开始 + +### 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..7b1a8da4ea --- /dev/null +++ b/docs/NEW_TRANSFER_API_USAGE.md @@ -0,0 +1,242 @@ +# 微信支付新版商户转账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` +- **特点**: 单笔转账,支持更丰富的转账场景 + +## 收款授权模式功能 + +### 授权模式说明 + +微信支付转账支持两种收款授权模式: + +#### 1. 需确认收款授权模式(默认) +- **常量**: `WxPayConstants.ReceiptAuthorizationMode.CONFIRM_RECEIPT_AUTHORIZATION` +- **特点**: 用户收到转账后需要手动点击确认才能到账 +- **适用场景**: 一般的转账场景 +- **用户体验**: 安全性高,但需要额外操作 + +#### 2. 免确认收款授权模式 +- **常量**: `WxPayConstants.ReceiptAuthorizationMode.NO_CONFIRM_RECEIPT_AUTHORIZATION` +- **特点**: 用户事先授权后,转账直接到账,无需确认 +- **适用场景**: 高频转账场景,如佣金发放、返现等 +- **用户体验**: 体验流畅,无需额外操作 +- **前提条件**: 需要用户事先进行授权 + +### 使用示例 + +#### 免确认授权模式转账 + +```java +TransferBillsRequest request = TransferBillsRequest.newBuilder() + .appid("your_appid") + .outBillNo("NO_CONFIRM_" + System.currentTimeMillis()) + .transferSceneId("1005") // 佣金报酬场景 + .openid("user_openid") + .transferAmount(200) // 2元 + .transferRemark("免确认收款转账") + .receiptAuthorizationMode(WxPayConstants.ReceiptAuthorizationMode.NO_CONFIRM_RECEIPT_AUTHORIZATION) + .userRecvPerception("Y") + .build(); + +try { + TransferBillsResult result = transferService.transferBills(request); + System.out.println("转账成功,直接到账:" + result.getTransferBillNo()); +} catch (WxPayException e) { + if ("USER_NOT_AUTHORIZED".equals(e.getErrCode())) { + System.err.println("用户未授权免确认收款,请先引导用户进行授权"); + } +} +``` + +#### 需确认授权模式转账(默认) + +```java +TransferBillsRequest request = TransferBillsRequest.newBuilder() + .appid("your_appid") + .outBillNo("CONFIRM_" + System.currentTimeMillis()) + .transferSceneId("1005") + .openid("user_openid") + .transferAmount(150) // 1.5元 + .transferRemark("需确认收款转账") + // .receiptAuthorizationMode(...) // 不设置时使用默认的确认模式 + .userRecvPerception("Y") + .build(); + +TransferBillsResult result = transferService.transferBills(request); +System.out.println("转账发起成功,等待用户确认:" + result.getPackageInfo()); +``` + +### 错误处理 + +使用免确认授权模式时,需要处理以下可能的错误: + +```java +try { + TransferBillsResult result = transferService.transferBills(request); +} catch (WxPayException e) { + switch (e.getErrCode()) { + case "USER_NOT_AUTHORIZED": + // 用户未授权免确认收款 + System.err.println("请先引导用户进行免确认收款授权"); + // 可以引导用户到授权页面 + break; + case "AUTHORIZATION_EXPIRED": + // 授权已过期 + System.err.println("用户授权已过期,请重新授权"); + break; + default: + System.err.println("转账失败:" + e.getMessage()); + } +} +``` + +### 使用建议 + +1. **高频转账场景**推荐使用免确认模式,提升用户体验 +2. **首次使用**需引导用户进行授权 +3. **处理异常**妥善处理授权相关异常,提供友好的错误提示 +4. **场景选择**根据业务场景选择合适的授权模式 + +## 使用新版转账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/api-signature/api-signature-1.png b/images/api-signature/api-signature-1.png new file mode 100644 index 0000000000..e4d4e1e278 Binary files /dev/null and b/images/api-signature/api-signature-1.png differ diff --git a/images/api-signature/api-signature-2.png b/images/api-signature/api-signature-2.png new file mode 100644 index 0000000000..30982f498b Binary files /dev/null and b/images/api-signature/api-signature-2.png 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/images/banners/vultr.jpg b/images/banners/vultr.jpg deleted file mode 100644 index 80cf3c2b5e..0000000000 Binary files a/images/banners/vultr.jpg and /dev/null differ diff --git a/others/fenxiang.md b/others/fenxiang.md deleted file mode 100644 index 8b13789179..0000000000 --- a/others/fenxiang.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/others/mvnw b/others/mvnw old mode 100644 new mode 100755 diff --git a/others/weixin-java-config/README.md b/others/weixin-java-config/README.md new file mode 100644 index 0000000000..aa70de9579 --- /dev/null +++ b/others/weixin-java-config/README.md @@ -0,0 +1,424 @@ +# weixin-java-config +1.目录说明:多配置文件目录 + +2.项目多配置集锦 +```yml +wechat: + pay: #微信服务商支付 + configs: + - appId: wxe97b2x9c2b3d #spAppId + mchId: 16486610 #服务商商户 + subAppId: wx118cexxe3c07679 #子appId + subMchId: 16496705 #子商户 + apiV3Key: Dc1DBwSc094jAKDGR5aqqb7PTHr #apiV3密钥 + privateKeyPath: classpath:cert/apiclient_key.pem #服务商证书文件,apiclient_key.pem证书文件的绝对路径或者以classpath:开头的类路径(可以配置绝对路径) + privateCertPath: classpath:cert/apiclient_cert.pem #apiclient_cert.pem证书文件的绝对路径或者以classpath:开头的类路径 + miniapp: #小程序 + configs: + - appid: wx118ce3xxc76ccg + secret: 8a132a276ee2f8fb58b1ed8f2 + token: #微信小程序消息服务器配置的token + aesKey: #微信小程序消息服务器配置的EncodingAESKey + msgDataFormat: JSON + cp: #企业微信 + corpId: wwa3be8efd2addfgj + appConfigs: + - agentId: 10001 #客户联系 + secret: T5fTj1n-sBAT4rKNW5c9IYNfPdXZ8-oGol5tX + token: 2bSNqTcLtFYBUa1u2 + aesKey: AXazu2Xyw44SNY1x8go2phn9p9B2O9oiEfqPN + - agentId: 10003 #会话内容存档 + secret: xIpum7Yt4NMXcyxdzcQ2l_46BG4QIQDR57MhA + token: + aesKey: + - agentId: 3010011 #打卡 + secret: 3i2Mhfusifaw_-04bMYI8OoKGxPe9mDALbUxV + token: + aesKey: + - agentId: 19998 #通讯录同步 + secret: rNyDae0Pg-3d-wqTd_ozMSJfF0DEjTCz3b_pr + token: xUke8yZciAZqImGZ + aesKey: EUTVyArqJcfnpFiudxjRpuOexNqBoPbwrNG3R + - agentId: 20000 #微盘 + secret: D-TVMvUji7PZZdjhZOSgiy2MTuBd0OCdvI_zi + token: + aesKey: +``` + +3.主要代码 +###### 1)微信服务商支付 +```java +@Data +@ConfigurationProperties(prefix = "wechat.pay") +public class WxPayProperties { + + private List configs; + + @Getter + @Setter + public static class Config { + + private String appId; + private String mchId; + private String subAppId; + private String subMchId; + private String apiV3Key; + private String privateKeyPath; + private String privateCertPath; + + } + +} +``` +```java +@Configuration +@EnableConfigurationProperties(WxPayProperties.class) +@AllArgsConstructor +public class WxPayConfiguration { + + private WxPayProperties properties; + + @Bean + public WxPayService wxPayService() { + + // 多配置 + WxPayService wxPayService = new WxPayServiceImpl(); + Map payConfigs = this.properties.getConfigs().stream().map(config -> { + WxPayConfig payConfig = new WxPayConfig(); + payConfig.setAppId(StringUtils.trimToNull(config.getAppId())); + payConfig.setMchId(StringUtils.trimToNull(config.getMchId())); + payConfig.setSubAppId(StringUtils.trimToNull(config.getSubAppId())); + payConfig.setSubMchId(StringUtils.trimToNull(config.getSubMchId())); + payConfig.setApiV3Key(StringUtils.trimToNull(config.getApiV3Key())); + payConfig.setPrivateKeyPath(StringUtils.trimToNull(config.getPrivateKeyPath())); + payConfig.setPrivateCertPath(StringUtils.trimToNull(config.getPrivateCertPath())); + + // 可以指定是否使用沙箱环境 + payConfig.setUseSandboxEnv(false); + return payConfig; + }).collect(Collectors.toMap(config -> config.getSubMchId(), a -> a)); + + wxPayService.setMultiConfig(payConfigs); + return wxPayService; + } + +} +``` +###### 2)微信小程序 +```java +@Setter +@Getter +@ConfigurationProperties(prefix = "wechat.miniapp") +public class WxMaProperties { + + private List configs; + + @Data + public static class Config { + + /** + * 设置微信小程序的appid + */ + private String appid; + + /** + * 设置微信小程序的Secret + */ + private String secret; + + /** + * 设置微信小程序消息服务器配置的token + */ + private String token; + + /** + * 设置微信小程序消息服务器配置的EncodingAESKey + */ + private String aesKey; + + /** + * 消息格式,XML或者JSON + */ + private String msgDataFormat; + + } + +} +``` +```java +@Configuration +@EnableConfigurationProperties(WxMaProperties.class) +public class WxMaConfiguration { + + private WxMaProperties properties; + private static Map maServices; + private static final Map routers = Maps.newHashMap(); + + @Autowired + public WxMaConfiguration(WxMaProperties properties) { + this.properties = properties; + } + + public static WxMaService getMaService(String appId) { + WxMaService wxService = maServices.get(appId); + Optional.ofNullable(wxService).orElseThrow(() -> new RuntimeException("没有配置appId")); + return wxService; + } + + public static WxMaMessageRouter getRouter(String appId) { + return routers.get(appId); + } + + @PostConstruct + public void init() { + List configs = this.properties.getConfigs(); + if (configs == null) { + return; + } + + maServices = configs.stream().map(a -> { + // 多配置 + WxMaDefaultConfigImpl config = new WxMaDefaultConfigImpl(); + config.setAppid(a.getAppid()); + config.setSecret(a.getSecret()); + config.setToken(a.getToken()); + config.setAesKey(a.getAesKey()); + config.setMsgDataFormat(a.getMsgDataFormat()); + + WxMaService service = new WxMaServiceImpl(); + service.setWxMaConfig(config); + + routers.put(a.getAppid(), this.newRouter(service)); + return service; + }).collect(Collectors.toMap(s -> s.getWxMaConfig().getAppid(), a -> a)); + } + + private WxMaMessageRouter newRouter(WxMaService service) { + final WxMaMessageRouter router = new WxMaMessageRouter(service); + router + .rule().handler(logHandler).next() + .rule().async(false).content("订阅消息").handler(subscribeMsgHandler).end() + .rule().async(false).content("文本").handler(textHandler).end() + .rule().async(false).content("图片").handler(picHandler).end() + .rule().async(false).content("二维码").handler(qrcodeHandler).end(); + return router; + } + + private final WxMaMessageHandler subscribeMsgHandler = (wxMessage, context, service, sessionManager) -> { + service.getMsgService().sendSubscribeMsg(WxMaSubscribeMessage.builder() + .templateId("此处更换为自己的模板id") + .data(Lists.newArrayList( + new WxMaSubscribeMessage.MsgData("keyword1", "339208499"))) + .toUser(wxMessage.getFromUser()) + .build()); + return null; + }; + + private final WxMaMessageHandler logHandler = (wxMessage, context, service, sessionManager) -> { + log.info("收到logHandler消息:" + wxMessage.toString()); + service.getMsgService().sendKefuMsg(WxMaKefuMessage.newTextBuilder().content("收到信息为:" + wxMessage.toJson()) + .toUser(wxMessage.getFromUser()).build()); + return null; + }; + + private final WxMaMessageHandler textHandler = (wxMessage, context, service, sessionManager) -> { + log.info("收到textHandler消息:" + wxMessage.toString()); + service.getMsgService().sendKefuMsg(WxMaKefuMessage.newTextBuilder().content("回复文本消息") + .toUser(wxMessage.getFromUser()).build()); + return null; + }; + + private final WxMaMessageHandler picHandler = (wxMessage, context, service, sessionManager) -> { + log.info("收到picHandler消息:" + wxMessage.toString()); + try { + WxMediaUploadResult uploadResult = service.getMediaService() + .uploadMedia("image", "png", + ClassLoader.getSystemResourceAsStream("tmp.png")); + service.getMsgService().sendKefuMsg( + WxMaKefuMessage + .newImageBuilder() + .mediaId(uploadResult.getMediaId()) + .toUser(wxMessage.getFromUser()) + .build()); + } catch (WxErrorException e) { + e.printStackTrace(); + } + + return null; + }; + + private final WxMaMessageHandler qrcodeHandler = (wxMessage, context, service, sessionManager) -> { + log.info("收到qrcodeHandler消息:" + wxMessage.toString()); + try { + final File file = service.getQrcodeService().createQrcode("123", 430); + WxMediaUploadResult uploadResult = service.getMediaService().uploadMedia("image", file); + service.getMsgService().sendKefuMsg( + WxMaKefuMessage + .newImageBuilder() + .mediaId(uploadResult.getMediaId()) + .toUser(wxMessage.getFromUser()) + .build()); + } catch (WxErrorException e) { + e.printStackTrace(); + } + + return null; + }; + +} +``` +###### 3)企业微信 +```java +@Getter +@Setter +@ConfigurationProperties(prefix = "wechat.cp") +public class WxCpProperties { + + /** + * 设置企业微信的corpId + */ + private String corpId; + + private List appConfigs; + + @Getter + @Setter + public static class AppConfig { + /** + * 设置企业微信应用的AgentId + */ + private Integer agentId; + + /** + * 设置企业微信应用的Secret + */ + private String secret; + + /** + * 设置企业微信应用的token + */ + private String token; + + /** + * 设置企业微信应用的EncodingAESKey + */ + private String aesKey; + + } + +} +``` +```java +@Configuration +@EnableConfigurationProperties(WxCpProperties.class) +public class WxCpConfiguration { + + private LogHandler logHandler; + private NullHandler nullHandler; + private LocationHandler locationHandler; + private MenuHandler menuHandler; + private MsgHandler msgHandler; + private UnsubscribeHandler unsubscribeHandler; + private SubscribeHandler subscribeHandler; + + private WxCpProperties properties; + + private static Map routers = Maps.newHashMap(); + private static Map cpServices = Maps.newHashMap(); + + @Autowired + public WxCpConfiguration(LogHandler logHandler, NullHandler nullHandler, LocationHandler locationHandler, + MenuHandler menuHandler, MsgHandler msgHandler, UnsubscribeHandler unsubscribeHandler, + SubscribeHandler subscribeHandler, WxCpProperties properties) { + this.logHandler = logHandler; + this.nullHandler = nullHandler; + this.locationHandler = locationHandler; + this.menuHandler = menuHandler; + this.msgHandler = msgHandler; + this.unsubscribeHandler = unsubscribeHandler; + this.subscribeHandler = subscribeHandler; + this.properties = properties; + } + + + public static Map getRouters() { + return routers; + } + + + public static WxCpService getCpService(Integer agentId) { + WxCpService cpService = cpServices.get(agentId); + Optional.ofNullable(cpService).orElseThrow(() -> new RuntimeException("cpService不能为空")); + return cpService; + } + + @PostConstruct + public void initServices() { + cpServices = this.properties.getAppConfigs().stream().map(a -> { + val configStorage = new WxCpDefaultConfigImpl(); + configStorage.setCorpId(this.properties.getCorpId()); + configStorage.setAgentId(a.getAgentId()); + configStorage.setCorpSecret(a.getSecret()); + configStorage.setToken(a.getToken()); + configStorage.setAesKey(a.getAesKey()); + + val service = new WxCpServiceImpl(); + service.setWxCpConfigStorage(configStorage); + + routers.put(a.getAgentId(), this.newRouter(service)); + return service; + }).collect(Collectors.toMap(service -> service.getWxCpConfigStorage().getAgentId(), a -> a)); + } + + private WxCpMessageRouter newRouter(WxCpService wxCpService) { + final val newRouter = new WxCpMessageRouter(wxCpService); + + // 记录所有事件的日志 (异步执行) + newRouter.rule().handler(this.logHandler).next(); + + // 自定义菜单事件 + newRouter.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT) + .event(WxConsts.MenuButtonType.CLICK).handler(this.menuHandler).end(); + + // 点击菜单链接事件(这里使用了一个空的处理器,可以根据自己需要进行扩展) + newRouter.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT) + .event(WxConsts.MenuButtonType.VIEW).handler(this.nullHandler).end(); + + // 关注事件 + newRouter.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT) + .event(WxConsts.EventType.SUBSCRIBE).handler(this.subscribeHandler) + .end(); + + // 取消关注事件 + newRouter.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT) + .event(WxConsts.EventType.UNSUBSCRIBE) + .handler(this.unsubscribeHandler).end(); + + // 上报地理位置事件 + newRouter.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT) + .event(WxConsts.EventType.LOCATION).handler(this.locationHandler) + .end(); + + // 接收地理位置消息 + newRouter.rule().async(false).msgType(WxConsts.XmlMsgType.LOCATION) + .handler(this.locationHandler).end(); + + // 扫码事件(这里使用了一个空的处理器,可以根据自己需要进行扩展) + newRouter.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT) + .event(WxConsts.EventType.SCAN).handler(this.nullHandler).end(); + + newRouter.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT) + .event(WxCpConsts.EventType.CHANGE_CONTACT).handler(new ContactChangeHandler()).end(); + + newRouter.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT) + .event(WxCpConsts.EventType.ENTER_AGENT).handler(new EnterAgentHandler()).end(); + + // 默认 + newRouter.rule().async(false).handler(this.msgHandler).end(); + + return newRouter; + } + +} +``` +4.其他请移步wiki:[GitHub wiki](https://github.com/Wechat-Group/WxJava/wiki) diff --git a/others/weixin-java-osgi/pom.xml b/others/weixin-java-osgi/pom.xml index 107a78b4a2..b8531da88d 100644 --- a/others/weixin-java-osgi/pom.xml +++ b/others/weixin-java-osgi/pom.xml @@ -6,7 +6,7 @@ com.github.binarywang wx-java - 2.6.0 + 4.6.0 weixin-java-osgi @@ -28,7 +28,7 @@ com.thoughtworks.xstream xstream - 1.4.7 + 1.4.21 provided diff --git a/pom.xml b/pom.xml index 930c7ce901..d7d93322b5 100644 --- a/pom.xml +++ b/pom.xml @@ -1,12 +1,9 @@ - + 4.0.0 com.github.binarywang wx-java - 3.6.0 + 4.8.0 pom WxJava - Weixin/Wechat Java SDK 微信开发Java SDK @@ -15,7 +12,7 @@ The Apache License, Version 2.0 - http://www.apache.org/licenses/LICENSE-2.0.txt + https://www.apache.org/licenses/LICENSE-2.0.txt @@ -90,6 +87,26 @@ liuxinghao1988@gmail.com https://github.com/howardliu-cn + + huangxiaoming + huangxm129@163.com + https://github.com/huangxm129 + + + xiaohe + xiaohe@53jy.net + https://github.com/xiaohe-53 + + + Wang_Wong + wangkaikate@163.com + https://github.com/0katekate0 + + + Bincent + hongbin.hsu@qq.com + https://gitee.com/bincent + @@ -99,46 +116,56 @@ + weixin-graal weixin-java-common weixin-java-cp weixin-java-mp weixin-java-pay weixin-java-miniapp weixin-java-open + weixin-java-qidian + weixin-java-channel spring-boot-starters + solon-plugins - 1.7 - 1.7 + 1.8 + 1.8 UTF-8 - 4.5 - 9.4.17.v20190418 + 4.5.13 + 5.5.2 + 9.4.57.v20241219 - com.github.binarywang qrcode-utils - 1.1 + 1.3 - org.jodd jodd-http - 3.7.1 + 6.3.0 provided com.squareup.okhttp3 okhttp - 3.7.0 + 4.12.0 provided + + + org.apache.httpcomponents.client5 + httpclient5 + ${httpclient5.version} + + org.apache.httpcomponents httpclient @@ -152,69 +179,86 @@ commons-codec commons-codec - 1.10 + 1.13 commons-io commons-io - 2.5 + 2.14.0 org.apache.commons commons-lang3 - 3.5 + 3.18.0 org.slf4j slf4j-api - 1.7.24 + 1.7.30 com.thoughtworks.xstream xstream - 1.4.11 + 1.4.21 - com.google.guava guava - 20.0 + 33.3.1-jre com.google.code.gson gson - 2.8.0 + 2.13.1 + + + com.fasterxml.jackson + jackson-bom + 2.18.4 + pom + import joda-time joda-time - 2.9.7 + 2.10.6 test ch.qos.logback logback-classic - 1.1.11 + 1.3.12 test com.google.inject guice - 3.0 + 4.2.3 test org.testng testng - 6.10 + 7.5.1 + test + + + guice + com.google.inject + + + org.yaml + snakeyaml + + org.mockito - mockito-all - 1.9.5 + mockito-core + 5.14.2 test @@ -235,33 +279,67 @@ 3.0.0 test + + com.github.dreamhead + moco-runner + 1.1.0 + test + redis.clients jedis - 2.9.0 + 3.3.0 + provided + + + com.github.jedis-lock + jedis-lock + 1.0.0 + provided + + + org.redisson + redisson + 3.23.3 + true + provided + + + com.fasterxml.jackson.core + jackson-core + + + org.jodd + jodd-core + + + org.reactivestreams + reactive-streams + + + + + org.springframework.data + spring-data-redis + 2.3.3.RELEASE + true provided org.projectlombok lombok - 1.18.8 + 1.18.30 provided + + org.bouncycastle + bcpkix-jdk18on + 1.80 + - - - ossrh - https://oss.sonatype.org/content/repositories/snapshots - - - ossrh - https://oss.sonatype.org/service/local/staging/deploy/maven2/ - - - doclint-java8-disable @@ -280,7 +358,7 @@ org.apache.maven.plugins maven-source-plugin - 2.2.1 + 3.1.0 attach-sources @@ -311,7 +389,7 @@ org.apache.maven.plugins maven-gpg-plugin - 1.6 + 3.1.0 sign-artifacts @@ -325,6 +403,14 @@ + + + native-image + + false + + + @@ -343,14 +429,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 @@ -384,6 +470,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 new file mode 100644 index 0000000000..d0ca564c24 --- /dev/null +++ b/solon-plugins/pom.xml @@ -0,0 +1,52 @@ + + + 4.0.0 + + com.github.binarywang + wx-java + 4.8.0 + + pom + wx-java-solon-plugins + WxJava - Solon Plugins + WxJava 各个模块的 Solon Plugin + + + 3.2.0 + + + + wx-java-miniapp-multi-solon-plugin + wx-java-miniapp-solon-plugin + wx-java-mp-multi-solon-plugin + wx-java-mp-solon-plugin + wx-java-pay-solon-plugin + wx-java-open-solon-plugin + wx-java-qidian-solon-plugin + wx-java-cp-multi-solon-plugin + wx-java-cp-solon-plugin + wx-java-channel-solon-plugin + wx-java-channel-multi-solon-plugin + + + + + org.noear + solon + ${solon.version} + + + org.projectlombok + lombok + provided + + + org.noear + solon-test + ${solon.version} + test + + + diff --git a/solon-plugins/wx-java-channel-multi-solon-plugin/README.md b/solon-plugins/wx-java-channel-multi-solon-plugin/README.md new file mode 100644 index 0000000000..6285f54953 --- /dev/null +++ b/solon-plugins/wx-java-channel-multi-solon-plugin/README.md @@ -0,0 +1,111 @@ +# wx-java-channel-multi-solon-plugin + +## 快速开始 + +1. 引入依赖 + ```xml + + + com.github.binarywang + wx-java-channel-multi-solon-plugin + ${version} + + + + + redis.clients + jedis + ${jedis.version} + + + + + org.redisson + redisson + ${redisson.version} + + + ``` +2. 添加配置(app.properties) + ```properties + # 视频号配置 + ## 应用 1 配置(必填) + wx.channel.apps.tenantId1.app-id=@appId + wx.channel.apps.tenantId1.secret=@secret + ## 选填 + wx.channel.apps.tenantId1.use-stable-access-token=false + wx.channel.apps.tenantId1.token= + wx.channel.apps.tenantId1.aes-key= + ## 应用 2 配置(必填) + wx.channel.apps.tenantId2.app-id=@appId + wx.channel.apps.tenantId2.secret=@secret + ## 选填 + wx.channel.apps.tenantId2.use-stable-access-token=false + wx.channel.apps.tenantId2.token= + wx.channel.apps.tenantId2.aes-key= + + # ConfigStorage 配置(选填) + ## 配置类型: memory(默认), jedis, redisson, redis_template + wx.channel.config-storage.type=memory + ## 相关redis前缀配置: wx:channel:multi(默认) + wx.channel.config-storage.key-prefix=wx:channel:multi + 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. 自动注入的类型:`WxChannelMultiServices` + +4. 使用样例 + + ```java + import com.binarywang.solon.wxjava.channel.service.WxChannelMultiServices; + import me.chanjar.weixin.channel.api.WxChannelService; + import me.chanjar.weixin.channel.api.WxFinderLiveService; + import me.chanjar.weixin.channel.bean.lead.component.response.FinderAttrResponse; + import me.chanjar.weixin.common.error.WxErrorException; + import org.noear.solon.annotation.Component; + import org.noear.solon.annotation.Inject; + + @Component + public class DemoService { + @Inject + private WxChannelMultiServices wxChannelMultiServices; + + public void test() throws WxErrorException { + // 应用 1 的 WxChannelService + WxChannelService wxChannelService1 = wxChannelMultiServices.getWxChannelService("tenantId1"); + WxFinderLiveService finderLiveService = wxChannelService1.getFinderLiveService(); + FinderAttrResponse response1 = finderLiveService.getFinderAttrByAppid(); + // todo ... + + // 应用 2 的 WxChannelService + WxChannelService wxChannelService2 = wxChannelMultiServices.getWxChannelService("tenantId2"); + WxFinderLiveService finderLiveService2 = wxChannelService2.getFinderLiveService(); + FinderAttrResponse response2 = finderLiveService2.getFinderAttrByAppid(); + // todo ... + + // 应用 3 的 WxChannelService + WxChannelService wxChannelService3 = wxChannelMultiServices.getWxChannelService("tenantId3"); + // 判断是否为空 + if (wxChannelService3 == null) { + // todo wxChannelService3 为空,请先配置 tenantId3 微信视频号应用参数 + return; + } + WxFinderLiveService finderLiveService3 = wxChannelService3.getFinderLiveService(); + FinderAttrResponse response3 = finderLiveService3.getFinderAttrByAppid(); + // todo ... + } + } + ``` diff --git a/solon-plugins/wx-java-channel-multi-solon-plugin/pom.xml b/solon-plugins/wx-java-channel-multi-solon-plugin/pom.xml new file mode 100644 index 0000000000..995ecbd532 --- /dev/null +++ b/solon-plugins/wx-java-channel-multi-solon-plugin/pom.xml @@ -0,0 +1,43 @@ + + + + wx-java-solon-plugins + com.github.binarywang + 4.8.0 + + 4.0.0 + + wx-java-channel-multi-solon-plugin + WxJava - Solon Plugin for Channel::支持多账号配置 + 微信视频号开发的 Solon Plugin::支持多账号配置 + + + + com.github.binarywang + weixin-java-channel + ${project.version} + + + redis.clients + jedis + provided + + + org.redisson + redisson + provided + + + org.jodd + jodd-http + provided + + + com.squareup.okhttp3 + okhttp + provided + + + 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 new file mode 100644 index 0000000000..eb80b5f7f3 --- /dev/null +++ b/solon-plugins/wx-java-channel-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/channel/configuration/services/AbstractWxChannelConfiguration.java @@ -0,0 +1,146 @@ +package com.binarywang.solon.wxjava.channel.configuration.services; + +import com.binarywang.solon.wxjava.channel.enums.HttpClientType; +import com.binarywang.solon.wxjava.channel.properties.WxChannelMultiProperties; +import com.binarywang.solon.wxjava.channel.properties.WxChannelSingleProperties; +import com.binarywang.solon.wxjava.channel.service.WxChannelMultiServices; +import com.binarywang.solon.wxjava.channel.service.WxChannelMultiServicesImpl; +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; +import me.chanjar.weixin.channel.config.impl.WxChannelDefaultConfigImpl; +import org.apache.commons.lang3.StringUtils; + +import java.util.Collection; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * WxChannelConfigStorage 抽象配置类 + * + * @author Winnie 2024/9/13 + * @author noear + */ +@RequiredArgsConstructor +@Slf4j +public abstract class AbstractWxChannelConfiguration { + protected WxChannelMultiServices wxChannelMultiServices(WxChannelMultiProperties wxChannelMultiProperties) { + Map appsMap = wxChannelMultiProperties.getApps(); + if (appsMap == null || appsMap.isEmpty()) { + log.warn("微信视频号应用参数未配置,通过 WxChannelMultiServices#getWxChannelService(\"tenantId\")获取实例将返回空"); + return new WxChannelMultiServicesImpl(); + } + /** + * 校验 appId 是否唯一,避免使用 redis 缓存 token、ticket 时错乱。 + * + * 查看 {@link me.chanjar.weixin.channel.config.impl.WxChannelRedisConfigImpl#setAppid(String)} + */ + Collection apps = appsMap.values(); + if (apps.size() > 1) { + // 校验 appId 是否唯一 + boolean multi = apps.stream() + // 没有 appId,如果不判断是否为空,这里会报 NPE 异常 + .collect(Collectors.groupingBy(c -> c.getAppId() == null ? 0 : c.getAppId(), Collectors.counting())) + .entrySet().stream().anyMatch(e -> e.getValue() > 1); + if (multi) { + throw new RuntimeException("请确保微信视频号配置 appId 的唯一性"); + } + } + WxChannelMultiServicesImpl services = new WxChannelMultiServicesImpl(); + + Set> entries = appsMap.entrySet(); + for (Map.Entry entry : entries) { + String tenantId = entry.getKey(); + WxChannelSingleProperties wxChannelSingleProperties = entry.getValue(); + WxChannelDefaultConfigImpl storage = this.wxChannelConfigStorage(wxChannelMultiProperties); + this.configApp(storage, wxChannelSingleProperties); + this.configHttp(storage, wxChannelMultiProperties.getConfigStorage()); + WxChannelService wxChannelService = this.wxChannelService(storage, wxChannelMultiProperties); + services.addWxChannelService(tenantId, wxChannelService); + } + return services; + } + + /** + * 配置 WxChannelDefaultConfigImpl + * + * @param wxChannelMultiProperties 参数 + * @return WxChannelDefaultConfigImpl + */ + protected abstract WxChannelDefaultConfigImpl wxChannelConfigStorage(WxChannelMultiProperties wxChannelMultiProperties); + + public WxChannelService wxChannelService(WxChannelConfig wxChannelConfig, WxChannelMultiProperties wxChannelMultiProperties) { + WxChannelMultiProperties.ConfigStorage storage = wxChannelMultiProperties.getConfigStorage(); + HttpClientType httpClientType = storage.getHttpClientType(); + WxChannelService wxChannelService; + switch (httpClientType) { +// case OK_HTTP: +// wxChannelService = new WxChannelServiceOkHttpImpl(false, false); +// break; + case HTTP_CLIENT: + wxChannelService = new WxChannelServiceHttpClientImpl(); + break; + case HTTP_COMPONENTS: + wxChannelService = new WxChannelServiceHttpComponentsImpl(); + break; + default: + wxChannelService = new WxChannelServiceImpl(); + break; + } + + wxChannelService.setConfig(wxChannelConfig); + int maxRetryTimes = storage.getMaxRetryTimes(); + if (maxRetryTimes < 0) { + maxRetryTimes = 0; + } + int retrySleepMillis = storage.getRetrySleepMillis(); + if (retrySleepMillis < 0) { + retrySleepMillis = 1000; + } + wxChannelService.setRetrySleepMillis(retrySleepMillis); + wxChannelService.setMaxRetryTimes(maxRetryTimes); + return wxChannelService; + } + + private void configApp(WxChannelDefaultConfigImpl config, WxChannelSingleProperties wxChannelSingleProperties) { + String appId = wxChannelSingleProperties.getAppId(); + String appSecret = wxChannelSingleProperties.getSecret(); + String token = wxChannelSingleProperties.getToken(); + String aesKey = wxChannelSingleProperties.getAesKey(); + boolean useStableAccessToken = wxChannelSingleProperties.isUseStableAccessToken(); + + config.setAppid(appId); + config.setSecret(appSecret); + if (StringUtils.isNotBlank(token)) { + config.setToken(token); + } + if (StringUtils.isNotBlank(aesKey)) { + config.setAesKey(aesKey); + } + config.setStableAccessToken(useStableAccessToken); + } + + private void configHttp(WxChannelDefaultConfigImpl config, WxChannelMultiProperties.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/solon-plugins/wx-java-channel-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/channel/configuration/services/WxChannelInJedisConfiguration.java b/solon-plugins/wx-java-channel-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/channel/configuration/services/WxChannelInJedisConfiguration.java new file mode 100644 index 0000000000..68afc13320 --- /dev/null +++ b/solon-plugins/wx-java-channel-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/channel/configuration/services/WxChannelInJedisConfiguration.java @@ -0,0 +1,77 @@ +package com.binarywang.solon.wxjava.channel.configuration.services; + +import com.binarywang.solon.wxjava.channel.properties.WxChannelMultiProperties; +import com.binarywang.solon.wxjava.channel.properties.WxChannelMultiRedisProperties; +import com.binarywang.solon.wxjava.channel.service.WxChannelMultiServices; +import lombok.RequiredArgsConstructor; +import me.chanjar.weixin.channel.config.impl.WxChannelDefaultConfigImpl; +import me.chanjar.weixin.channel.config.impl.WxChannelRedisConfigImpl; +import me.chanjar.weixin.common.redis.JedisWxRedisOps; +import org.apache.commons.lang3.StringUtils; +import org.noear.solon.annotation.Bean; +import org.noear.solon.annotation.Condition; +import org.noear.solon.annotation.Configuration; +import org.noear.solon.core.AppContext; +import redis.clients.jedis.JedisPool; +import redis.clients.jedis.JedisPoolConfig; + +/** + * 自动装配基于 jedis 策略配置 + * + * @author Winnie 2024/9/13 + * @author noear + */ +@Configuration +@Condition( + onProperty = "${"+WxChannelMultiProperties.PREFIX + ".configStorage.type} = jedis", + onClass = JedisPool.class +) +@RequiredArgsConstructor +public class WxChannelInJedisConfiguration extends AbstractWxChannelConfiguration { + private final WxChannelMultiProperties wxChannelMultiProperties; + private final AppContext applicationContext; + + @Bean + public WxChannelMultiServices wxChannelMultiServices() { + return this.wxChannelMultiServices(wxChannelMultiProperties); + } + + @Override + protected WxChannelDefaultConfigImpl wxChannelConfigStorage(WxChannelMultiProperties wxChannelMultiProperties) { + return this.configRedis(wxChannelMultiProperties); + } + + private WxChannelDefaultConfigImpl configRedis(WxChannelMultiProperties wxChannelMultiProperties) { + WxChannelMultiRedisProperties wxChannelMultiRedisProperties = wxChannelMultiProperties.getConfigStorage().getRedis(); + JedisPool jedisPool; + if (wxChannelMultiRedisProperties != null && StringUtils.isNotEmpty(wxChannelMultiRedisProperties.getHost())) { + jedisPool = getJedisPool(wxChannelMultiProperties); + } else { + jedisPool = applicationContext.getBean(JedisPool.class); + } + return new WxChannelRedisConfigImpl(new JedisWxRedisOps(jedisPool), wxChannelMultiProperties.getConfigStorage().getKeyPrefix()); + } + + private JedisPool getJedisPool(WxChannelMultiProperties wxChannelMultiProperties) { + WxChannelMultiProperties.ConfigStorage storage = wxChannelMultiProperties.getConfigStorage(); + WxChannelMultiRedisProperties redis = storage.getRedis(); + + JedisPoolConfig config = new JedisPoolConfig(); + if (redis.getMaxActive() != null) { + config.setMaxTotal(redis.getMaxActive()); + } + if (redis.getMaxIdle() != null) { + config.setMaxIdle(redis.getMaxIdle()); + } + if (redis.getMaxWaitMillis() != null) { + config.setMaxWaitMillis(redis.getMaxWaitMillis()); + } + if (redis.getMinIdle() != null) { + config.setMinIdle(redis.getMinIdle()); + } + config.setTestOnBorrow(true); + config.setTestWhileIdle(true); + + return new JedisPool(config, redis.getHost(), redis.getPort(), redis.getTimeout(), redis.getPassword(), redis.getDatabase()); + } +} diff --git a/solon-plugins/wx-java-channel-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/channel/configuration/services/WxChannelInMemoryConfiguration.java b/solon-plugins/wx-java-channel-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/channel/configuration/services/WxChannelInMemoryConfiguration.java new file mode 100644 index 0000000000..71cd5ca33c --- /dev/null +++ b/solon-plugins/wx-java-channel-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/channel/configuration/services/WxChannelInMemoryConfiguration.java @@ -0,0 +1,40 @@ +package com.binarywang.solon.wxjava.channel.configuration.services; + +import com.binarywang.solon.wxjava.channel.properties.WxChannelMultiProperties; +import com.binarywang.solon.wxjava.channel.service.WxChannelMultiServices; +import lombok.RequiredArgsConstructor; +import me.chanjar.weixin.channel.config.impl.WxChannelDefaultConfigImpl; +import org.noear.solon.annotation.Bean; +import org.noear.solon.annotation.Condition; +import org.noear.solon.annotation.Configuration; +import redis.clients.jedis.JedisPool; + +/** + * 自动装配基于内存策略配置 + * + * @author Winnie 2024/9/13 + * @author noear + */ +@Configuration +@Condition( + onProperty = "${"+WxChannelMultiProperties.PREFIX + ".configStorage.type} = memory", + onClass = JedisPool.class +) +@RequiredArgsConstructor +public class WxChannelInMemoryConfiguration extends AbstractWxChannelConfiguration { + private final WxChannelMultiProperties wxChannelMultiProperties; + + @Bean + public WxChannelMultiServices wxChannelMultiServices() { + return this.wxChannelMultiServices(wxChannelMultiProperties); + } + + @Override + protected WxChannelDefaultConfigImpl wxChannelConfigStorage(WxChannelMultiProperties wxChannelMultiProperties) { + return this.configInMemory(); + } + + private WxChannelDefaultConfigImpl configInMemory() { + return new WxChannelDefaultConfigImpl(); + } +} diff --git a/solon-plugins/wx-java-channel-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/channel/configuration/services/WxChannelInRedissonConfiguration.java b/solon-plugins/wx-java-channel-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/channel/configuration/services/WxChannelInRedissonConfiguration.java new file mode 100644 index 0000000000..fce6a735ea --- /dev/null +++ b/solon-plugins/wx-java-channel-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/channel/configuration/services/WxChannelInRedissonConfiguration.java @@ -0,0 +1,65 @@ +package com.binarywang.solon.wxjava.channel.configuration.services; + +import com.binarywang.solon.wxjava.channel.properties.WxChannelMultiProperties; +import com.binarywang.solon.wxjava.channel.properties.WxChannelMultiRedisProperties; +import com.binarywang.solon.wxjava.channel.service.WxChannelMultiServices; +import lombok.RequiredArgsConstructor; +import me.chanjar.weixin.channel.config.impl.WxChannelDefaultConfigImpl; +import me.chanjar.weixin.channel.config.impl.WxChannelRedissonConfigImpl; +import org.apache.commons.lang3.StringUtils; +import org.noear.solon.annotation.Bean; +import org.noear.solon.annotation.Condition; +import org.noear.solon.annotation.Configuration; +import org.noear.solon.core.AppContext; +import org.redisson.Redisson; +import org.redisson.api.RedissonClient; +import org.redisson.config.Config; +import org.redisson.config.TransportMode; + +/** + * 自动装配基于 redisson 策略配置 + * + * @author Winnie 2024/9/13 + * @author noear + */ +@Configuration +@Condition( + onProperty = "${"+WxChannelMultiProperties.PREFIX + ".configStorage.type} = redisson", + onClass = Redisson.class +) +@RequiredArgsConstructor +public class WxChannelInRedissonConfiguration extends AbstractWxChannelConfiguration { + private final WxChannelMultiProperties wxChannelMultiProperties; + private final AppContext applicationContext; + + @Bean + public WxChannelMultiServices wxChannelMultiServices() { + return this.wxChannelMultiServices(wxChannelMultiProperties); + } + + @Override + protected WxChannelDefaultConfigImpl wxChannelConfigStorage(WxChannelMultiProperties wxChannelMultiProperties) { + return this.configRedisson(wxChannelMultiProperties); + } + + private WxChannelDefaultConfigImpl configRedisson(WxChannelMultiProperties wxChannelMultiProperties) { + WxChannelMultiRedisProperties redisProperties = wxChannelMultiProperties.getConfigStorage().getRedis(); + RedissonClient redissonClient; + if (redisProperties != null && StringUtils.isNotEmpty(redisProperties.getHost())) { + redissonClient = getRedissonClient(wxChannelMultiProperties); + } else { + redissonClient = applicationContext.getBean(RedissonClient.class); + } + return new WxChannelRedissonConfigImpl(redissonClient, wxChannelMultiProperties.getConfigStorage().getKeyPrefix()); + } + + private RedissonClient getRedissonClient(WxChannelMultiProperties wxChannelMultiProperties) { + WxChannelMultiProperties.ConfigStorage storage = wxChannelMultiProperties.getConfigStorage(); + WxChannelMultiRedisProperties 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/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 new file mode 100644 index 0000000000..c34533c6d1 --- /dev/null +++ b/solon-plugins/wx-java-channel-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/channel/enums/HttpClientType.java @@ -0,0 +1,23 @@ +package com.binarywang.solon.wxjava.channel.enums; + +/** + * httpclient类型 + * + * @author Winnie + * @date 2024/9/13 + */ +public enum HttpClientType { + /** + * HttpClient + */ + HTTP_CLIENT, + /** + * HttpComponents + */ + HTTP_COMPONENTS + // WxChannelServiceOkHttpImpl 实现经测试无法正常完成业务固暂不支持OK_HTTP方式 +// /** +// * OkHttp. +// */ +// OK_HTTP, +} diff --git a/solon-plugins/wx-java-channel-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/channel/enums/StorageType.java b/solon-plugins/wx-java-channel-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/channel/enums/StorageType.java new file mode 100644 index 0000000000..a1b710cd2a --- /dev/null +++ b/solon-plugins/wx-java-channel-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/channel/enums/StorageType.java @@ -0,0 +1,26 @@ +package com.binarywang.solon.wxjava.channel.enums; + +/** + * storage类型 + * + * @author Winnie + * @date 2024/9/13 + */ +public enum StorageType { + /** + * 内存 + */ + MEMORY, + /** + * redis(JedisClient) + */ + JEDIS, + /** + * redis(Redisson) + */ + REDISSON, + /** + * redis(RedisTemplate) + */ + REDIS_TEMPLATE +} diff --git a/solon-plugins/wx-java-channel-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/channel/integration/WxChannelMultiPluginImpl.java b/solon-plugins/wx-java-channel-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/channel/integration/WxChannelMultiPluginImpl.java new file mode 100644 index 0000000000..3b84794eac --- /dev/null +++ b/solon-plugins/wx-java-channel-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/channel/integration/WxChannelMultiPluginImpl.java @@ -0,0 +1,25 @@ +package com.binarywang.solon.wxjava.channel.integration; + +import com.binarywang.solon.wxjava.channel.configuration.services.WxChannelInJedisConfiguration; +import com.binarywang.solon.wxjava.channel.configuration.services.WxChannelInMemoryConfiguration; +import com.binarywang.solon.wxjava.channel.configuration.services.WxChannelInRedissonConfiguration; +import com.binarywang.solon.wxjava.channel.properties.WxChannelMultiProperties; +import org.noear.solon.core.AppContext; +import org.noear.solon.core.Plugin; + +/** + * 微信视频号自动注册 + * + * @author Winnie 2024/9/13 + * @author noear 2024/10/9 created + */ +public class WxChannelMultiPluginImpl implements Plugin { + @Override + public void start(AppContext context) throws Throwable { + context.beanMake(WxChannelMultiProperties.class); + + context.beanMake(WxChannelInJedisConfiguration.class); + context.beanMake(WxChannelInMemoryConfiguration.class); + context.beanMake(WxChannelInRedissonConfiguration.class); + } +} diff --git a/solon-plugins/wx-java-channel-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/channel/properties/WxChannelMultiProperties.java b/solon-plugins/wx-java-channel-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/channel/properties/WxChannelMultiProperties.java new file mode 100644 index 0000000000..ca99e522b9 --- /dev/null +++ b/solon-plugins/wx-java-channel-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/channel/properties/WxChannelMultiProperties.java @@ -0,0 +1,96 @@ +package com.binarywang.solon.wxjava.channel.properties; + +import com.binarywang.solon.wxjava.channel.enums.HttpClientType; +import com.binarywang.solon.wxjava.channel.enums.StorageType; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.noear.solon.annotation.Configuration; +import org.noear.solon.annotation.Inject; + +import java.io.Serializable; +import java.util.HashMap; +import java.util.Map; + +/** + * 微信多视频号接入相关配置属性 + * + * @author Winnie + * @date 2024/9/13 + */ +@Data +@NoArgsConstructor +@Configuration +@Inject("${" + WxChannelMultiProperties.PREFIX +"}") +public class WxChannelMultiProperties implements Serializable { + private static final long serialVersionUID = - 8361973118805546037L; + public static final String PREFIX = "wx.channel"; + + private Map apps = new HashMap<>(); + + /** + * 存储策略 + */ + private final ConfigStorage configStorage = new ConfigStorage(); + + @Data + @NoArgsConstructor + public static class ConfigStorage implements Serializable { + private static final long serialVersionUID = - 5152619132544179942L; + + /** + * 存储类型. + */ + private StorageType type = StorageType.MEMORY; + + /** + * 指定key前缀. + */ + private String keyPrefix = "wx:channel:multi"; + + /** + * redis连接配置. + */ + private final WxChannelMultiRedisProperties redis = new WxChannelMultiRedisProperties(); + + /** + * http客户端类型. + */ + private HttpClientType httpClientType = HttpClientType.HTTP_COMPONENTS; + + /** + * http代理主机. + */ + private String httpProxyHost; + + /** + * http代理端口. + */ + private Integer httpProxyPort; + + /** + * http代理用户名. + */ + private String httpProxyUsername; + + /** + * http代理密码. + */ + private String httpProxyPassword; + + /** + * http 请求最大重试次数 + * + *

{@link me.chanjar.weixin.channel.api.WxChannelService#setMaxRetryTimes(int)}

+ *

{@link me.chanjar.weixin.channel.api.impl.BaseWxChannelServiceImpl#setMaxRetryTimes(int)}

+ */ + private int maxRetryTimes = 5; + + /** + * http 请求重试间隔 + * + *

{@link me.chanjar.weixin.channel.api.WxChannelService#setRetrySleepMillis(int)}

+ *

{@link me.chanjar.weixin.channel.api.impl.BaseWxChannelServiceImpl#setRetrySleepMillis(int)}

+ */ + private int retrySleepMillis = 1000; + } +} diff --git a/solon-plugins/wx-java-channel-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/channel/properties/WxChannelMultiRedisProperties.java b/solon-plugins/wx-java-channel-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/channel/properties/WxChannelMultiRedisProperties.java new file mode 100644 index 0000000000..36c649b311 --- /dev/null +++ b/solon-plugins/wx-java-channel-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/channel/properties/WxChannelMultiRedisProperties.java @@ -0,0 +1,63 @@ +package com.binarywang.solon.wxjava.channel.properties; + +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * Redis配置 + * + * @author Winnie + * @date 2024/9/13 + */ +@Data +@NoArgsConstructor +public class WxChannelMultiRedisProperties implements Serializable { + private static final long serialVersionUID = 9061055444734277357L; + + /** + * 主机地址. + */ + private String host = "127.0.0.1"; + + /** + * 端口号. + */ + private int port = 6379; + + /** + * 密码. + */ + private String password; + + /** + * 超时. + */ + private int timeout = 2000; + + /** + * 数据库. + */ + private int database = 0; + + /** + * 最大活动连接数 + */ + private Integer maxActive; + + /** + * 最大空闲连接数 + */ + private Integer maxIdle; + + /** + * 最小空闲连接数 + */ + private Integer minIdle; + + /** + * 最大等待时间 + */ + private Integer maxWaitMillis; +} diff --git a/solon-plugins/wx-java-channel-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/channel/properties/WxChannelSingleProperties.java b/solon-plugins/wx-java-channel-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/channel/properties/WxChannelSingleProperties.java new file mode 100644 index 0000000000..438c3ecb03 --- /dev/null +++ b/solon-plugins/wx-java-channel-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/channel/properties/WxChannelSingleProperties.java @@ -0,0 +1,43 @@ +package com.binarywang.solon.wxjava.channel.properties; + +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * 微信视频号相关配置属性 + * + * @author Winnie + * @date 2024/9/13 + */ +@Data +@NoArgsConstructor +public class WxChannelSingleProperties implements Serializable { + private static final long serialVersionUID = 5306630351265124825L; + + /** + * 设置微信视频号的 appid. + */ + private String appId; + + /** + * 设置微信视频号的 secret. + */ + private String secret; + + /** + * 设置微信视频号的 token. + */ + private String token; + + /** + * 设置微信视频号的 EncodingAESKey. + */ + private String aesKey; + + /** + * 是否使用稳定版 Access Token + */ + private boolean useStableAccessToken = false; +} diff --git a/solon-plugins/wx-java-channel-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/channel/service/WxChannelMultiServices.java b/solon-plugins/wx-java-channel-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/channel/service/WxChannelMultiServices.java new file mode 100644 index 0000000000..f12461e197 --- /dev/null +++ b/solon-plugins/wx-java-channel-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/channel/service/WxChannelMultiServices.java @@ -0,0 +1,26 @@ +package com.binarywang.solon.wxjava.channel.service; + +import me.chanjar.weixin.channel.api.WxChannelService; + +/** + * 视频号 {@link WxChannelService} 所有实例存放类. + * + * @author Winnie + * @date 2024/9/13 + */ +public interface WxChannelMultiServices { + /** + * 通过租户 Id 获取 WxChannelService + * + * @param tenantId 租户 Id + * @return WxChannelService + */ + WxChannelService getWxChannelService(String tenantId); + + /** + * 根据租户 Id,从列表中移除一个 WxChannelService 实例 + * + * @param tenantId 租户 Id + */ + void removeWxChannelService(String tenantId); +} diff --git a/solon-plugins/wx-java-channel-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/channel/service/WxChannelMultiServicesImpl.java b/solon-plugins/wx-java-channel-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/channel/service/WxChannelMultiServicesImpl.java new file mode 100644 index 0000000000..8420e29d73 --- /dev/null +++ b/solon-plugins/wx-java-channel-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/channel/service/WxChannelMultiServicesImpl.java @@ -0,0 +1,36 @@ +package com.binarywang.solon.wxjava.channel.service; + +import me.chanjar.weixin.channel.api.WxChannelService; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * 视频号 {@link WxChannelMultiServices} 实现 + * + * @author Winnie + * @date 2024/9/13 + */ +public class WxChannelMultiServicesImpl implements WxChannelMultiServices { + private final Map services = new ConcurrentHashMap<>(); + + @Override + public WxChannelService getWxChannelService(String tenantId) { + return this.services.get(tenantId); + } + + /** + * 根据租户 Id,添加一个 WxChannelService 到列表 + * + * @param tenantId 租户 Id + * @param wxChannelService WxChannelService 实例 + */ + public void addWxChannelService(String tenantId, WxChannelService wxChannelService) { + this.services.put(tenantId, wxChannelService); + } + + @Override + public void removeWxChannelService(String tenantId) { + this.services.remove(tenantId); + } +} diff --git a/solon-plugins/wx-java-channel-multi-solon-plugin/src/main/resources/META-INF/solon/wx-java-multi-channel-solon-plugin.properties b/solon-plugins/wx-java-channel-multi-solon-plugin/src/main/resources/META-INF/solon/wx-java-multi-channel-solon-plugin.properties new file mode 100644 index 0000000000..b9fc24b210 --- /dev/null +++ b/solon-plugins/wx-java-channel-multi-solon-plugin/src/main/resources/META-INF/solon/wx-java-multi-channel-solon-plugin.properties @@ -0,0 +1,2 @@ +solon.plugin=com.binarywang.solon.wxjava.channel.integration.WxChannelMultiPluginImpl +solon.plugin.priority=10 diff --git a/solon-plugins/wx-java-channel-multi-solon-plugin/src/test/java/features/test/LoadTest.java b/solon-plugins/wx-java-channel-multi-solon-plugin/src/test/java/features/test/LoadTest.java new file mode 100644 index 0000000000..d049f5a51a --- /dev/null +++ b/solon-plugins/wx-java-channel-multi-solon-plugin/src/test/java/features/test/LoadTest.java @@ -0,0 +1,15 @@ +package features.test; + +import org.junit.jupiter.api.Test; +import org.noear.solon.test.SolonTest; + +/** + * @author noear 2024/9/4 created + */ +@SolonTest +public class LoadTest { + @Test + public void load(){ + + } +} diff --git a/solon-plugins/wx-java-channel-multi-solon-plugin/src/test/resources/app.properties b/solon-plugins/wx-java-channel-multi-solon-plugin/src/test/resources/app.properties new file mode 100644 index 0000000000..c90a560a82 --- /dev/null +++ b/solon-plugins/wx-java-channel-multi-solon-plugin/src/test/resources/app.properties @@ -0,0 +1,36 @@ +# 视频号配置 +## 应用 1 配置(必填) +wx.channel.apps.tenantId1.app-id=appId +wx.channel.apps.tenantId1.secret=secret +## 选填 +wx.channel.apps.tenantId1.use-stable-access-token=false +wx.channel.apps.tenantId1.token= +wx.channel.apps.tenantId1.aes-key= +## 应用 2 配置(必填) +wx.channel.apps.tenantId2.app-id=@appId +wx.channel.apps.tenantId2.secret=@secret +## 选填 +wx.channel.apps.tenantId2.use-stable-access-token=false +wx.channel.apps.tenantId2.token= +wx.channel.apps.tenantId2.aes-key= + +# ConfigStorage 配置(选填) +## 配置类型: memory(默认), jedis, redisson, redis_template +wx.channel.config-storage.type=memory +## 相关redis前缀配置: wx:channel:multi(默认) +wx.channel.config-storage.key-prefix=wx:channel:multi +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 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 new file mode 100644 index 0000000000..b2ca356692 --- /dev/null +++ b/solon-plugins/wx-java-channel-solon-plugin/pom.xml @@ -0,0 +1,31 @@ + + + wx-java-solon-plugins + com.github.binarywang + 4.8.0 + + 4.0.0 + + wx-java-channel-solon-plugin + WxJava - Solon Plugin for Channel + 微信视频号开发的 Solon Plugin + + + + com.github.binarywang + weixin-java-channel + ${project.version} + + + redis.clients + jedis + provided + + + org.redisson + redisson + provided + + + diff --git a/solon-plugins/wx-java-channel-solon-plugin/src/main/java/com/binarywang/solon/wxjava/channel/config/WxChannelServiceAutoConfiguration.java b/solon-plugins/wx-java-channel-solon-plugin/src/main/java/com/binarywang/solon/wxjava/channel/config/WxChannelServiceAutoConfiguration.java new file mode 100644 index 0000000000..9ffccc64bf --- /dev/null +++ b/solon-plugins/wx-java-channel-solon-plugin/src/main/java/com/binarywang/solon/wxjava/channel/config/WxChannelServiceAutoConfiguration.java @@ -0,0 +1,35 @@ +package com.binarywang.solon.wxjava.channel.config; + + +import com.binarywang.solon.wxjava.channel.properties.WxChannelProperties; +import lombok.AllArgsConstructor; +import me.chanjar.weixin.channel.api.WxChannelService; +import me.chanjar.weixin.channel.api.impl.WxChannelServiceImpl; +import me.chanjar.weixin.channel.config.WxChannelConfig; +import org.noear.solon.annotation.Bean; +import org.noear.solon.annotation.Condition; +import org.noear.solon.annotation.Configuration; + +/** + * 微信小程序平台相关服务自动注册 + * + * @author Zeyes + */ +@Configuration +@AllArgsConstructor +public class WxChannelServiceAutoConfiguration { + private final WxChannelProperties properties; + + /** + * Channel Service + * + * @return Channel Service + */ + @Bean + @Condition(onMissingBean=WxChannelService.class, onBean = WxChannelConfig.class) + public WxChannelService wxChannelService(WxChannelConfig wxChannelConfig) { + WxChannelService wxChannelService = new WxChannelServiceImpl(); + wxChannelService.setConfig(wxChannelConfig); + return wxChannelService; + } +} diff --git a/solon-plugins/wx-java-channel-solon-plugin/src/main/java/com/binarywang/solon/wxjava/channel/config/storage/AbstractWxChannelConfigStorageConfiguration.java b/solon-plugins/wx-java-channel-solon-plugin/src/main/java/com/binarywang/solon/wxjava/channel/config/storage/AbstractWxChannelConfigStorageConfiguration.java new file mode 100644 index 0000000000..2df3dbf23f --- /dev/null +++ b/solon-plugins/wx-java-channel-solon-plugin/src/main/java/com/binarywang/solon/wxjava/channel/config/storage/AbstractWxChannelConfigStorageConfiguration.java @@ -0,0 +1,40 @@ +package com.binarywang.solon.wxjava.channel.config.storage; + +import com.binarywang.solon.wxjava.channel.properties.WxChannelProperties; +import me.chanjar.weixin.channel.config.impl.WxChannelDefaultConfigImpl; +import org.apache.commons.lang3.StringUtils; + +/** + * @author Zeyes + */ +public abstract class AbstractWxChannelConfigStorageConfiguration { + + protected WxChannelDefaultConfigImpl config(WxChannelDefaultConfigImpl config, WxChannelProperties properties) { + 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.setStableAccessToken(properties.isUseStableAccessToken()); + + WxChannelProperties.ConfigStorage configStorageProperties = properties.getConfigStorage(); + config.setHttpProxyHost(configStorageProperties.getHttpProxyHost()); + config.setHttpProxyUsername(configStorageProperties.getHttpProxyUsername()); + config.setHttpProxyPassword(configStorageProperties.getHttpProxyPassword()); + if (configStorageProperties.getHttpProxyPort() != null) { + config.setHttpProxyPort(configStorageProperties.getHttpProxyPort()); + } + + int maxRetryTimes = configStorageProperties.getMaxRetryTimes(); + if (configStorageProperties.getMaxRetryTimes() < 0) { + maxRetryTimes = 0; + } + int retrySleepMillis = configStorageProperties.getRetrySleepMillis(); + if (retrySleepMillis < 0) { + retrySleepMillis = 1000; + } + config.setRetrySleepMillis(retrySleepMillis); + config.setMaxRetryTimes(maxRetryTimes); + return config; + } +} diff --git a/solon-plugins/wx-java-channel-solon-plugin/src/main/java/com/binarywang/solon/wxjava/channel/config/storage/WxChannelInJedisConfigStorageConfiguration.java b/solon-plugins/wx-java-channel-solon-plugin/src/main/java/com/binarywang/solon/wxjava/channel/config/storage/WxChannelInJedisConfigStorageConfiguration.java new file mode 100644 index 0000000000..f074241914 --- /dev/null +++ b/solon-plugins/wx-java-channel-solon-plugin/src/main/java/com/binarywang/solon/wxjava/channel/config/storage/WxChannelInJedisConfigStorageConfiguration.java @@ -0,0 +1,74 @@ +package com.binarywang.solon.wxjava.channel.config.storage; + + +import com.binarywang.solon.wxjava.channel.properties.RedisProperties; +import com.binarywang.solon.wxjava.channel.properties.WxChannelProperties; +import lombok.RequiredArgsConstructor; +import me.chanjar.weixin.channel.config.WxChannelConfig; +import me.chanjar.weixin.channel.config.impl.WxChannelRedisConfigImpl; +import me.chanjar.weixin.common.redis.JedisWxRedisOps; +import me.chanjar.weixin.common.redis.WxRedisOps; +import org.apache.commons.lang3.StringUtils; +import org.noear.solon.annotation.Bean; +import org.noear.solon.annotation.Condition; +import org.noear.solon.annotation.Configuration; +import org.noear.solon.core.AppContext; +import redis.clients.jedis.JedisPool; +import redis.clients.jedis.JedisPoolConfig; + +/** + * @author Zeyes + * @author noear + */ +@Configuration +@Condition( + onProperty = "${"+WxChannelProperties.PREFIX + ".configStorage.type} = jedis", + onClass = JedisPool.class +) +@RequiredArgsConstructor +public class WxChannelInJedisConfigStorageConfiguration extends AbstractWxChannelConfigStorageConfiguration { + private final WxChannelProperties properties; + private final AppContext applicationContext; + + @Bean + @Condition(onMissingBean=WxChannelConfig.class) + public WxChannelConfig wxChannelConfig() { + WxChannelRedisConfigImpl config = getWxChannelRedisConfig(); + return this.config(config, properties); + } + + private WxChannelRedisConfigImpl getWxChannelRedisConfig() { + RedisProperties redisProperties = properties.getConfigStorage().getRedis(); + JedisPool jedisPool; + if (redisProperties != null && StringUtils.isNotEmpty(redisProperties.getHost())) { + jedisPool = getJedisPool(); + } else { + jedisPool = applicationContext.getBean(JedisPool.class); + } + WxRedisOps redisOps = new JedisWxRedisOps(jedisPool); + return new WxChannelRedisConfigImpl(redisOps, properties.getConfigStorage().getKeyPrefix()); + } + + private JedisPool getJedisPool() { + WxChannelProperties.ConfigStorage storage = properties.getConfigStorage(); + RedisProperties redis = storage.getRedis(); + + JedisPoolConfig config = new JedisPoolConfig(); + if (redis.getMaxActive() != null) { + config.setMaxTotal(redis.getMaxActive()); + } + if (redis.getMaxIdle() != null) { + config.setMaxIdle(redis.getMaxIdle()); + } + if (redis.getMaxWaitMillis() != null) { + config.setMaxWaitMillis(redis.getMaxWaitMillis()); + } + if (redis.getMinIdle() != null) { + config.setMinIdle(redis.getMinIdle()); + } + config.setTestOnBorrow(true); + config.setTestWhileIdle(true); + + return new JedisPool(config, redis.getHost(), redis.getPort(), redis.getTimeout(), redis.getPassword(), redis.getDatabase()); + } +} diff --git a/solon-plugins/wx-java-channel-solon-plugin/src/main/java/com/binarywang/solon/wxjava/channel/config/storage/WxChannelInMemoryConfigStorageConfiguration.java b/solon-plugins/wx-java-channel-solon-plugin/src/main/java/com/binarywang/solon/wxjava/channel/config/storage/WxChannelInMemoryConfigStorageConfiguration.java new file mode 100644 index 0000000000..a560db29ac --- /dev/null +++ b/solon-plugins/wx-java-channel-solon-plugin/src/main/java/com/binarywang/solon/wxjava/channel/config/storage/WxChannelInMemoryConfigStorageConfiguration.java @@ -0,0 +1,29 @@ +package com.binarywang.solon.wxjava.channel.config.storage; + + +import com.binarywang.solon.wxjava.channel.properties.WxChannelProperties; +import lombok.RequiredArgsConstructor; +import me.chanjar.weixin.channel.config.WxChannelConfig; +import me.chanjar.weixin.channel.config.impl.WxChannelDefaultConfigImpl; +import org.noear.solon.annotation.Bean; +import org.noear.solon.annotation.Condition; +import org.noear.solon.annotation.Configuration; + +/** + * @author Zeyes + */ +@Configuration +@Condition( + onProperty = "${"+WxChannelProperties.PREFIX + ".configStorage.type:memory} = memory" +) +@RequiredArgsConstructor +public class WxChannelInMemoryConfigStorageConfiguration extends AbstractWxChannelConfigStorageConfiguration { + private final WxChannelProperties properties; + + @Bean + @Condition(onMissingBean = WxChannelProperties.class) + public WxChannelConfig wxChannelConfig() { + WxChannelDefaultConfigImpl config = new WxChannelDefaultConfigImpl(); + return this.config(config, properties); + } +} diff --git a/solon-plugins/wx-java-channel-solon-plugin/src/main/java/com/binarywang/solon/wxjava/channel/config/storage/WxChannelInRedissonConfigStorageConfiguration.java b/solon-plugins/wx-java-channel-solon-plugin/src/main/java/com/binarywang/solon/wxjava/channel/config/storage/WxChannelInRedissonConfigStorageConfiguration.java new file mode 100644 index 0000000000..cd4de68f21 --- /dev/null +++ b/solon-plugins/wx-java-channel-solon-plugin/src/main/java/com/binarywang/solon/wxjava/channel/config/storage/WxChannelInRedissonConfigStorageConfiguration.java @@ -0,0 +1,62 @@ +package com.binarywang.solon.wxjava.channel.config.storage; + + +import com.binarywang.solon.wxjava.channel.properties.RedisProperties; +import com.binarywang.solon.wxjava.channel.properties.WxChannelProperties; +import lombok.RequiredArgsConstructor; +import me.chanjar.weixin.channel.config.WxChannelConfig; +import me.chanjar.weixin.channel.config.impl.WxChannelRedissonConfigImpl; +import org.apache.commons.lang3.StringUtils; +import org.noear.solon.annotation.Bean; +import org.noear.solon.annotation.Condition; +import org.noear.solon.annotation.Configuration; +import org.noear.solon.core.AppContext; +import org.redisson.Redisson; +import org.redisson.api.RedissonClient; +import org.redisson.config.Config; +import org.redisson.config.TransportMode; + +/** + * @author Zeyes + */ +@Configuration +@Condition( + onProperty = "${"+WxChannelProperties.PREFIX + ".configStorage.type} = redisson", + onClass = Redisson.class +) +@RequiredArgsConstructor +public class WxChannelInRedissonConfigStorageConfiguration extends AbstractWxChannelConfigStorageConfiguration { + private final WxChannelProperties properties; + private final AppContext applicationContext; + + @Bean + @Condition(onMissingBean=WxChannelConfig.class) + public WxChannelConfig wxChannelConfig() { + WxChannelRedissonConfigImpl config = getWxChannelRedissonConfig(); + return this.config(config, properties); + } + + private WxChannelRedissonConfigImpl getWxChannelRedissonConfig() { + RedisProperties redisProperties = properties.getConfigStorage().getRedis(); + RedissonClient redissonClient; + if (redisProperties != null && StringUtils.isNotEmpty(redisProperties.getHost())) { + redissonClient = getRedissonClient(); + } else { + redissonClient = applicationContext.getBean(RedissonClient.class); + } + return new WxChannelRedissonConfigImpl(redissonClient, properties.getConfigStorage().getKeyPrefix()); + } + + private RedissonClient getRedissonClient() { + WxChannelProperties.ConfigStorage storage = properties.getConfigStorage(); + RedisProperties 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/solon-plugins/wx-java-channel-solon-plugin/src/main/java/com/binarywang/solon/wxjava/channel/enums/HttpClientType.java b/solon-plugins/wx-java-channel-solon-plugin/src/main/java/com/binarywang/solon/wxjava/channel/enums/HttpClientType.java new file mode 100644 index 0000000000..5614f63e86 --- /dev/null +++ b/solon-plugins/wx-java-channel-solon-plugin/src/main/java/com/binarywang/solon/wxjava/channel/enums/HttpClientType.java @@ -0,0 +1,17 @@ +package com.binarywang.solon.wxjava.channel.enums; + +/** + * httpclient类型 + * + * @author Zeyes + */ +public enum HttpClientType { + /** + * HttpClient. + */ + HttpClient, + /** + * HttpComponents. + */ + HttpComponents, +} diff --git a/solon-plugins/wx-java-channel-solon-plugin/src/main/java/com/binarywang/solon/wxjava/channel/enums/StorageType.java b/solon-plugins/wx-java-channel-solon-plugin/src/main/java/com/binarywang/solon/wxjava/channel/enums/StorageType.java new file mode 100644 index 0000000000..976f869438 --- /dev/null +++ b/solon-plugins/wx-java-channel-solon-plugin/src/main/java/com/binarywang/solon/wxjava/channel/enums/StorageType.java @@ -0,0 +1,25 @@ +package com.binarywang.solon.wxjava.channel.enums; + +/** + * storage类型 + * + * @author Zeyes + */ +public enum StorageType { + /** + * 内存 + */ + Memory, + /** + * redis(JedisClient) + */ + Jedis, + /** + * redis(Redisson) + */ + Redisson, + /** + * redis(RedisTemplate) + */ + RedisTemplate +} diff --git a/solon-plugins/wx-java-channel-solon-plugin/src/main/java/com/binarywang/solon/wxjava/channel/integration/WxChannelPluginImpl.java b/solon-plugins/wx-java-channel-solon-plugin/src/main/java/com/binarywang/solon/wxjava/channel/integration/WxChannelPluginImpl.java new file mode 100644 index 0000000000..0377bc6f41 --- /dev/null +++ b/solon-plugins/wx-java-channel-solon-plugin/src/main/java/com/binarywang/solon/wxjava/channel/integration/WxChannelPluginImpl.java @@ -0,0 +1,25 @@ +package com.binarywang.solon.wxjava.channel.integration; + + +import com.binarywang.solon.wxjava.channel.config.WxChannelServiceAutoConfiguration; +import com.binarywang.solon.wxjava.channel.config.storage.WxChannelInJedisConfigStorageConfiguration; +import com.binarywang.solon.wxjava.channel.config.storage.WxChannelInMemoryConfigStorageConfiguration; +import com.binarywang.solon.wxjava.channel.config.storage.WxChannelInRedissonConfigStorageConfiguration; +import com.binarywang.solon.wxjava.channel.properties.WxChannelProperties; +import org.noear.solon.core.AppContext; +import org.noear.solon.core.Plugin; + +/** + * @author noear 2024/9/2 created + */ +public class WxChannelPluginImpl implements Plugin { + @Override + public void start(AppContext context) throws Throwable { + context.beanMake(WxChannelProperties.class); + context.beanMake(WxChannelServiceAutoConfiguration.class); + + context.beanMake(WxChannelInMemoryConfigStorageConfiguration.class); + context.beanMake(WxChannelInJedisConfigStorageConfiguration.class); + context.beanMake(WxChannelInRedissonConfigStorageConfiguration.class); + } +} diff --git a/solon-plugins/wx-java-channel-solon-plugin/src/main/java/com/binarywang/solon/wxjava/channel/properties/RedisProperties.java b/solon-plugins/wx-java-channel-solon-plugin/src/main/java/com/binarywang/solon/wxjava/channel/properties/RedisProperties.java new file mode 100644 index 0000000000..b74ad89f4e --- /dev/null +++ b/solon-plugins/wx-java-channel-solon-plugin/src/main/java/com/binarywang/solon/wxjava/channel/properties/RedisProperties.java @@ -0,0 +1,42 @@ +package com.binarywang.solon.wxjava.channel.properties; + +import lombok.Data; + +/** + * redis 配置 + * + * @author Zeyes + */ +@Data +public class RedisProperties { + + /** + * 主机地址,不填则从solon容器内获取JedisPool + */ + 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/solon-plugins/wx-java-channel-solon-plugin/src/main/java/com/binarywang/solon/wxjava/channel/properties/WxChannelProperties.java b/solon-plugins/wx-java-channel-solon-plugin/src/main/java/com/binarywang/solon/wxjava/channel/properties/WxChannelProperties.java new file mode 100644 index 0000000000..89b81b7d9f --- /dev/null +++ b/solon-plugins/wx-java-channel-solon-plugin/src/main/java/com/binarywang/solon/wxjava/channel/properties/WxChannelProperties.java @@ -0,0 +1,114 @@ +package com.binarywang.solon.wxjava.channel.properties; + +import com.binarywang.solon.wxjava.channel.enums.HttpClientType; +import com.binarywang.solon.wxjava.channel.enums.StorageType; +import lombok.Data; +import org.noear.solon.annotation.Configuration; +import org.noear.solon.annotation.Inject; + +/** + * 属性配置类 + * + * @author Zeyes + */ +@Data +@Configuration +@Inject("${" + WxChannelProperties.PREFIX +"}") +public class WxChannelProperties { + public static final String PREFIX = "wx.channel"; + + /** + * 设置视频号小店的appid + */ + private String appid; + + /** + * 设置视频号小店的Secret + */ + private String secret; + + /** + * 设置视频号小店消息服务器配置的token. + */ + private String token; + + /** + * 设置视频号小店消息服务器配置的EncodingAESKey + */ + private String aesKey; + + /** + * 消息格式,XML或者JSON + */ + private String msgDataFormat = "JSON"; + + /** + * 是否使用稳定版 Access Token + */ + private boolean useStableAccessToken = false; + + /** + * 存储策略 + */ + private final ConfigStorage configStorage = new ConfigStorage(); + + @Data + public static class ConfigStorage { + + /** + * 存储类型 + */ + private StorageType type = StorageType.Memory; + + /** + * 指定key前缀 + */ + private String keyPrefix = "wh"; + + /** + * redis连接配置 + */ + private final RedisProperties redis = new RedisProperties(); + + /** + * http客户端类型 + */ + private HttpClientType httpClientType = HttpClientType.HttpComponents; + + /** + * http代理主机 + */ + private String httpProxyHost; + + /** + * http代理端口 + */ + private Integer httpProxyPort; + + /** + * http代理用户名 + */ + private String httpProxyUsername; + + /** + * http代理密码 + */ + private String httpProxyPassword; + + /** + * http 请求重试间隔 + *
+     *   {@link me.chanjar.weixin.channel.api.BaseWxChannelService#setRetrySleepMillis(int)}
+     * 
+ */ + private int retrySleepMillis = 1000; + /** + * http 请求最大重试次数 + *
+     *   {@link me.chanjar.weixin.channel.api.BaseWxChannelService#setMaxRetryTimes(int)}
+     * 
+ */ + private int maxRetryTimes = 5; + } + +} diff --git a/solon-plugins/wx-java-channel-solon-plugin/src/main/resources/META-INF/solon/wx-java-channel-solon-plugin.properties b/solon-plugins/wx-java-channel-solon-plugin/src/main/resources/META-INF/solon/wx-java-channel-solon-plugin.properties new file mode 100644 index 0000000000..d8ec8f5112 --- /dev/null +++ b/solon-plugins/wx-java-channel-solon-plugin/src/main/resources/META-INF/solon/wx-java-channel-solon-plugin.properties @@ -0,0 +1,2 @@ +solon.plugin=com.binarywang.solon.wxjava.channel.integration.WxChannelPluginImpl +solon.plugin.priority=10 diff --git a/solon-plugins/wx-java-channel-solon-plugin/src/test/java/features/test/LoadTest.java b/solon-plugins/wx-java-channel-solon-plugin/src/test/java/features/test/LoadTest.java new file mode 100644 index 0000000000..d049f5a51a --- /dev/null +++ b/solon-plugins/wx-java-channel-solon-plugin/src/test/java/features/test/LoadTest.java @@ -0,0 +1,15 @@ +package features.test; + +import org.junit.jupiter.api.Test; +import org.noear.solon.test.SolonTest; + +/** + * @author noear 2024/9/4 created + */ +@SolonTest +public class LoadTest { + @Test + public void load(){ + + } +} diff --git a/solon-plugins/wx-java-channel-solon-plugin/src/test/resources/app.yml b/solon-plugins/wx-java-channel-solon-plugin/src/test/resources/app.yml new file mode 100644 index 0000000000..e69de29bb2 diff --git a/solon-plugins/wx-java-cp-multi-solon-plugin/README.md b/solon-plugins/wx-java-cp-multi-solon-plugin/README.md new file mode 100644 index 0000000000..97bcf0723f --- /dev/null +++ b/solon-plugins/wx-java-cp-multi-solon-plugin/README.md @@ -0,0 +1,97 @@ +# wx-java-cp-multi-solon-plugin + +企业微信多账号配置 + +- 实现多 WxCpService 初始化。 +- 未实现 WxCpTpService 初始化,需要的小伙伴可以参考多 WxCpService 配置的实现。 +- 未实现 WxCpCgService 初始化,需要的小伙伴可以参考多 WxCpService 配置的实现。 + +## 快速开始 + +1. 引入依赖 + ```xml + + com.github.binarywang + wx-java-cp-multi-solon-plugin + ${version} + + ``` +2. 添加配置(app.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.solon.wxjava.cp_multi.service.WxCpMultiServices; +import me.chanjar.weixin.cp.api.WxCpService; +import me.chanjar.weixin.cp.api.WxCpUserService; +import org.noear.solon.annotation.Component; +import org.noear.solon.annotation.Inject; + +@Component +public class DemoService { + @Inject + private WxCpMultiServices wxCpMultiServices; + + 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/solon-plugins/wx-java-cp-multi-solon-plugin/pom.xml b/solon-plugins/wx-java-cp-multi-solon-plugin/pom.xml new file mode 100644 index 0000000000..17e24bfe2d --- /dev/null +++ b/solon-plugins/wx-java-cp-multi-solon-plugin/pom.xml @@ -0,0 +1,32 @@ + + + + wx-java-solon-plugins + com.github.binarywang + 4.8.0 + + 4.0.0 + + wx-java-cp-multi-solon-plugin + WxJava - Solon Plugin for WxCp::支持多账号配置 + 微信企业号开发的 Solon Plugin::支持多账号配置 + + + + com.github.binarywang + weixin-java-cp + ${project.version} + + + redis.clients + jedis + provided + + + org.redisson + redisson + provided + + + 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 new file mode 100644 index 0000000000..ada4ac504c --- /dev/null +++ b/solon-plugins/wx-java-cp-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/cp_multi/configuration/services/AbstractWxCpConfiguration.java @@ -0,0 +1,162 @@ +package com.binarywang.solon.wxjava.cp_multi.configuration.services; + +import com.binarywang.solon.wxjava.cp_multi.properties.WxCpMultiProperties; +import com.binarywang.solon.wxjava.cp_multi.properties.WxCpSingleProperties; +import com.binarywang.solon.wxjava.cp_multi.service.WxCpMultiServices; +import com.binarywang.solon.wxjava.cp_multi.service.WxCpMultiServicesImpl; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import me.chanjar.weixin.cp.api.WxCpService; +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; + +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 AbstractWxCpConfiguration { + + protected WxCpMultiServices wxCpMultiServices(WxCpMultiProperties wxCpMultiProperties) { + Map corps = wxCpMultiProperties.getCorps(); + if (corps == null || corps.isEmpty()) { + log.warn("企业微信应用参数未配置,通过 WxCpMultiServices#getWxCpService(\"tenantId\")获取实例将返回空"); + return new WxCpMultiServicesImpl(); + } + /** + * 校验同一个企业下,agentId 是否唯一,避免使用 redis 缓存 token、ticket 时错乱。 + * + * 查看 {@link me.chanjar.weixin.cp.config.impl.AbstractWxCpInRedisConfigImpl#setAgentId(Integer)} + */ + Collection corpList = corps.values(); + if (corpList.size() > 1) { + // 先按 corpId 分组统计 + Map> corpsMap = corpList.stream() + .collect(Collectors.groupingBy(WxCpSingleProperties::getCorpId)); + Set>> entries = corpsMap.entrySet(); + for (Map.Entry> entry : entries) { + String corpId = entry.getKey(); + // 校验每个企业下,agentId 是否唯一 + boolean multi = entry.getValue().stream() + // 通讯录没有 agentId,如果不判断是否为空,这里会报 NPE 异常 + .collect(Collectors.groupingBy(c -> c.getAgentId() == null ? 0 : c.getAgentId(), Collectors.counting())) + .entrySet().stream().anyMatch(e -> e.getValue() > 1); + if (multi) { + throw new RuntimeException("请确保企业微信配置唯一性[" + corpId + "]"); + } + } + } + WxCpMultiServicesImpl services = new WxCpMultiServicesImpl(); + + Set> entries = corps.entrySet(); + for (Map.Entry entry : entries) { + String tenantId = entry.getKey(); + WxCpSingleProperties wxCpSingleProperties = entry.getValue(); + WxCpDefaultConfigImpl storage = this.wxCpConfigStorage(wxCpMultiProperties); + this.configCorp(storage, wxCpSingleProperties); + this.configHttp(storage, wxCpMultiProperties.getConfigStorage()); + WxCpService wxCpService = this.wxCpService(storage, wxCpMultiProperties.getConfigStorage()); + services.addWxCpService(tenantId, wxCpService); + } + return services; + } + + /** + * 配置 WxCpDefaultConfigImpl + * + * @param wxCpMultiProperties 参数 + * @return WxCpDefaultConfigImpl + */ + protected abstract WxCpDefaultConfigImpl wxCpConfigStorage(WxCpMultiProperties wxCpMultiProperties); + + private WxCpService wxCpService(WxCpConfigStorage wxCpConfigStorage, WxCpMultiProperties.ConfigStorage storage) { + WxCpMultiProperties.HttpClientType httpClientType = storage.getHttpClientType(); + WxCpService wxCpService; + switch (httpClientType) { + case OK_HTTP: + wxCpService = new WxCpServiceOkHttpImpl(); + break; + case JODD_HTTP: + wxCpService = new WxCpServiceJoddHttpImpl(); + break; + case HTTP_CLIENT: + wxCpService = new WxCpServiceApacheHttpClientImpl(); + break; + case HTTP_COMPONENTS: + wxCpService = new WxCpServiceHttpComponentsImpl(); + break; + default: + wxCpService = new WxCpServiceImpl(); + break; + } + wxCpService.setWxCpConfigStorage(wxCpConfigStorage); + int maxRetryTimes = storage.getMaxRetryTimes(); + if (maxRetryTimes < 0) { + maxRetryTimes = 0; + } + int retrySleepMillis = storage.getRetrySleepMillis(); + if (retrySleepMillis < 0) { + retrySleepMillis = 1000; + } + wxCpService.setRetrySleepMillis(retrySleepMillis); + wxCpService.setMaxRetryTimes(maxRetryTimes); + return wxCpService; + } + + private void configCorp(WxCpDefaultConfigImpl config, WxCpSingleProperties wxCpSingleProperties) { + String corpId = wxCpSingleProperties.getCorpId(); + String corpSecret = wxCpSingleProperties.getCorpSecret(); + Integer agentId = wxCpSingleProperties.getAgentId(); + String token = wxCpSingleProperties.getToken(); + String aesKey = wxCpSingleProperties.getAesKey(); + // 企业微信,私钥,会话存档路径 + String msgAuditPriKey = wxCpSingleProperties.getMsgAuditPriKey(); + String msgAuditLibPath = wxCpSingleProperties.getMsgAuditLibPath(); + + config.setCorpId(corpId); + config.setCorpSecret(corpSecret); + config.setAgentId(agentId); + if (StringUtils.isNotBlank(token)) { + config.setToken(token); + } + if (StringUtils.isNotBlank(aesKey)) { + config.setAesKey(aesKey); + } + if (StringUtils.isNotBlank(msgAuditPriKey)) { + config.setMsgAuditPriKey(msgAuditPriKey); + } + if (StringUtils.isNotBlank(msgAuditLibPath)) { + config.setMsgAuditLibPath(msgAuditLibPath); + } + } + + private void configHttp(WxCpDefaultConfigImpl config, WxCpMultiProperties.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/solon-plugins/wx-java-cp-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/cp_multi/configuration/services/WxCpInJedisConfiguration.java b/solon-plugins/wx-java-cp-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/cp_multi/configuration/services/WxCpInJedisConfiguration.java new file mode 100644 index 0000000000..71f5fd6725 --- /dev/null +++ b/solon-plugins/wx-java-cp-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/cp_multi/configuration/services/WxCpInJedisConfiguration.java @@ -0,0 +1,77 @@ +package com.binarywang.solon.wxjava.cp_multi.configuration.services; + +import com.binarywang.solon.wxjava.cp_multi.properties.WxCpMultiProperties; +import com.binarywang.solon.wxjava.cp_multi.properties.WxCpMultiRedisProperties; +import com.binarywang.solon.wxjava.cp_multi.service.WxCpMultiServices; +import lombok.RequiredArgsConstructor; +import me.chanjar.weixin.cp.config.impl.WxCpDefaultConfigImpl; +import me.chanjar.weixin.cp.config.impl.WxCpJedisConfigImpl; +import org.apache.commons.lang3.StringUtils; +import org.noear.solon.annotation.Bean; +import org.noear.solon.annotation.Condition; +import org.noear.solon.annotation.Configuration; +import org.noear.solon.core.AppContext; +import redis.clients.jedis.JedisPool; +import redis.clients.jedis.JedisPoolConfig; + +/** + * 自动装配基于 jedis 策略配置 + * + * @author yl + * created on 2023/10/16 + */ +@Configuration +@Condition( + onProperty = "${"+WxCpMultiProperties.PREFIX + ".configStorage.type} = jedis", + onClass = JedisPool.class +) +@RequiredArgsConstructor +public class WxCpInJedisConfiguration extends AbstractWxCpConfiguration { + private final WxCpMultiProperties wxCpMultiProperties; + private final AppContext applicationContext; + + @Bean + public WxCpMultiServices wxCpMultiServices() { + return this.wxCpMultiServices(wxCpMultiProperties); + } + + @Override + protected WxCpDefaultConfigImpl wxCpConfigStorage(WxCpMultiProperties wxCpMultiProperties) { + return this.configRedis(wxCpMultiProperties); + } + + private WxCpDefaultConfigImpl configRedis(WxCpMultiProperties wxCpMultiProperties) { + WxCpMultiRedisProperties wxCpMultiRedisProperties = wxCpMultiProperties.getConfigStorage().getRedis(); + JedisPool jedisPool; + if (wxCpMultiRedisProperties != null && StringUtils.isNotEmpty(wxCpMultiRedisProperties.getHost())) { + jedisPool = getJedisPool(wxCpMultiProperties); + } else { + jedisPool = applicationContext.getBean(JedisPool.class); + } + return new WxCpJedisConfigImpl(jedisPool, wxCpMultiProperties.getConfigStorage().getKeyPrefix()); + } + + private JedisPool getJedisPool(WxCpMultiProperties wxCpMultiProperties) { + WxCpMultiProperties.ConfigStorage storage = wxCpMultiProperties.getConfigStorage(); + WxCpMultiRedisProperties redis = storage.getRedis(); + + JedisPoolConfig config = new JedisPoolConfig(); + if (redis.getMaxActive() != null) { + config.setMaxTotal(redis.getMaxActive()); + } + if (redis.getMaxIdle() != null) { + config.setMaxIdle(redis.getMaxIdle()); + } + if (redis.getMaxWaitMillis() != null) { + config.setMaxWaitMillis(redis.getMaxWaitMillis()); + } + if (redis.getMinIdle() != null) { + config.setMinIdle(redis.getMinIdle()); + } + config.setTestOnBorrow(true); + config.setTestWhileIdle(true); + + return new JedisPool(config, redis.getHost(), redis.getPort(), + redis.getTimeout(), redis.getPassword(), redis.getDatabase()); + } +} diff --git a/solon-plugins/wx-java-cp-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/cp_multi/configuration/services/WxCpInMemoryConfiguration.java b/solon-plugins/wx-java-cp-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/cp_multi/configuration/services/WxCpInMemoryConfiguration.java new file mode 100644 index 0000000000..3dfb36e258 --- /dev/null +++ b/solon-plugins/wx-java-cp-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/cp_multi/configuration/services/WxCpInMemoryConfiguration.java @@ -0,0 +1,38 @@ +package com.binarywang.solon.wxjava.cp_multi.configuration.services; + +import com.binarywang.solon.wxjava.cp_multi.properties.WxCpMultiProperties; +import com.binarywang.solon.wxjava.cp_multi.service.WxCpMultiServices; +import lombok.RequiredArgsConstructor; +import me.chanjar.weixin.cp.config.impl.WxCpDefaultConfigImpl; +import org.noear.solon.annotation.Bean; +import org.noear.solon.annotation.Condition; +import org.noear.solon.annotation.Configuration; + +/** + * 自动装配基于内存策略配置 + * + * @author yl + * created on 2023/10/16 + */ +@Configuration +@Condition( + onProperty = "${"+WxCpMultiProperties.PREFIX + ".configStorage.type:memory} = memory" +) +@RequiredArgsConstructor +public class WxCpInMemoryConfiguration extends AbstractWxCpConfiguration { + private final WxCpMultiProperties wxCpMultiProperties; + + @Bean + public WxCpMultiServices wxCpMultiServices() { + return this.wxCpMultiServices(wxCpMultiProperties); + } + + @Override + protected WxCpDefaultConfigImpl wxCpConfigStorage(WxCpMultiProperties wxCpMultiProperties) { + return this.configInMemory(); + } + + private WxCpDefaultConfigImpl configInMemory() { + return new WxCpDefaultConfigImpl(); + } +} diff --git a/solon-plugins/wx-java-cp-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/cp_multi/configuration/services/WxCpInRedissonConfiguration.java b/solon-plugins/wx-java-cp-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/cp_multi/configuration/services/WxCpInRedissonConfiguration.java new file mode 100644 index 0000000000..6700570af8 --- /dev/null +++ b/solon-plugins/wx-java-cp-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/cp_multi/configuration/services/WxCpInRedissonConfiguration.java @@ -0,0 +1,68 @@ +package com.binarywang.solon.wxjava.cp_multi.configuration.services; + +import com.binarywang.solon.wxjava.cp_multi.properties.WxCpMultiProperties; +import com.binarywang.solon.wxjava.cp_multi.properties.WxCpMultiRedisProperties; +import com.binarywang.solon.wxjava.cp_multi.service.WxCpMultiServices; +import lombok.RequiredArgsConstructor; +import me.chanjar.weixin.cp.config.impl.WxCpDefaultConfigImpl; +import me.chanjar.weixin.cp.config.impl.WxCpRedissonConfigImpl; +import org.apache.commons.lang3.StringUtils; +import org.noear.solon.annotation.Bean; +import org.noear.solon.annotation.Condition; +import org.noear.solon.annotation.Configuration; +import org.noear.solon.core.AppContext; +import org.redisson.Redisson; +import org.redisson.api.RedissonClient; +import org.redisson.config.Config; +import org.redisson.config.TransportMode; + +/** + * 自动装配基于 redisson 策略配置 + * + * @author yl + * created on 2023/10/16 + */ +@Configuration +@Condition( + onProperty = "${"+WxCpMultiProperties.PREFIX + ".configStorage.type} = redisson", + onClass = Redisson.class +) +@RequiredArgsConstructor +public class WxCpInRedissonConfiguration extends AbstractWxCpConfiguration { + private final WxCpMultiProperties wxCpMultiProperties; + private final AppContext applicationContext; + + @Bean + public WxCpMultiServices wxCpMultiServices() { + return this.wxCpMultiServices(wxCpMultiProperties); + } + + @Override + protected WxCpDefaultConfigImpl wxCpConfigStorage(WxCpMultiProperties wxCpMultiProperties) { + return this.configRedisson(wxCpMultiProperties); + } + + private WxCpDefaultConfigImpl configRedisson(WxCpMultiProperties wxCpMultiProperties) { + WxCpMultiRedisProperties redisProperties = wxCpMultiProperties.getConfigStorage().getRedis(); + RedissonClient redissonClient; + if (redisProperties != null && StringUtils.isNotEmpty(redisProperties.getHost())) { + redissonClient = getRedissonClient(wxCpMultiProperties); + } else { + redissonClient = applicationContext.getBean(RedissonClient.class); + } + return new WxCpRedissonConfigImpl(redissonClient, wxCpMultiProperties.getConfigStorage().getKeyPrefix()); + } + + private RedissonClient getRedissonClient(WxCpMultiProperties wxCpMultiProperties) { + WxCpMultiProperties.ConfigStorage storage = wxCpMultiProperties.getConfigStorage(); + WxCpMultiRedisProperties 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/solon-plugins/wx-java-cp-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/cp_multi/integration/WxCpMultiPluginImpl.java b/solon-plugins/wx-java-cp-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/cp_multi/integration/WxCpMultiPluginImpl.java new file mode 100644 index 0000000000..b2a078c727 --- /dev/null +++ b/solon-plugins/wx-java-cp-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/cp_multi/integration/WxCpMultiPluginImpl.java @@ -0,0 +1,21 @@ +package com.binarywang.solon.wxjava.cp_multi.integration; + +import com.binarywang.solon.wxjava.cp_multi.configuration.services.WxCpInJedisConfiguration; +import com.binarywang.solon.wxjava.cp_multi.configuration.services.WxCpInMemoryConfiguration; +import com.binarywang.solon.wxjava.cp_multi.configuration.services.WxCpInRedissonConfiguration; +import com.binarywang.solon.wxjava.cp_multi.properties.WxCpMultiProperties; +import org.noear.solon.core.AppContext; +import org.noear.solon.core.Plugin; + +/** + * @author noear 2024/9/2 created + */ +public class WxCpMultiPluginImpl implements Plugin { + @Override + public void start(AppContext context) throws Throwable { + context.beanMake(WxCpMultiProperties.class); + context.beanMake(WxCpInJedisConfiguration.class); + context.beanMake(WxCpInMemoryConfiguration.class); + context.beanMake(WxCpInRedissonConfiguration.class); + } +} 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 new file mode 100644 index 0000000000..2d4bffae66 --- /dev/null +++ b/solon-plugins/wx-java-cp-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/cp_multi/properties/WxCpMultiProperties.java @@ -0,0 +1,133 @@ +package com.binarywang.solon.wxjava.cp_multi.properties; + +import lombok.Data; +import lombok.NoArgsConstructor; +import org.noear.solon.annotation.Configuration; +import org.noear.solon.annotation.Inject; + +import java.io.Serializable; +import java.util.HashMap; +import java.util.Map; + +/** + * 企业微信多企业接入相关配置属性 + * + * @author yl + * created on 2023/10/16 + */ +@Data +@NoArgsConstructor +@Configuration +@Inject("${" + WxCpMultiProperties.PREFIX + "}") +public class WxCpMultiProperties implements Serializable { + private static final long serialVersionUID = -1569510477055668503L; + public static final String PREFIX = "wx.cp"; + + 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"; + + /** + * redis连接配置 + */ + private WxCpMultiRedisProperties redis = new WxCpMultiRedisProperties(); + + /** + * http客户端类型. + */ + private HttpClientType httpClientType = HttpClientType.HTTP_COMPONENTS; + + /** + * 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, + /** + * HttpComponents + */ + HTTP_COMPONENTS, + /** + * OkHttp + */ + OK_HTTP, + /** + * JoddHttp + */ + JODD_HTTP + } +} diff --git a/solon-plugins/wx-java-cp-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/cp_multi/properties/WxCpMultiRedisProperties.java b/solon-plugins/wx-java-cp-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/cp_multi/properties/WxCpMultiRedisProperties.java new file mode 100644 index 0000000000..14952d69d9 --- /dev/null +++ b/solon-plugins/wx-java-cp-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/cp_multi/properties/WxCpMultiRedisProperties.java @@ -0,0 +1,48 @@ +package com.binarywang.solon.wxjava.cp_multi.properties; + +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * Redis配置. + * + * @author yl + * created on 2023/10/16 + */ +@Data +@NoArgsConstructor +public class WxCpMultiRedisProperties 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/solon-plugins/wx-java-cp-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/cp_multi/properties/WxCpSingleProperties.java b/solon-plugins/wx-java-cp-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/cp_multi/properties/WxCpSingleProperties.java new file mode 100644 index 0000000000..e761a09062 --- /dev/null +++ b/solon-plugins/wx-java-cp-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/cp_multi/properties/WxCpSingleProperties.java @@ -0,0 +1,46 @@ +package com.binarywang.solon.wxjava.cp_multi.properties; + +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * 企业微信企业相关配置属性 + * + * @author yl + * created on 2023/10/16 + */ +@Data +@NoArgsConstructor +public class WxCpSingleProperties implements Serializable { + private static final long serialVersionUID = -7502823825007859418L; + /** + * 微信企业号 corpId + */ + private String corpId; + /** + * 微信企业号 corpSecret + */ + private String corpSecret; + /** + * 微信企业号应用 token + */ + private String token; + /** + * 微信企业号应用 ID + */ + private Integer agentId; + /** + * 微信企业号应用 EncodingAESKey + */ + private String aesKey; + /** + * 微信企业号应用 会话存档私钥 + */ + private String msgAuditPriKey; + /** + * 微信企业号应用 会话存档类库路径 + */ + private String msgAuditLibPath; +} diff --git a/solon-plugins/wx-java-cp-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/cp_multi/service/WxCpMultiServices.java b/solon-plugins/wx-java-cp-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/cp_multi/service/WxCpMultiServices.java new file mode 100644 index 0000000000..c66c28233d --- /dev/null +++ b/solon-plugins/wx-java-cp-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/cp_multi/service/WxCpMultiServices.java @@ -0,0 +1,26 @@ +package com.binarywang.solon.wxjava.cp_multi.service; + +import me.chanjar.weixin.cp.api.WxCpService; + +/** + * 企业微信 {@link WxCpService} 所有实例存放类. + * + * @author yl + * created on 2023/10/16 + */ +public interface WxCpMultiServices { + /** + * 通过租户 Id 获取 WxCpService + * + * @param tenantId 租户 Id + * @return WxCpService + */ + WxCpService getWxCpService(String tenantId); + + /** + * 根据租户 Id,从列表中移除一个 WxCpService 实例 + * + * @param tenantId 租户 Id + */ + void removeWxCpService(String tenantId); +} diff --git a/solon-plugins/wx-java-cp-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/cp_multi/service/WxCpMultiServicesImpl.java b/solon-plugins/wx-java-cp-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/cp_multi/service/WxCpMultiServicesImpl.java new file mode 100644 index 0000000000..d7833a05f9 --- /dev/null +++ b/solon-plugins/wx-java-cp-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/cp_multi/service/WxCpMultiServicesImpl.java @@ -0,0 +1,42 @@ +package com.binarywang.solon.wxjava.cp_multi.service; + +import me.chanjar.weixin.cp.api.WxCpService; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * 企业微信 {@link WxCpMultiServices} 默认实现 + * + * @author yl + * created on 2023/10/16 + */ +public class WxCpMultiServicesImpl implements WxCpMultiServices { + private final Map services = new ConcurrentHashMap<>(); + + /** + * 通过租户 Id 获取 WxCpService + * + * @param tenantId 租户 Id + * @return WxCpService + */ + @Override + public WxCpService getWxCpService(String tenantId) { + return this.services.get(tenantId); + } + + /** + * 根据租户 Id,添加一个 WxCpService 到列表 + * + * @param tenantId 租户 Id + * @param wxCpService WxCpService 实例 + */ + public void addWxCpService(String tenantId, WxCpService wxCpService) { + this.services.put(tenantId, wxCpService); + } + + @Override + public void removeWxCpService(String tenantId) { + this.services.remove(tenantId); + } +} diff --git a/solon-plugins/wx-java-cp-multi-solon-plugin/src/main/resources/META-INF/solon/wx-java-cp-multi-solon-plugin.properties b/solon-plugins/wx-java-cp-multi-solon-plugin/src/main/resources/META-INF/solon/wx-java-cp-multi-solon-plugin.properties new file mode 100644 index 0000000000..eb537e9a66 --- /dev/null +++ b/solon-plugins/wx-java-cp-multi-solon-plugin/src/main/resources/META-INF/solon/wx-java-cp-multi-solon-plugin.properties @@ -0,0 +1,2 @@ +solon.plugin=com.binarywang.solon.wxjava.cp_multi.integration.WxCpMultiPluginImpl +solon.plugin.priority=10 diff --git a/solon-plugins/wx-java-cp-multi-solon-plugin/src/test/java/features/test/LoadTest.java b/solon-plugins/wx-java-cp-multi-solon-plugin/src/test/java/features/test/LoadTest.java new file mode 100644 index 0000000000..d049f5a51a --- /dev/null +++ b/solon-plugins/wx-java-cp-multi-solon-plugin/src/test/java/features/test/LoadTest.java @@ -0,0 +1,15 @@ +package features.test; + +import org.junit.jupiter.api.Test; +import org.noear.solon.test.SolonTest; + +/** + * @author noear 2024/9/4 created + */ +@SolonTest +public class LoadTest { + @Test + public void load(){ + + } +} diff --git a/solon-plugins/wx-java-cp-multi-solon-plugin/src/test/resources/app.properties b/solon-plugins/wx-java-cp-multi-solon-plugin/src/test/resources/app.properties new file mode 100644 index 0000000000..0602c0a807 --- /dev/null +++ b/solon-plugins/wx-java-cp-multi-solon-plugin/src/test/resources/app.properties @@ -0,0 +1,19 @@ +# ?? 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 diff --git a/solon-plugins/wx-java-cp-solon-plugin/README.md b/solon-plugins/wx-java-cp-solon-plugin/README.md new file mode 100644 index 0000000000..04d5dfab58 --- /dev/null +++ b/solon-plugins/wx-java-cp-solon-plugin/README.md @@ -0,0 +1,41 @@ +# wx-java-cp-solon-plugin + +## 快速开始 + +1. 引入依赖 + ```xml + + com.github.binarywang + wx-java-cp-solon-plugin + ${version} + + ``` +2. 添加配置(app.properties) + ```properties + # 企业微信号配置(必填) + wx.cp.corp-id = @corp-id + wx.cp.corp-secret = @corp-secret + # 选填 + wx.cp.agent-id = @agent-id + wx.cp.token = @token + wx.cp.aes-key = @aes-key + wx.cp.msg-audit-priKey = @msg-audit-priKey + wx.cp.msg-audit-lib-path = @msg-audit-lib-path + # ConfigStorage 配置(选填) + wx.cp.config-storage.type=memory # 配置类型: memory(默认), jedis, redisson, redistemplate + # http 客户端配置(选填) + 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. 支持自动注入的类型: `WxCpService`, `WxCpConfigStorage` + +4. 覆盖自动配置: 自定义注入的bean会覆盖自动注入的 + +- WxCpService +- WxCpConfigStorage diff --git a/solon-plugins/wx-java-cp-solon-plugin/pom.xml b/solon-plugins/wx-java-cp-solon-plugin/pom.xml new file mode 100644 index 0000000000..7e6f2f8164 --- /dev/null +++ b/solon-plugins/wx-java-cp-solon-plugin/pom.xml @@ -0,0 +1,30 @@ + + + + wx-java-solon-plugins + com.github.binarywang + 4.8.0 + + 4.0.0 + + wx-java-cp-solon-plugin + WxJava - Solon Plugin for WxCp + 微信企业号开发的 Solon Plugin + + + + com.github.binarywang + weixin-java-cp + ${project.version} + + + redis.clients + jedis + + + org.redisson + redisson + + + diff --git a/solon-plugins/wx-java-cp-solon-plugin/src/main/java/com/binarywang/solon/wxjava/cp/config/WxCpServiceAutoConfiguration.java b/solon-plugins/wx-java-cp-solon-plugin/src/main/java/com/binarywang/solon/wxjava/cp/config/WxCpServiceAutoConfiguration.java new file mode 100644 index 0000000000..82aeeaf859 --- /dev/null +++ b/solon-plugins/wx-java-cp-solon-plugin/src/main/java/com/binarywang/solon/wxjava/cp/config/WxCpServiceAutoConfiguration.java @@ -0,0 +1,43 @@ +package com.binarywang.solon.wxjava.cp.config; + +import com.binarywang.solon.wxjava.cp.properties.WxCpProperties; +import lombok.RequiredArgsConstructor; +import me.chanjar.weixin.cp.api.WxCpService; +import me.chanjar.weixin.cp.api.impl.WxCpServiceImpl; +import me.chanjar.weixin.cp.config.WxCpConfigStorage; +import org.noear.solon.annotation.Bean; +import org.noear.solon.annotation.Condition; +import org.noear.solon.annotation.Configuration; + +/** + * 企业微信平台相关服务自动注册 + * + * @author yl + * created on 2021/12/6 + */ +@Configuration +@RequiredArgsConstructor +public class WxCpServiceAutoConfiguration { + private final WxCpProperties wxCpProperties; + + @Bean + @Condition(onMissingBean = WxCpService.class, + onBean = WxCpConfigStorage.class) + public WxCpService wxCpService(WxCpConfigStorage wxCpConfigStorage) { + WxCpService wxCpService = new WxCpServiceImpl(); + wxCpService.setWxCpConfigStorage(wxCpConfigStorage); + + WxCpProperties.ConfigStorage storage = wxCpProperties.getConfigStorage(); + int maxRetryTimes = storage.getMaxRetryTimes(); + if (maxRetryTimes < 0) { + maxRetryTimes = 0; + } + int retrySleepMillis = storage.getRetrySleepMillis(); + if (retrySleepMillis < 0) { + retrySleepMillis = 1000; + } + wxCpService.setRetrySleepMillis(retrySleepMillis); + wxCpService.setMaxRetryTimes(maxRetryTimes); + return wxCpService; + } +} diff --git a/solon-plugins/wx-java-cp-solon-plugin/src/main/java/com/binarywang/solon/wxjava/cp/integration/WxCpPluginImpl.java b/solon-plugins/wx-java-cp-solon-plugin/src/main/java/com/binarywang/solon/wxjava/cp/integration/WxCpPluginImpl.java new file mode 100644 index 0000000000..fda64b3a17 --- /dev/null +++ b/solon-plugins/wx-java-cp-solon-plugin/src/main/java/com/binarywang/solon/wxjava/cp/integration/WxCpPluginImpl.java @@ -0,0 +1,25 @@ +package com.binarywang.solon.wxjava.cp.integration; + +import com.binarywang.solon.wxjava.cp.config.WxCpServiceAutoConfiguration; +import com.binarywang.solon.wxjava.cp.properties.WxCpProperties; +import com.binarywang.solon.wxjava.cp.storage.WxCpInJedisConfigStorageConfiguration; +import com.binarywang.solon.wxjava.cp.storage.WxCpInMemoryConfigStorageConfiguration; +import com.binarywang.solon.wxjava.cp.storage.WxCpInRedissonConfigStorageConfiguration; +import org.noear.solon.core.AppContext; +import org.noear.solon.core.Plugin; + +/** + * @author noear 2024/9/2 created + */ +public class WxCpPluginImpl implements Plugin { + @Override + public void start(AppContext context) throws Throwable { + context.beanMake(WxCpProperties.class); + + context.beanMake(WxCpServiceAutoConfiguration.class); + + context.beanMake(WxCpInMemoryConfigStorageConfiguration.class); + context.beanMake(WxCpInJedisConfigStorageConfiguration.class); + context.beanMake(WxCpInRedissonConfigStorageConfiguration.class); + } +} diff --git a/solon-plugins/wx-java-cp-solon-plugin/src/main/java/com/binarywang/solon/wxjava/cp/properties/WxCpProperties.java b/solon-plugins/wx-java-cp-solon-plugin/src/main/java/com/binarywang/solon/wxjava/cp/properties/WxCpProperties.java new file mode 100644 index 0000000000..60524f5228 --- /dev/null +++ b/solon-plugins/wx-java-cp-solon-plugin/src/main/java/com/binarywang/solon/wxjava/cp/properties/WxCpProperties.java @@ -0,0 +1,133 @@ +package com.binarywang.solon.wxjava.cp.properties; + +import lombok.Data; +import lombok.NoArgsConstructor; +import org.noear.solon.annotation.Configuration; +import org.noear.solon.annotation.Inject; + +import java.io.Serializable; + +/** + * 企业微信接入相关配置属性 + * + * @author yl + * created on 2021/12/6 + */ +@Data +@NoArgsConstructor +@Configuration +@Inject("${" + WxCpProperties.PREFIX + "}") +public class WxCpProperties { + public static final String PREFIX = "wx.cp"; + + /** + * 微信企业号 corpId + */ + private String corpId; + /** + * 微信企业号 corpSecret + */ + private String corpSecret; + /** + * 微信企业号应用 token + */ + private String token; + /** + * 微信企业号应用 ID + */ + private Integer agentId; + /** + * 微信企业号应用 EncodingAESKey + */ + private String aesKey; + /** + * 微信企业号应用 会话存档私钥 + */ + private String msgAuditPriKey; + /** + * 微信企业号应用 会话存档类库路径 + */ + private String msgAuditLibPath; + + /** + * 配置存储策略,默认内存 + */ + 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"; + + /** + * redis连接配置 + */ + private WxCpRedisProperties redis = new WxCpRedisProperties(); + + /** + * 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 + } +} diff --git a/solon-plugins/wx-java-cp-solon-plugin/src/main/java/com/binarywang/solon/wxjava/cp/properties/WxCpRedisProperties.java b/solon-plugins/wx-java-cp-solon-plugin/src/main/java/com/binarywang/solon/wxjava/cp/properties/WxCpRedisProperties.java new file mode 100644 index 0000000000..43b8788d3f --- /dev/null +++ b/solon-plugins/wx-java-cp-solon-plugin/src/main/java/com/binarywang/solon/wxjava/cp/properties/WxCpRedisProperties.java @@ -0,0 +1,46 @@ +package com.binarywang.solon.wxjava.cp.properties; + +import lombok.Data; + +import java.io.Serializable; + +/** + * Redis配置. + * + * @author yl + * created on 2023/04/23 + */ +@Data +public class WxCpRedisProperties 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/solon-plugins/wx-java-cp-solon-plugin/src/main/java/com/binarywang/solon/wxjava/cp/storage/AbstractWxCpConfigStorageConfiguration.java b/solon-plugins/wx-java-cp-solon-plugin/src/main/java/com/binarywang/solon/wxjava/cp/storage/AbstractWxCpConfigStorageConfiguration.java new file mode 100644 index 0000000000..9fcdd5779a --- /dev/null +++ b/solon-plugins/wx-java-cp-solon-plugin/src/main/java/com/binarywang/solon/wxjava/cp/storage/AbstractWxCpConfigStorageConfiguration.java @@ -0,0 +1,61 @@ +package com.binarywang.solon.wxjava.cp.storage; + +import com.binarywang.solon.wxjava.cp.properties.WxCpProperties; +import me.chanjar.weixin.cp.config.impl.WxCpDefaultConfigImpl; +import org.apache.commons.lang3.StringUtils; + +/** + * WxCpConfigStorage 抽象配置类 + * + * @author yl & Wang_Wong + * created on 2021/12/6 + */ +public abstract class AbstractWxCpConfigStorageConfiguration { + + protected WxCpDefaultConfigImpl config(WxCpDefaultConfigImpl config, WxCpProperties properties) { + String corpId = properties.getCorpId(); + String corpSecret = properties.getCorpSecret(); + Integer agentId = properties.getAgentId(); + String token = properties.getToken(); + String aesKey = properties.getAesKey(); + // 企业微信,私钥,会话存档路径 + String msgAuditPriKey = properties.getMsgAuditPriKey(); + String msgAuditLibPath = properties.getMsgAuditLibPath(); + + config.setCorpId(corpId); + config.setCorpSecret(corpSecret); + config.setAgentId(agentId); + if (StringUtils.isNotBlank(token)) { + config.setToken(token); + } + if (StringUtils.isNotBlank(aesKey)) { + config.setAesKey(aesKey); + } + if (StringUtils.isNotBlank(msgAuditPriKey)) { + config.setMsgAuditPriKey(msgAuditPriKey); + } + if (StringUtils.isNotBlank(msgAuditLibPath)) { + config.setMsgAuditLibPath(msgAuditLibPath); + } + + WxCpProperties.ConfigStorage storage = properties.getConfigStorage(); + 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); + } + } + return config; + } + +} diff --git a/solon-plugins/wx-java-cp-solon-plugin/src/main/java/com/binarywang/solon/wxjava/cp/storage/WxCpInJedisConfigStorageConfiguration.java b/solon-plugins/wx-java-cp-solon-plugin/src/main/java/com/binarywang/solon/wxjava/cp/storage/WxCpInJedisConfigStorageConfiguration.java new file mode 100644 index 0000000000..f6f6931992 --- /dev/null +++ b/solon-plugins/wx-java-cp-solon-plugin/src/main/java/com/binarywang/solon/wxjava/cp/storage/WxCpInJedisConfigStorageConfiguration.java @@ -0,0 +1,74 @@ +package com.binarywang.solon.wxjava.cp.storage; + +import com.binarywang.solon.wxjava.cp.properties.WxCpProperties; +import com.binarywang.solon.wxjava.cp.properties.WxCpRedisProperties; +import lombok.RequiredArgsConstructor; +import me.chanjar.weixin.cp.config.WxCpConfigStorage; +import me.chanjar.weixin.cp.config.impl.WxCpDefaultConfigImpl; +import me.chanjar.weixin.cp.config.impl.WxCpJedisConfigImpl; +import org.apache.commons.lang3.StringUtils; +import org.noear.solon.annotation.Bean; +import org.noear.solon.annotation.Condition; +import org.noear.solon.annotation.Configuration; +import org.noear.solon.core.AppContext; +import redis.clients.jedis.JedisPool; +import redis.clients.jedis.JedisPoolConfig; + +/** + * 自动装配基于 jedis 策略配置 + * + * @author yl + * created on 2023/04/23 + */ +@Configuration +@Condition( + onProperty = "${"+WxCpProperties.PREFIX + ".configStorage.type} = jedis", + onClass = JedisPool.class +) +@RequiredArgsConstructor +public class WxCpInJedisConfigStorageConfiguration extends AbstractWxCpConfigStorageConfiguration { + private final WxCpProperties wxCpProperties; + private final AppContext applicationContext; + + @Bean + @Condition(onMissingBean=WxCpConfigStorage.class) + public WxCpConfigStorage wxCpConfigStorage() { + WxCpDefaultConfigImpl config = getConfigStorage(); + return this.config(config, wxCpProperties); + } + + private WxCpJedisConfigImpl getConfigStorage() { + WxCpRedisProperties wxCpRedisProperties = wxCpProperties.getConfigStorage().getRedis(); + JedisPool jedisPool; + if (wxCpRedisProperties != null && StringUtils.isNotEmpty(wxCpRedisProperties.getHost())) { + jedisPool = getJedisPool(); + } else { + jedisPool = applicationContext.getBean(JedisPool.class); + } + return new WxCpJedisConfigImpl(jedisPool, wxCpProperties.getConfigStorage().getKeyPrefix()); + } + + private JedisPool getJedisPool() { + WxCpProperties.ConfigStorage storage = wxCpProperties.getConfigStorage(); + WxCpRedisProperties redis = storage.getRedis(); + + JedisPoolConfig config = new JedisPoolConfig(); + if (redis.getMaxActive() != null) { + config.setMaxTotal(redis.getMaxActive()); + } + if (redis.getMaxIdle() != null) { + config.setMaxIdle(redis.getMaxIdle()); + } + if (redis.getMaxWaitMillis() != null) { + config.setMaxWaitMillis(redis.getMaxWaitMillis()); + } + if (redis.getMinIdle() != null) { + config.setMinIdle(redis.getMinIdle()); + } + config.setTestOnBorrow(true); + config.setTestWhileIdle(true); + + return new JedisPool(config, redis.getHost(), redis.getPort(), + redis.getTimeout(), redis.getPassword(), redis.getDatabase()); + } +} diff --git a/solon-plugins/wx-java-cp-solon-plugin/src/main/java/com/binarywang/solon/wxjava/cp/storage/WxCpInMemoryConfigStorageConfiguration.java b/solon-plugins/wx-java-cp-solon-plugin/src/main/java/com/binarywang/solon/wxjava/cp/storage/WxCpInMemoryConfigStorageConfiguration.java new file mode 100644 index 0000000000..2776fea368 --- /dev/null +++ b/solon-plugins/wx-java-cp-solon-plugin/src/main/java/com/binarywang/solon/wxjava/cp/storage/WxCpInMemoryConfigStorageConfiguration.java @@ -0,0 +1,31 @@ +package com.binarywang.solon.wxjava.cp.storage; + +import com.binarywang.solon.wxjava.cp.properties.WxCpProperties; +import lombok.RequiredArgsConstructor; +import me.chanjar.weixin.cp.config.WxCpConfigStorage; +import me.chanjar.weixin.cp.config.impl.WxCpDefaultConfigImpl; +import org.noear.solon.annotation.Bean; +import org.noear.solon.annotation.Condition; +import org.noear.solon.annotation.Configuration; + +/** + * 自动装配基于内存策略配置 + * + * @author yl + * created on 2021/12/6 + */ +@Configuration +@Condition( + onProperty = "${"+WxCpProperties.PREFIX + ".configStorage.type:memory} = memory" +) +@RequiredArgsConstructor +public class WxCpInMemoryConfigStorageConfiguration extends AbstractWxCpConfigStorageConfiguration { + private final WxCpProperties wxCpProperties; + + @Bean + @Condition(onMissingBean=WxCpConfigStorage.class) + public WxCpConfigStorage wxCpConfigStorage() { + WxCpDefaultConfigImpl config = new WxCpDefaultConfigImpl(); + return this.config(config, wxCpProperties); + } +} diff --git a/solon-plugins/wx-java-cp-solon-plugin/src/main/java/com/binarywang/solon/wxjava/cp/storage/WxCpInRedissonConfigStorageConfiguration.java b/solon-plugins/wx-java-cp-solon-plugin/src/main/java/com/binarywang/solon/wxjava/cp/storage/WxCpInRedissonConfigStorageConfiguration.java new file mode 100644 index 0000000000..0aef4d520a --- /dev/null +++ b/solon-plugins/wx-java-cp-solon-plugin/src/main/java/com/binarywang/solon/wxjava/cp/storage/WxCpInRedissonConfigStorageConfiguration.java @@ -0,0 +1,65 @@ +package com.binarywang.solon.wxjava.cp.storage; + +import com.binarywang.solon.wxjava.cp.properties.WxCpProperties; +import com.binarywang.solon.wxjava.cp.properties.WxCpRedisProperties; +import lombok.RequiredArgsConstructor; +import me.chanjar.weixin.cp.config.WxCpConfigStorage; +import me.chanjar.weixin.cp.config.impl.WxCpDefaultConfigImpl; +import me.chanjar.weixin.cp.config.impl.WxCpRedissonConfigImpl; +import org.apache.commons.lang3.StringUtils; +import org.noear.solon.annotation.Bean; +import org.noear.solon.annotation.Condition; +import org.noear.solon.annotation.Configuration; +import org.noear.solon.core.AppContext; +import org.redisson.Redisson; +import org.redisson.api.RedissonClient; +import org.redisson.config.Config; +import org.redisson.config.TransportMode; + +/** + * 自动装配基于 redisson 策略配置 + * + * @author yl + * created on 2023/04/23 + */ +@Configuration +@Condition( + onProperty = "${"+WxCpProperties.PREFIX + ".configStorage.type} = redisson", + onClass = Redisson.class +) +@RequiredArgsConstructor +public class WxCpInRedissonConfigStorageConfiguration extends AbstractWxCpConfigStorageConfiguration { + private final WxCpProperties wxCpProperties; + private final AppContext applicationContext; + + @Bean + @Condition(onMissingBean=WxCpConfigStorage.class) + public WxCpConfigStorage wxCpConfigStorage() { + WxCpDefaultConfigImpl config = getConfigStorage(); + return this.config(config, wxCpProperties); + } + + private WxCpRedissonConfigImpl getConfigStorage() { + WxCpRedisProperties redisProperties = wxCpProperties.getConfigStorage().getRedis(); + RedissonClient redissonClient; + if (redisProperties != null && StringUtils.isNotEmpty(redisProperties.getHost())) { + redissonClient = getRedissonClient(); + } else { + redissonClient = applicationContext.getBean(RedissonClient.class); + } + return new WxCpRedissonConfigImpl(redissonClient, wxCpProperties.getConfigStorage().getKeyPrefix()); + } + + private RedissonClient getRedissonClient() { + WxCpProperties.ConfigStorage storage = wxCpProperties.getConfigStorage(); + WxCpRedisProperties 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/solon-plugins/wx-java-cp-solon-plugin/src/main/resources/META-INF/solon/wx-java-cp-solon-plugin.properties b/solon-plugins/wx-java-cp-solon-plugin/src/main/resources/META-INF/solon/wx-java-cp-solon-plugin.properties new file mode 100644 index 0000000000..c765affecb --- /dev/null +++ b/solon-plugins/wx-java-cp-solon-plugin/src/main/resources/META-INF/solon/wx-java-cp-solon-plugin.properties @@ -0,0 +1,2 @@ +solon.plugin=com.binarywang.solon.wxjava.cp.integration.WxCpPluginImpl +solon.plugin.priority=10 diff --git a/solon-plugins/wx-java-cp-solon-plugin/src/test/java/features/test/LoadTest.java b/solon-plugins/wx-java-cp-solon-plugin/src/test/java/features/test/LoadTest.java new file mode 100644 index 0000000000..d049f5a51a --- /dev/null +++ b/solon-plugins/wx-java-cp-solon-plugin/src/test/java/features/test/LoadTest.java @@ -0,0 +1,15 @@ +package features.test; + +import org.junit.jupiter.api.Test; +import org.noear.solon.test.SolonTest; + +/** + * @author noear 2024/9/4 created + */ +@SolonTest +public class LoadTest { + @Test + public void load(){ + + } +} diff --git a/solon-plugins/wx-java-cp-solon-plugin/src/test/resources/app.properties b/solon-plugins/wx-java-cp-solon-plugin/src/test/resources/app.properties new file mode 100644 index 0000000000..0c99c8b64d --- /dev/null +++ b/solon-plugins/wx-java-cp-solon-plugin/src/test/resources/app.properties @@ -0,0 +1,20 @@ +# ???????(??) +wx.cp.corp-id = @corp-id +wx.cp.corp-secret = @corp-secret +# ?? +wx.cp.agent-id = @agent-id +wx.cp.token = @token +wx.cp.aes-key = @aes-key +wx.cp.msg-audit-priKey = @msg-audit-priKey +wx.cp.msg-audit-lib-path = @msg-audit-lib-path +# ConfigStorage ?????? +wx.cp.config-storage.type=memory # ????: memory(??), jedis, redisson, redistemplate +# http ????????? +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 diff --git a/solon-plugins/wx-java-miniapp-multi-solon-plugin/README.md b/solon-plugins/wx-java-miniapp-multi-solon-plugin/README.md new file mode 100644 index 0000000000..4555a4fc5e --- /dev/null +++ b/solon-plugins/wx-java-miniapp-multi-solon-plugin/README.md @@ -0,0 +1,95 @@ +# wx-java-miniapp-multi-solon-plugin + +## 快速开始 + +1. 引入依赖 + ```xml + + com.github.binarywang + wx-java-miniapp-multi-solon-plugin + ${version} + + ``` +2. 添加配置(app.properties) + ```properties + # 公众号配置 + ## 应用 1 配置(必填) + wx.ma.apps.tenantId1.app-id=appId + wx.ma.apps.tenantId1.app-secret=@secret + ## 选填 + wx.ma.apps.tenantId1.token=@token + wx.ma.apps.tenantId1.aes-key=@aesKey + wx.ma.apps.tenantId1.use-stable-access-token=@useStableAccessToken + ## 应用 2 配置(必填) + wx.ma.apps.tenantId2.app-id=@appId + wx.ma.apps.tenantId2.app-secret =@secret + ## 选填 + wx.ma.apps.tenantId2.token=@token + wx.ma.apps.tenantId2.aes-key=@aesKey + wx.ma.apps.tenantId2.use-stable-access-token=@useStableAccessToken + + # ConfigStorage 配置(选填) + ## 配置类型: memory(默认), jedis, redisson + wx.ma.config-storage.type=memory + ## 相关redis前缀配置: wx:ma:multi(默认) + wx.ma.config-storage.key-prefix=wx:ma:multi + wx.ma.config-storage.redis.host=127.0.0.1 + wx.ma.config-storage.redis.port=6379 + ## 单机和 sentinel 同时存在时,优先使用sentinel配置 + # wx.ma.config-storage.redis.sentinel-ips=127.0.0.1:16379,127.0.0.1:26379 + # wx.ma.config-storage.redis.sentinel-name=mymaster + + # http 客户端配置(选填) + ## # http客户端类型: http_client(默认), ok_http, jodd_http + wx.ma.config-storage.http-client-type=http_client + wx.ma.config-storage.http-proxy-host= + wx.ma.config-storage.http-proxy-port= + wx.ma.config-storage.http-proxy-username= + wx.ma.config-storage.http-proxy-password= + ## 最大重试次数,默认:5 次,如果小于 0,则为 0 + wx.ma.config-storage.max-retry-times=5 + ## 重试时间间隔步进,默认:1000 毫秒,如果小于 0,则为 1000 + wx.ma.config-storage.retry-sleep-millis=1000 + ``` +3. 自动注入的类型:`WxMaMultiServices` + +4. 使用样例 + +```java +import com.binarywang.solon.wxjava.miniapp.service.WxMaMultiServices; +import cn.binarywang.wx.miniapp.api.WxMaService; +import cn.binarywang.wx.miniapp.api.WxMaUserService; +import org.noear.solon.annotation.Component; +import org.noear.solon.annotation.Inject; + +@Component +public class DemoService { + @Inject + private WxMaMultiServices wxMaMultiServices; + + public void test() { + // 应用 1 的 WxMaService + WxMaService wxMaService1 = wxMaMultiServices.getWxMaService("tenantId1"); + WxMaUserService userService1 = wxMaService1.getUserService(); + userService1.userInfo("xxx"); + // todo ... + + // 应用 2 的 WxMaService + WxMaService wxMaService2 = wxMaMultiServices.getWxMaService("tenantId2"); + WxMaUserService userService2 = wxMaService2.getUserService(); + userService2.userInfo("xxx"); + // todo ... + + // 应用 3 的 WxMaService + WxMaService wxMaService3 = wxMaMultiServices.getWxMaService("tenantId3"); + // 判断是否为空 + if (wxMaService3 == null) { + // todo wxMaService3 为空,请先配置 tenantId3 微信公众号应用参数 + return; + } + WxMaUserService userService3 = wxMaService3.getUserService(); + userService3.userInfo("xxx"); + // todo ... + } +} +``` diff --git a/solon-plugins/wx-java-miniapp-multi-solon-plugin/pom.xml b/solon-plugins/wx-java-miniapp-multi-solon-plugin/pom.xml new file mode 100644 index 0000000000..932f9244ce --- /dev/null +++ b/solon-plugins/wx-java-miniapp-multi-solon-plugin/pom.xml @@ -0,0 +1,43 @@ + + + + wx-java-solon-plugins + com.github.binarywang + 4.8.0 + + 4.0.0 + + wx-java-miniapp-multi-solon-plugin + WxJava - Solon Plugin for MiniApp::支持多账号配置 + 微信公众号开发的 Solon Plugin::支持多账号配置 + + + + com.github.binarywang + weixin-java-miniapp + ${project.version} + + + redis.clients + jedis + provided + + + org.redisson + redisson + provided + + + org.jodd + jodd-http + provided + + + com.squareup.okhttp3 + okhttp + provided + + + diff --git a/solon-plugins/wx-java-miniapp-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/miniapp/configuration/services/AbstractWxMaConfiguration.java b/solon-plugins/wx-java-miniapp-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/miniapp/configuration/services/AbstractWxMaConfiguration.java new file mode 100644 index 0000000000..8ad85c96b8 --- /dev/null +++ b/solon-plugins/wx-java-miniapp-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/miniapp/configuration/services/AbstractWxMaConfiguration.java @@ -0,0 +1,151 @@ +package com.binarywang.solon.wxjava.miniapp.configuration.services; + +import cn.binarywang.wx.miniapp.api.WxMaService; +import cn.binarywang.wx.miniapp.api.impl.WxMaServiceHttpClientImpl; +import cn.binarywang.wx.miniapp.api.impl.WxMaServiceHttpComponentsImpl; +import cn.binarywang.wx.miniapp.api.impl.WxMaServiceImpl; +import cn.binarywang.wx.miniapp.api.impl.WxMaServiceJoddHttpImpl; +import cn.binarywang.wx.miniapp.api.impl.WxMaServiceOkHttpImpl; +import cn.binarywang.wx.miniapp.config.WxMaConfig; +import cn.binarywang.wx.miniapp.config.impl.WxMaDefaultConfigImpl; +import com.binarywang.solon.wxjava.miniapp.properties.WxMaMultiProperties; +import com.binarywang.solon.wxjava.miniapp.properties.WxMaSingleProperties; +import com.binarywang.solon.wxjava.miniapp.service.WxMaMultiServices; +import com.binarywang.solon.wxjava.miniapp.service.WxMaMultiServicesImpl; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; + +import java.util.Collection; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * WxMaConfigStorage 抽象配置类 + * + * @author monch + * created on 2024/9/6 + */ +@RequiredArgsConstructor +@Slf4j +public abstract class AbstractWxMaConfiguration { + + protected WxMaMultiServices wxMaMultiServices(WxMaMultiProperties wxMaMultiProperties) { + Map appsMap = wxMaMultiProperties.getApps(); + if (appsMap == null || appsMap.isEmpty()) { + log.warn("微信公众号应用参数未配置,通过 WxMaMultiServices#getWxMaService(\"tenantId\")获取实例将返回空"); + return new WxMaMultiServicesImpl(); + } + /** + * 校验 appId 是否唯一,避免使用 redis 缓存 token、ticket 时错乱。 + * + * 查看 {@link cn.binarywang.wx.miniapp.config.impl.WxMaRedisConfigImpl#setAppId(String)} + */ + Collection apps = appsMap.values(); + if (apps.size() > 1) { + // 校验 appId 是否唯一 + boolean multi = apps.stream() + // 没有 appId,如果不判断是否为空,这里会报 NPE 异常 + .collect(Collectors.groupingBy(c -> c.getAppId() == null ? 0 : c.getAppId(), Collectors.counting())) + .entrySet().stream().anyMatch(e -> e.getValue() > 1); + if (multi) { + throw new RuntimeException("请确保微信公众号配置 appId 的唯一性"); + } + } + WxMaMultiServicesImpl services = new WxMaMultiServicesImpl(); + + Set> entries = appsMap.entrySet(); + for (Map.Entry entry : entries) { + String tenantId = entry.getKey(); + WxMaSingleProperties wxMaSingleProperties = entry.getValue(); + WxMaDefaultConfigImpl storage = this.wxMaConfigStorage(wxMaMultiProperties); + this.configApp(storage, wxMaSingleProperties); + this.configHttp(storage, wxMaMultiProperties.getConfigStorage()); + WxMaService wxMaService = this.wxMaService(storage, wxMaMultiProperties); + services.addWxMaService(tenantId, wxMaService); + } + return services; + } + + /** + * 配置 WxMaDefaultConfigImpl + * + * @param wxMaMultiProperties 参数 + * @return WxMaDefaultConfigImpl + */ + protected abstract WxMaDefaultConfigImpl wxMaConfigStorage(WxMaMultiProperties wxMaMultiProperties); + + public WxMaService wxMaService(WxMaConfig wxMaConfig, WxMaMultiProperties wxMaMultiProperties) { + WxMaMultiProperties.ConfigStorage storage = wxMaMultiProperties.getConfigStorage(); + WxMaMultiProperties.HttpClientType httpClientType = storage.getHttpClientType(); + WxMaService wxMaService; + switch (httpClientType) { + case OK_HTTP: + wxMaService = new WxMaServiceOkHttpImpl(); + break; + case JODD_HTTP: + wxMaService = new WxMaServiceJoddHttpImpl(); + break; + case HTTP_CLIENT: + wxMaService = new WxMaServiceHttpClientImpl(); + break; + case HTTP_COMPONENTS: + wxMaService = new WxMaServiceHttpComponentsImpl(); + break; + default: + wxMaService = new WxMaServiceImpl(); + break; + } + + wxMaService.setWxMaConfig(wxMaConfig); + int maxRetryTimes = storage.getMaxRetryTimes(); + if (maxRetryTimes < 0) { + maxRetryTimes = 0; + } + int retrySleepMillis = storage.getRetrySleepMillis(); + if (retrySleepMillis < 0) { + retrySleepMillis = 1000; + } + wxMaService.setRetrySleepMillis(retrySleepMillis); + wxMaService.setMaxRetryTimes(maxRetryTimes); + 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(); + + config.setAppid(appId); + config.setSecret(appSecret); + if (StringUtils.isNotBlank(token)) { + config.setToken(token); + } + if (StringUtils.isNotBlank(aesKey)) { + config.setAesKey(aesKey); + } + config.useStableAccessToken(useStableAccessToken); + } + + private void configHttp(WxMaDefaultConfigImpl config, WxMaMultiProperties.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/solon-plugins/wx-java-miniapp-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/miniapp/configuration/services/WxMaInJedisConfiguration.java b/solon-plugins/wx-java-miniapp-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/miniapp/configuration/services/WxMaInJedisConfiguration.java new file mode 100644 index 0000000000..24950fae10 --- /dev/null +++ b/solon-plugins/wx-java-miniapp-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/miniapp/configuration/services/WxMaInJedisConfiguration.java @@ -0,0 +1,77 @@ +package com.binarywang.solon.wxjava.miniapp.configuration.services; + +import cn.binarywang.wx.miniapp.config.impl.WxMaDefaultConfigImpl; +import cn.binarywang.wx.miniapp.config.impl.WxMaRedisConfigImpl; +import com.binarywang.solon.wxjava.miniapp.properties.WxMaMultiProperties; +import com.binarywang.solon.wxjava.miniapp.properties.WxMaMultiRedisProperties; +import com.binarywang.solon.wxjava.miniapp.service.WxMaMultiServices; +import lombok.RequiredArgsConstructor; +import org.apache.commons.lang3.StringUtils; +import org.noear.solon.annotation.Bean; +import org.noear.solon.annotation.Condition; +import org.noear.solon.annotation.Configuration; +import org.noear.solon.core.AppContext; +import redis.clients.jedis.JedisPool; +import redis.clients.jedis.JedisPoolConfig; + +/** + * 自动装配基于 jedis 策略配置 + * + * @author monch + * created on 2024/9/6 + */ +@Configuration +@Condition( + onProperty = "${"+WxMaMultiProperties.PREFIX + ".configStorage.type} = jedis", + onClass = JedisPool.class +) +@RequiredArgsConstructor +public class WxMaInJedisConfiguration extends AbstractWxMaConfiguration { + private final WxMaMultiProperties wxMaMultiProperties; + private final AppContext applicationContext; + + @Bean + public WxMaMultiServices wxMaMultiServices() { + return this.wxMaMultiServices(wxMaMultiProperties); + } + + @Override + protected WxMaDefaultConfigImpl wxMaConfigStorage(WxMaMultiProperties wxMaMultiProperties) { + return this.configRedis(wxMaMultiProperties); + } + + private WxMaDefaultConfigImpl configRedis(WxMaMultiProperties wxMaMultiProperties) { + WxMaMultiRedisProperties wxMaMultiRedisProperties = wxMaMultiProperties.getConfigStorage().getRedis(); + JedisPool jedisPool; + if (wxMaMultiRedisProperties != null && StringUtils.isNotEmpty(wxMaMultiRedisProperties.getHost())) { + jedisPool = getJedisPool(wxMaMultiProperties); + } else { + jedisPool = applicationContext.getBean(JedisPool.class); + } + return new WxMaRedisConfigImpl(jedisPool); + } + + private JedisPool getJedisPool(WxMaMultiProperties wxMaMultiProperties) { + WxMaMultiProperties.ConfigStorage storage = wxMaMultiProperties.getConfigStorage(); + WxMaMultiRedisProperties redis = storage.getRedis(); + + JedisPoolConfig config = new JedisPoolConfig(); + if (redis.getMaxActive() != null) { + config.setMaxTotal(redis.getMaxActive()); + } + if (redis.getMaxIdle() != null) { + config.setMaxIdle(redis.getMaxIdle()); + } + if (redis.getMaxWaitMillis() != null) { + config.setMaxWaitMillis(redis.getMaxWaitMillis()); + } + if (redis.getMinIdle() != null) { + config.setMinIdle(redis.getMinIdle()); + } + config.setTestOnBorrow(true); + config.setTestWhileIdle(true); + + return new JedisPool(config, redis.getHost(), redis.getPort(), + redis.getTimeout(), redis.getPassword(), redis.getDatabase()); + } +} diff --git a/solon-plugins/wx-java-miniapp-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/miniapp/configuration/services/WxMaInMemoryConfiguration.java b/solon-plugins/wx-java-miniapp-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/miniapp/configuration/services/WxMaInMemoryConfiguration.java new file mode 100644 index 0000000000..0b9ef1c4a8 --- /dev/null +++ b/solon-plugins/wx-java-miniapp-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/miniapp/configuration/services/WxMaInMemoryConfiguration.java @@ -0,0 +1,39 @@ +package com.binarywang.solon.wxjava.miniapp.configuration.services; + +import cn.binarywang.wx.miniapp.config.impl.WxMaDefaultConfigImpl; +import com.binarywang.solon.wxjava.miniapp.properties.WxMaMultiProperties; +import com.binarywang.solon.wxjava.miniapp.service.WxMaMultiServices; +import lombok.RequiredArgsConstructor; +import org.noear.solon.annotation.Bean; +import org.noear.solon.annotation.Condition; +import org.noear.solon.annotation.Configuration; + +/** + * 自动装配基于内存策略配置 + * + * @author monch + * created on 2024/9/6 + */ +@Configuration +@Condition( + onProperty = "${"+WxMaMultiProperties.PREFIX + ".configStorage.type:memory} = memory" +) +@RequiredArgsConstructor +public class WxMaInMemoryConfiguration extends AbstractWxMaConfiguration { + private final WxMaMultiProperties wxMaMultiProperties; + + @Bean + public WxMaMultiServices wxMaMultiServices() { + return this.wxMaMultiServices(wxMaMultiProperties); + } + + @Override + protected WxMaDefaultConfigImpl wxMaConfigStorage(WxMaMultiProperties wxMaMultiProperties) { + return this.configInMemory(); + } + + private WxMaDefaultConfigImpl configInMemory() { + return new WxMaDefaultConfigImpl(); + // return new WxMaDefaultConfigImpl(); + } +} diff --git a/solon-plugins/wx-java-miniapp-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/miniapp/configuration/services/WxMaInRedissonConfiguration.java b/solon-plugins/wx-java-miniapp-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/miniapp/configuration/services/WxMaInRedissonConfiguration.java new file mode 100644 index 0000000000..4e97071f01 --- /dev/null +++ b/solon-plugins/wx-java-miniapp-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/miniapp/configuration/services/WxMaInRedissonConfiguration.java @@ -0,0 +1,68 @@ +package com.binarywang.solon.wxjava.miniapp.configuration.services; + +import cn.binarywang.wx.miniapp.config.impl.WxMaDefaultConfigImpl; +import cn.binarywang.wx.miniapp.config.impl.WxMaRedissonConfigImpl; +import com.binarywang.solon.wxjava.miniapp.properties.WxMaMultiProperties; +import com.binarywang.solon.wxjava.miniapp.properties.WxMaMultiRedisProperties; +import com.binarywang.solon.wxjava.miniapp.service.WxMaMultiServices; +import lombok.RequiredArgsConstructor; +import org.apache.commons.lang3.StringUtils; +import org.noear.solon.annotation.Bean; +import org.noear.solon.annotation.Condition; +import org.noear.solon.annotation.Configuration; +import org.noear.solon.core.AppContext; +import org.redisson.Redisson; +import org.redisson.api.RedissonClient; +import org.redisson.config.Config; +import org.redisson.config.TransportMode; + +/** + * 自动装配基于 redisson 策略配置 + * + * @author monch + * created on 2024/9/6 + */ +@Configuration +@Condition( + onProperty = "${"+WxMaMultiProperties.PREFIX + ".configStorage.type} = redisson", + onClass = Redisson.class +) +@RequiredArgsConstructor +public class WxMaInRedissonConfiguration extends AbstractWxMaConfiguration { + private final WxMaMultiProperties wxMaMultiProperties; + private final AppContext applicationContext; + + @Bean + public WxMaMultiServices wxMaMultiServices() { + return this.wxMaMultiServices(wxMaMultiProperties); + } + + @Override + protected WxMaDefaultConfigImpl wxMaConfigStorage(WxMaMultiProperties wxMaMultiProperties) { + return this.configRedisson(wxMaMultiProperties); + } + + private WxMaDefaultConfigImpl configRedisson(WxMaMultiProperties wxMaMultiProperties) { + WxMaMultiRedisProperties redisProperties = wxMaMultiProperties.getConfigStorage().getRedis(); + RedissonClient redissonClient; + if (redisProperties != null && StringUtils.isNotEmpty(redisProperties.getHost())) { + redissonClient = getRedissonClient(wxMaMultiProperties); + } else { + redissonClient = applicationContext.getBean(RedissonClient.class); + } + return new WxMaRedissonConfigImpl(redissonClient, wxMaMultiProperties.getConfigStorage().getKeyPrefix()); + } + + private RedissonClient getRedissonClient(WxMaMultiProperties wxMaMultiProperties) { + WxMaMultiProperties.ConfigStorage storage = wxMaMultiProperties.getConfigStorage(); + WxMaMultiRedisProperties 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/solon-plugins/wx-java-miniapp-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/miniapp/integration/WxMiniappMultiPluginImpl.java b/solon-plugins/wx-java-miniapp-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/miniapp/integration/WxMiniappMultiPluginImpl.java new file mode 100644 index 0000000000..c1153be1bb --- /dev/null +++ b/solon-plugins/wx-java-miniapp-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/miniapp/integration/WxMiniappMultiPluginImpl.java @@ -0,0 +1,22 @@ +package com.binarywang.solon.wxjava.miniapp.integration; + +import com.binarywang.solon.wxjava.miniapp.configuration.services.WxMaInJedisConfiguration; +import com.binarywang.solon.wxjava.miniapp.configuration.services.WxMaInMemoryConfiguration; +import com.binarywang.solon.wxjava.miniapp.configuration.services.WxMaInRedissonConfiguration; +import com.binarywang.solon.wxjava.miniapp.properties.WxMaMultiProperties; +import org.noear.solon.core.AppContext; +import org.noear.solon.core.Plugin; + +/** + * @author noear 2024/10/9 created + */ +public class WxMiniappMultiPluginImpl implements Plugin { + @Override + public void start(AppContext context) throws Throwable { + context.beanMake(WxMaMultiProperties.class); + + context.beanMake(WxMaInJedisConfiguration.class); + context.beanMake(WxMaInMemoryConfiguration.class); + context.beanMake(WxMaInRedissonConfiguration.class); + } +} diff --git a/solon-plugins/wx-java-miniapp-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/miniapp/properties/WxMaMultiProperties.java b/solon-plugins/wx-java-miniapp-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/miniapp/properties/WxMaMultiProperties.java new file mode 100644 index 0000000000..f99d6280ec --- /dev/null +++ b/solon-plugins/wx-java-miniapp-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/miniapp/properties/WxMaMultiProperties.java @@ -0,0 +1,158 @@ +package com.binarywang.solon.wxjava.miniapp.properties; + +import lombok.Data; +import lombok.NoArgsConstructor; +import org.noear.solon.annotation.Configuration; +import org.noear.solon.annotation.Inject; + +import java.io.Serializable; +import java.util.HashMap; +import java.util.Map; + +/** + * @author monch created on 2024/9/6 + * @author noear + */ +@Data +@NoArgsConstructor +@Configuration +@Inject("${" + WxMaMultiProperties.PREFIX + "}") +public class WxMaMultiProperties implements Serializable { + private static final long serialVersionUID = -5358245184407791011L; + public static final String PREFIX = "wx.ma"; + + private Map apps = new HashMap<>(); + + /** + * 自定义host配置 + */ + private HostConfig hosts; + + /** + * 存储策略 + */ + private final ConfigStorage configStorage = new ConfigStorage(); + + @Data + @NoArgsConstructor + public static class HostConfig implements Serializable { + private static final long serialVersionUID = -4172767630740346001L; + + /** + * 对应于:https://api.weixin.qq.com + */ + private String apiHost; + + /** + * 对应于:https://open.weixin.qq.com + */ + private String openHost; + + /** + * 对应于:https://mp.weixin.qq.com + */ + private String mpHost; + } + + @Data + @NoArgsConstructor + public static class ConfigStorage implements Serializable { + private static final long serialVersionUID = 4815731027000065434L; + + /** + * 存储类型. + */ + private StorageType type = StorageType.MEMORY; + + /** + * 指定key前缀. + */ + private String keyPrefix = "wx:ma:multi"; + + /** + * redis连接配置. + */ + private final WxMaMultiRedisProperties redis = new WxMaMultiRedisProperties(); + + /** + * http客户端类型. + */ + private HttpClientType httpClientType = HttpClientType.HTTP_COMPONENTS; + + /** + * http代理主机. + */ + private String httpProxyHost; + + /** + * http代理端口. + */ + private Integer httpProxyPort; + + /** + * http代理用户名. + */ + private String httpProxyUsername; + + /** + * http代理密码. + */ + private String httpProxyPassword; + + /** + * http 请求最大重试次数 + *
+     *   {@link cn.binarywang.wx.miniapp.api.WxMaService#setMaxRetryTimes(int)}
+     *   {@link cn.binarywang.wx.miniapp.api.impl.BaseWxMaServiceImpl#setMaxRetryTimes(int)}
+     * 
+ */ + private int maxRetryTimes = 5; + + /** + * http 请求重试间隔 + *
+     *   {@link cn.binarywang.wx.miniapp.api.WxMaService#setRetrySleepMillis(int)}
+     *   {@link cn.binarywang.wx.miniapp.api.impl.BaseWxMaServiceImpl#setRetrySleepMillis(int)}
+     * 
+ */ + private int retrySleepMillis = 1000; + } + + public enum StorageType { + /** + * 内存 + */ + MEMORY, + /** + * jedis + */ + JEDIS, + /** + * redisson + */ + REDISSON, + /** + * redisTemplate + */ + REDIS_TEMPLATE + } + + public enum HttpClientType { + /** + * HttpClient + */ + HTTP_CLIENT, + /** + * OkHttp + */ + OK_HTTP, + /** + * JoddHttp + */ + JODD_HTTP, + /** + * HttpComponents + */ + HTTP_COMPONENTS + } +} diff --git a/solon-plugins/wx-java-miniapp-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/miniapp/properties/WxMaMultiRedisProperties.java b/solon-plugins/wx-java-miniapp-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/miniapp/properties/WxMaMultiRedisProperties.java new file mode 100644 index 0000000000..1f4c07806e --- /dev/null +++ b/solon-plugins/wx-java-miniapp-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/miniapp/properties/WxMaMultiRedisProperties.java @@ -0,0 +1,56 @@ +package com.binarywang.solon.wxjava.miniapp.properties; + +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * @author monch + * created on 2024/9/6 + */ +@Data +@NoArgsConstructor +public class WxMaMultiRedisProperties implements Serializable { + private static final long serialVersionUID = -5924815351660074401L; + + /** + * 主机地址. + */ + private String host = "127.0.0.1"; + + /** + * 端口号. + */ + private int port = 6379; + + /** + * 密码. + */ + private String password; + + /** + * 超时. + */ + private int timeout = 2000; + + /** + * 数据库. + */ + private int database = 0; + + /** + * sentinel ips + */ + private String sentinelIps; + + /** + * sentinel name + */ + private String sentinelName; + + private Integer maxActive; + private Integer maxIdle; + private Integer maxWaitMillis; + private Integer minIdle; +} diff --git a/solon-plugins/wx-java-miniapp-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/miniapp/properties/WxMaSingleProperties.java b/solon-plugins/wx-java-miniapp-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/miniapp/properties/WxMaSingleProperties.java new file mode 100644 index 0000000000..f61985716e --- /dev/null +++ b/solon-plugins/wx-java-miniapp-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/miniapp/properties/WxMaSingleProperties.java @@ -0,0 +1,40 @@ +package com.binarywang.solon.wxjava.miniapp.properties; + +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * @author monch + * created on 2024/9/6 + */ +@Data +@NoArgsConstructor +public class WxMaSingleProperties implements Serializable { + private static final long serialVersionUID = 1980986361098922525L; + /** + * 设置微信公众号的 appid. + */ + private String appId; + + /** + * 设置微信公众号的 app secret. + */ + private String appSecret; + + /** + * 设置微信公众号的 token. + */ + private String token; + + /** + * 设置微信公众号的 EncodingAESKey. + */ + private String aesKey; + + /** + * 是否使用稳定版 Access Token + */ + private boolean useStableAccessToken = false; +} diff --git a/solon-plugins/wx-java-miniapp-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/miniapp/service/WxMaMultiServices.java b/solon-plugins/wx-java-miniapp-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/miniapp/service/WxMaMultiServices.java new file mode 100644 index 0000000000..80d073cceb --- /dev/null +++ b/solon-plugins/wx-java-miniapp-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/miniapp/service/WxMaMultiServices.java @@ -0,0 +1,27 @@ +package com.binarywang.solon.wxjava.miniapp.service; + + +import cn.binarywang.wx.miniapp.api.WxMaService; + +/** + * 微信小程序 {@link WxMaService} 所有实例存放类. + * + * @author monch + * created on 2024/9/6 + */ +public interface WxMaMultiServices { + /** + * 通过租户 Id 获取 WxMaService + * + * @param tenantId 租户 Id + * @return WxMaService + */ + WxMaService getWxMaService(String tenantId); + + /** + * 根据租户 Id,从列表中移除一个 WxMaService 实例 + * + * @param tenantId 租户 Id + */ + void removeWxMaService(String tenantId); +} diff --git a/solon-plugins/wx-java-miniapp-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/miniapp/service/WxMaMultiServicesImpl.java b/solon-plugins/wx-java-miniapp-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/miniapp/service/WxMaMultiServicesImpl.java new file mode 100644 index 0000000000..d0ba21cdb8 --- /dev/null +++ b/solon-plugins/wx-java-miniapp-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/miniapp/service/WxMaMultiServicesImpl.java @@ -0,0 +1,36 @@ +package com.binarywang.solon.wxjava.miniapp.service; + +import cn.binarywang.wx.miniapp.api.WxMaService; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * 微信小程序 {@link WxMaMultiServices} 默认实现 + * + * @author monch + * created on 2024/9/6 + */ +public class WxMaMultiServicesImpl implements WxMaMultiServices { + private final Map services = new ConcurrentHashMap<>(); + + @Override + public WxMaService getWxMaService(String tenantId) { + return this.services.get(tenantId); + } + + /** + * 根据租户 Id,添加一个 WxMaService 到列表 + * + * @param tenantId 租户 Id + * @param wxMaService WxMaService 实例 + */ + public void addWxMaService(String tenantId, WxMaService wxMaService) { + this.services.put(tenantId, wxMaService); + } + + @Override + public void removeWxMaService(String tenantId) { + this.services.remove(tenantId); + } +} diff --git a/solon-plugins/wx-java-miniapp-multi-solon-plugin/src/main/resources/META-INF/solon/wx-java-miniapp-multi-solon-plugin.properties b/solon-plugins/wx-java-miniapp-multi-solon-plugin/src/main/resources/META-INF/solon/wx-java-miniapp-multi-solon-plugin.properties new file mode 100644 index 0000000000..9d3e2557a8 --- /dev/null +++ b/solon-plugins/wx-java-miniapp-multi-solon-plugin/src/main/resources/META-INF/solon/wx-java-miniapp-multi-solon-plugin.properties @@ -0,0 +1,2 @@ +solon.plugin=com.binarywang.solon.wxjava.miniapp.integration.WxMiniappMultiPluginImpl +solon.plugin.priority=10 diff --git a/solon-plugins/wx-java-miniapp-multi-solon-plugin/src/test/java/features/test/LoadTest.java b/solon-plugins/wx-java-miniapp-multi-solon-plugin/src/test/java/features/test/LoadTest.java new file mode 100644 index 0000000000..d049f5a51a --- /dev/null +++ b/solon-plugins/wx-java-miniapp-multi-solon-plugin/src/test/java/features/test/LoadTest.java @@ -0,0 +1,15 @@ +package features.test; + +import org.junit.jupiter.api.Test; +import org.noear.solon.test.SolonTest; + +/** + * @author noear 2024/9/4 created + */ +@SolonTest +public class LoadTest { + @Test + public void load(){ + + } +} diff --git a/solon-plugins/wx-java-miniapp-multi-solon-plugin/src/test/resources/app.properties b/solon-plugins/wx-java-miniapp-multi-solon-plugin/src/test/resources/app.properties new file mode 100644 index 0000000000..6522b172c6 --- /dev/null +++ b/solon-plugins/wx-java-miniapp-multi-solon-plugin/src/test/resources/app.properties @@ -0,0 +1,38 @@ +# 公众号配置 +## 应用 1 配置(必填) +wx.ma.apps.tenantId1.app-id=appId +wx.ma.apps.tenantId1.app-secret=@secret +## 选填 +wx.ma.apps.tenantId1.token=@token +wx.ma.apps.tenantId1.aes-key=@aesKey +wx.ma.apps.tenantId1.use-stable-access-token=@useStableAccessToken +## 应用 2 配置(必填) +wx.ma.apps.tenantId2.app-id=@appId +wx.ma.apps.tenantId2.app-secret =@secret +## 选填 +wx.ma.apps.tenantId2.token=@token +wx.ma.apps.tenantId2.aes-key=@aesKey +wx.ma.apps.tenantId2.use-stable-access-token=@useStableAccessToken + +# ConfigStorage 配置(选填) +## 配置类型: memory(默认), jedis, redisson +wx.ma.config-storage.type=memory +## 相关redis前缀配置: wx:ma:multi(默认) +wx.ma.config-storage.key-prefix=wx:ma:multi +wx.ma.config-storage.redis.host=127.0.0.1 +wx.ma.config-storage.redis.port=6379 +## 单机和 sentinel 同时存在时,优先使用sentinel配置 +# wx.ma.config-storage.redis.sentinel-ips=127.0.0.1:16379,127.0.0.1:26379 +# wx.ma.config-storage.redis.sentinel-name=mymaster + +# http 客户端配置(选填) +## # http客户端类型: http_client(默认), ok_http, jodd_http +wx.ma.config-storage.http-client-type=http_client +wx.ma.config-storage.http-proxy-host= +wx.ma.config-storage.http-proxy-port= +wx.ma.config-storage.http-proxy-username= +wx.ma.config-storage.http-proxy-password= +## 最大重试次数,默认:5 次,如果小于 0,则为 0 +wx.ma.config-storage.max-retry-times=5 +## 重试时间间隔步进,默认:1000 毫秒,如果小于 0,则为 1000 +wx.ma.config-storage.retry-sleep-millis=1000 diff --git a/solon-plugins/wx-java-miniapp-solon-plugin/README.md b/solon-plugins/wx-java-miniapp-solon-plugin/README.md new file mode 100644 index 0000000000..3d1d7517f7 --- /dev/null +++ b/solon-plugins/wx-java-miniapp-solon-plugin/README.md @@ -0,0 +1,35 @@ +# wx-java-miniapp-solon-plugin +## 快速开始 +1. 引入依赖 + ```xml + + com.github.binarywang + wx-java-miniapp-solon-plugin + ${version} + + ``` +2. 添加配置(app.properties) + ```properties + # 公众号配置(必填) + wx.miniapp.appid = appId + wx.miniapp.secret = @secret + wx.miniapp.token = @token + wx.miniapp.aesKey = @aesKey + wx.miniapp.msgDataFormat = @msgDataFormat # 消息格式,XML或者JSON. + # 存储配置redis(可选) + # 注意: 指定redis.host值后不会使用容器注入的redis连接(JedisPool) + wx.miniapp.config-storage.type = Jedis # 配置类型: Memory(默认), Jedis, RedisTemplate + wx.miniapp.config-storage.key-prefix = wa # 相关redis前缀配置: wa(默认) + wx.miniapp.config-storage.redis.host = 127.0.0.1 + wx.miniapp.config-storage.redis.port = 6379 + # http客户端配置 + wx.miniapp.config-storage.http-client-type=HttpClient # http客户端类型: HttpClient(默认), OkHttp, JoddHttp + wx.miniapp.config-storage.http-proxy-host= + wx.miniapp.config-storage.http-proxy-port= + wx.miniapp.config-storage.http-proxy-username= + wx.miniapp.config-storage.http-proxy-password= + ``` +3. 自动注入的类型 +- `WxMaService` +- `WxMaConfig` + diff --git a/solon-plugins/wx-java-miniapp-solon-plugin/pom.xml b/solon-plugins/wx-java-miniapp-solon-plugin/pom.xml new file mode 100644 index 0000000000..5ad8da85e6 --- /dev/null +++ b/solon-plugins/wx-java-miniapp-solon-plugin/pom.xml @@ -0,0 +1,43 @@ + + + + wx-java-solon-plugins + com.github.binarywang + 4.8.0 + + 4.0.0 + + wx-java-miniapp-solon-plugin + WxJava - Solon Plugin for MiniApp + 微信小程序开发的 Solon Plugin + + + + com.github.binarywang + weixin-java-miniapp + ${project.version} + + + redis.clients + jedis + provided + + + org.redisson + redisson + provided + + + org.jodd + jodd-http + provided + + + com.squareup.okhttp3 + okhttp + provided + + + + diff --git a/solon-plugins/wx-java-miniapp-solon-plugin/src/main/java/com/binarywang/solon/wxjava/miniapp/config/WxMaServiceAutoConfiguration.java b/solon-plugins/wx-java-miniapp-solon-plugin/src/main/java/com/binarywang/solon/wxjava/miniapp/config/WxMaServiceAutoConfiguration.java new file mode 100644 index 0000000000..78f95380b2 --- /dev/null +++ b/solon-plugins/wx-java-miniapp-solon-plugin/src/main/java/com/binarywang/solon/wxjava/miniapp/config/WxMaServiceAutoConfiguration.java @@ -0,0 +1,58 @@ +package com.binarywang.solon.wxjava.miniapp.config; + +import cn.binarywang.wx.miniapp.api.WxMaService; +import cn.binarywang.wx.miniapp.api.impl.WxMaServiceHttpClientImpl; +import cn.binarywang.wx.miniapp.api.impl.WxMaServiceHttpComponentsImpl; +import cn.binarywang.wx.miniapp.api.impl.WxMaServiceImpl; +import cn.binarywang.wx.miniapp.api.impl.WxMaServiceJoddHttpImpl; +import cn.binarywang.wx.miniapp.api.impl.WxMaServiceOkHttpImpl; +import cn.binarywang.wx.miniapp.config.WxMaConfig; +import com.binarywang.solon.wxjava.miniapp.enums.HttpClientType; +import com.binarywang.solon.wxjava.miniapp.properties.WxMaProperties; +import lombok.AllArgsConstructor; +import org.noear.solon.annotation.Bean; +import org.noear.solon.annotation.Condition; +import org.noear.solon.annotation.Configuration; + +/** + * 微信小程序平台相关服务自动注册. + * + * @author someone TaoYu + */ +@Configuration +@AllArgsConstructor +public class WxMaServiceAutoConfiguration { + + private final WxMaProperties wxMaProperties; + + /** + * 小程序service. + * + * @return 小程序service + */ + @Bean + @Condition(onMissingBean=WxMaService.class, onBean=WxMaConfig.class) + public WxMaService wxMaService(WxMaConfig wxMaConfig) { + HttpClientType httpClientType = wxMaProperties.getConfigStorage().getHttpClientType(); + WxMaService wxMaService; + switch (httpClientType) { + case OkHttp: + wxMaService = new WxMaServiceOkHttpImpl(); + break; + case JoddHttp: + wxMaService = new WxMaServiceJoddHttpImpl(); + break; + case HttpClient: + wxMaService = new WxMaServiceHttpClientImpl(); + break; + case HttpComponents: + wxMaService = new WxMaServiceHttpComponentsImpl(); + break; + default: + wxMaService = new WxMaServiceImpl(); + break; + } + wxMaService.setWxMaConfig(wxMaConfig); + return wxMaService; + } +} diff --git a/solon-plugins/wx-java-miniapp-solon-plugin/src/main/java/com/binarywang/solon/wxjava/miniapp/config/storage/AbstractWxMaConfigStorageConfiguration.java b/solon-plugins/wx-java-miniapp-solon-plugin/src/main/java/com/binarywang/solon/wxjava/miniapp/config/storage/AbstractWxMaConfigStorageConfiguration.java new file mode 100644 index 0000000000..acc147a705 --- /dev/null +++ b/solon-plugins/wx-java-miniapp-solon-plugin/src/main/java/com/binarywang/solon/wxjava/miniapp/config/storage/AbstractWxMaConfigStorageConfiguration.java @@ -0,0 +1,40 @@ +package com.binarywang.solon.wxjava.miniapp.config.storage; + +import cn.binarywang.wx.miniapp.config.impl.WxMaDefaultConfigImpl; +import com.binarywang.solon.wxjava.miniapp.properties.WxMaProperties; +import org.apache.commons.lang3.StringUtils; + +/** + * @author yl TaoYu + */ +public abstract class AbstractWxMaConfigStorageConfiguration { + + protected WxMaDefaultConfigImpl config(WxMaDefaultConfigImpl config, WxMaProperties properties) { + 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()); + + WxMaProperties.ConfigStorage configStorageProperties = properties.getConfigStorage(); + config.setHttpProxyHost(configStorageProperties.getHttpProxyHost()); + config.setHttpProxyUsername(configStorageProperties.getHttpProxyUsername()); + config.setHttpProxyPassword(configStorageProperties.getHttpProxyPassword()); + if (configStorageProperties.getHttpProxyPort() != null) { + config.setHttpProxyPort(configStorageProperties.getHttpProxyPort()); + } + + int maxRetryTimes = configStorageProperties.getMaxRetryTimes(); + if (configStorageProperties.getMaxRetryTimes() < 0) { + maxRetryTimes = 0; + } + int retrySleepMillis = configStorageProperties.getRetrySleepMillis(); + if (retrySleepMillis < 0) { + retrySleepMillis = 1000; + } + config.setRetrySleepMillis(retrySleepMillis); + config.setMaxRetryTimes(maxRetryTimes); + return config; + } +} diff --git a/solon-plugins/wx-java-miniapp-solon-plugin/src/main/java/com/binarywang/solon/wxjava/miniapp/config/storage/WxMaInJedisConfigStorageConfiguration.java b/solon-plugins/wx-java-miniapp-solon-plugin/src/main/java/com/binarywang/solon/wxjava/miniapp/config/storage/WxMaInJedisConfigStorageConfiguration.java new file mode 100644 index 0000000000..da8c4701ba --- /dev/null +++ b/solon-plugins/wx-java-miniapp-solon-plugin/src/main/java/com/binarywang/solon/wxjava/miniapp/config/storage/WxMaInJedisConfigStorageConfiguration.java @@ -0,0 +1,72 @@ +package com.binarywang.solon.wxjava.miniapp.config.storage; + +import cn.binarywang.wx.miniapp.config.WxMaConfig; +import cn.binarywang.wx.miniapp.config.impl.WxMaRedisBetterConfigImpl; +import com.binarywang.solon.wxjava.miniapp.properties.RedisProperties; +import com.binarywang.solon.wxjava.miniapp.properties.WxMaProperties; +import lombok.RequiredArgsConstructor; +import me.chanjar.weixin.common.redis.JedisWxRedisOps; +import me.chanjar.weixin.common.redis.WxRedisOps; +import org.apache.commons.lang3.StringUtils; +import org.noear.solon.annotation.Bean; +import org.noear.solon.annotation.Condition; +import org.noear.solon.annotation.Configuration; +import org.noear.solon.core.AppContext; +import redis.clients.jedis.JedisPool; +import redis.clients.jedis.JedisPoolConfig; + +/** + * @author yl TaoYu + */ +@Configuration +@Condition( + onProperty = "${"+WxMaProperties.PREFIX + ".configStorage.type} = jedis", + onClass = JedisPool.class +) +@RequiredArgsConstructor +public class WxMaInJedisConfigStorageConfiguration extends AbstractWxMaConfigStorageConfiguration { + private final WxMaProperties properties; + private final AppContext applicationContext; + + @Bean + @Condition(onMissingBean=WxMaConfig.class) + public WxMaConfig wxMaConfig() { + WxMaRedisBetterConfigImpl config = getWxMaRedisBetterConfigImpl(); + return this.config(config, properties); + } + + private WxMaRedisBetterConfigImpl getWxMaRedisBetterConfigImpl() { + RedisProperties redisProperties = properties.getConfigStorage().getRedis(); + JedisPool jedisPool; + if (redisProperties != null && StringUtils.isNotEmpty(redisProperties.getHost())) { + jedisPool = getJedisPool(); + } else { + jedisPool = applicationContext.getBean(JedisPool.class); + } + WxRedisOps redisOps = new JedisWxRedisOps(jedisPool); + return new WxMaRedisBetterConfigImpl(redisOps, properties.getConfigStorage().getKeyPrefix()); + } + + private JedisPool getJedisPool() { + WxMaProperties.ConfigStorage storage = properties.getConfigStorage(); + RedisProperties redis = storage.getRedis(); + + JedisPoolConfig config = new JedisPoolConfig(); + if (redis.getMaxActive() != null) { + config.setMaxTotal(redis.getMaxActive()); + } + if (redis.getMaxIdle() != null) { + config.setMaxIdle(redis.getMaxIdle()); + } + if (redis.getMaxWaitMillis() != null) { + config.setMaxWaitMillis(redis.getMaxWaitMillis()); + } + if (redis.getMinIdle() != null) { + config.setMinIdle(redis.getMinIdle()); + } + config.setTestOnBorrow(true); + config.setTestWhileIdle(true); + + return new JedisPool(config, redis.getHost(), redis.getPort(), redis.getTimeout(), redis.getPassword(), redis.getDatabase()); + } +} diff --git a/solon-plugins/wx-java-miniapp-solon-plugin/src/main/java/com/binarywang/solon/wxjava/miniapp/config/storage/WxMaInMemoryConfigStorageConfiguration.java b/solon-plugins/wx-java-miniapp-solon-plugin/src/main/java/com/binarywang/solon/wxjava/miniapp/config/storage/WxMaInMemoryConfigStorageConfiguration.java new file mode 100644 index 0000000000..958742d2aa --- /dev/null +++ b/solon-plugins/wx-java-miniapp-solon-plugin/src/main/java/com/binarywang/solon/wxjava/miniapp/config/storage/WxMaInMemoryConfigStorageConfiguration.java @@ -0,0 +1,28 @@ +package com.binarywang.solon.wxjava.miniapp.config.storage; + +import cn.binarywang.wx.miniapp.config.WxMaConfig; +import cn.binarywang.wx.miniapp.config.impl.WxMaDefaultConfigImpl; +import com.binarywang.solon.wxjava.miniapp.properties.WxMaProperties; +import lombok.RequiredArgsConstructor; +import org.noear.solon.annotation.Bean; +import org.noear.solon.annotation.Condition; +import org.noear.solon.annotation.Configuration; + +/** + * @author yl TaoYu + */ +@Configuration +@Condition( + onProperty = "${"+WxMaProperties.PREFIX + ".configStorage.type:memory} = memory" +) +@RequiredArgsConstructor +public class WxMaInMemoryConfigStorageConfiguration extends AbstractWxMaConfigStorageConfiguration { + private final WxMaProperties properties; + + @Bean + @Condition(onMissingBean=WxMaConfig.class) + public WxMaConfig wxMaConfig() { + WxMaDefaultConfigImpl config = new WxMaDefaultConfigImpl(); + return this.config(config, properties); + } +} diff --git a/solon-plugins/wx-java-miniapp-solon-plugin/src/main/java/com/binarywang/solon/wxjava/miniapp/config/storage/WxMaInRedissonConfigStorageConfiguration.java b/solon-plugins/wx-java-miniapp-solon-plugin/src/main/java/com/binarywang/solon/wxjava/miniapp/config/storage/WxMaInRedissonConfigStorageConfiguration.java new file mode 100644 index 0000000000..af7c11448e --- /dev/null +++ b/solon-plugins/wx-java-miniapp-solon-plugin/src/main/java/com/binarywang/solon/wxjava/miniapp/config/storage/WxMaInRedissonConfigStorageConfiguration.java @@ -0,0 +1,61 @@ +package com.binarywang.solon.wxjava.miniapp.config.storage; + +import cn.binarywang.wx.miniapp.config.WxMaConfig; +import cn.binarywang.wx.miniapp.config.impl.WxMaRedissonConfigImpl; +import com.binarywang.solon.wxjava.miniapp.properties.RedisProperties; +import com.binarywang.solon.wxjava.miniapp.properties.WxMaProperties; +import lombok.RequiredArgsConstructor; +import org.apache.commons.lang3.StringUtils; +import org.noear.solon.annotation.Bean; +import org.noear.solon.annotation.Condition; +import org.noear.solon.annotation.Configuration; +import org.noear.solon.core.AppContext; +import org.redisson.Redisson; +import org.redisson.api.RedissonClient; +import org.redisson.config.Config; +import org.redisson.config.TransportMode; + +/** + * @author yl TaoYu + */ +@Configuration +@Condition( + onProperty = "${"+WxMaProperties.PREFIX + ".configStorage.type} = redisson", + onClass = Redisson.class +) +@RequiredArgsConstructor +public class WxMaInRedissonConfigStorageConfiguration extends AbstractWxMaConfigStorageConfiguration { + private final WxMaProperties properties; + private final AppContext applicationContext; + + @Bean + @Condition(onMissingBean=WxMaConfig.class) + public WxMaConfig wxMaConfig() { + WxMaRedissonConfigImpl config = getWxMaInRedissonConfigStorage(); + return this.config(config, properties); + } + + private WxMaRedissonConfigImpl getWxMaInRedissonConfigStorage() { + RedisProperties redisProperties = properties.getConfigStorage().getRedis(); + RedissonClient redissonClient; + if (redisProperties != null && StringUtils.isNotEmpty(redisProperties.getHost())) { + redissonClient = getRedissonClient(); + } else { + redissonClient = applicationContext.getBean(RedissonClient.class); + } + return new WxMaRedissonConfigImpl(redissonClient, properties.getConfigStorage().getKeyPrefix()); + } + + private RedissonClient getRedissonClient() { + WxMaProperties.ConfigStorage storage = properties.getConfigStorage(); + RedisProperties 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/solon-plugins/wx-java-miniapp-solon-plugin/src/main/java/com/binarywang/solon/wxjava/miniapp/enums/HttpClientType.java b/solon-plugins/wx-java-miniapp-solon-plugin/src/main/java/com/binarywang/solon/wxjava/miniapp/enums/HttpClientType.java new file mode 100644 index 0000000000..d116a30cf6 --- /dev/null +++ b/solon-plugins/wx-java-miniapp-solon-plugin/src/main/java/com/binarywang/solon/wxjava/miniapp/enums/HttpClientType.java @@ -0,0 +1,26 @@ +package com.binarywang.solon.wxjava.miniapp.enums; + +/** + * httpclient类型. + * + * @author Binary Wang + * created on 2020-05-25 + */ +public enum HttpClientType { + /** + * HttpClient. + */ + HttpClient, + /** + * OkHttp. + */ + OkHttp, + /** + * JoddHttp. + */ + JoddHttp, + /** + * HttpComponents. + */ + HttpComponents, +} diff --git a/solon-plugins/wx-java-miniapp-solon-plugin/src/main/java/com/binarywang/solon/wxjava/miniapp/enums/StorageType.java b/solon-plugins/wx-java-miniapp-solon-plugin/src/main/java/com/binarywang/solon/wxjava/miniapp/enums/StorageType.java new file mode 100644 index 0000000000..b82261ba8a --- /dev/null +++ b/solon-plugins/wx-java-miniapp-solon-plugin/src/main/java/com/binarywang/solon/wxjava/miniapp/enums/StorageType.java @@ -0,0 +1,26 @@ +package com.binarywang.solon.wxjava.miniapp.enums; + +/** + * storage类型. + * + * @author Binary Wang + * created on 2020-05-25 + */ +public enum StorageType { + /** + * 内存. + */ + Memory, + /** + * redis(JedisClient). + */ + Jedis, + /** + * redis(Redisson). + */ + Redisson, + /** + * redis(RedisTemplate). + */ + RedisTemplate +} diff --git a/solon-plugins/wx-java-miniapp-solon-plugin/src/main/java/com/binarywang/solon/wxjava/miniapp/integration/WxMiniappPluginImpl.java b/solon-plugins/wx-java-miniapp-solon-plugin/src/main/java/com/binarywang/solon/wxjava/miniapp/integration/WxMiniappPluginImpl.java new file mode 100644 index 0000000000..88d1c3023a --- /dev/null +++ b/solon-plugins/wx-java-miniapp-solon-plugin/src/main/java/com/binarywang/solon/wxjava/miniapp/integration/WxMiniappPluginImpl.java @@ -0,0 +1,25 @@ +package com.binarywang.solon.wxjava.miniapp.integration; + +import com.binarywang.solon.wxjava.miniapp.config.WxMaServiceAutoConfiguration; +import com.binarywang.solon.wxjava.miniapp.config.storage.WxMaInJedisConfigStorageConfiguration; +import com.binarywang.solon.wxjava.miniapp.config.storage.WxMaInMemoryConfigStorageConfiguration; +import com.binarywang.solon.wxjava.miniapp.config.storage.WxMaInRedissonConfigStorageConfiguration; +import com.binarywang.solon.wxjava.miniapp.properties.WxMaProperties; +import org.noear.solon.core.AppContext; +import org.noear.solon.core.Plugin; + +/** + * @author noear 2024/9/2 created + */ +public class WxMiniappPluginImpl implements Plugin { + @Override + public void start(AppContext context) throws Throwable { + context.beanMake(WxMaProperties.class); + + context.beanMake(WxMaServiceAutoConfiguration.class); + + context.beanMake(WxMaInMemoryConfigStorageConfiguration.class); + context.beanMake(WxMaInJedisConfigStorageConfiguration.class); + context.beanMake(WxMaInRedissonConfigStorageConfiguration.class); + } +} diff --git a/solon-plugins/wx-java-miniapp-solon-plugin/src/main/java/com/binarywang/solon/wxjava/miniapp/properties/RedisProperties.java b/solon-plugins/wx-java-miniapp-solon-plugin/src/main/java/com/binarywang/solon/wxjava/miniapp/properties/RedisProperties.java new file mode 100644 index 0000000000..021a4b1b6b --- /dev/null +++ b/solon-plugins/wx-java-miniapp-solon-plugin/src/main/java/com/binarywang/solon/wxjava/miniapp/properties/RedisProperties.java @@ -0,0 +1,43 @@ +package com.binarywang.solon.wxjava.miniapp.properties; + +import lombok.Data; + +/** + * redis 配置. + * + * @author Binary Wang + * created on 2020-08-30 + */ +@Data +public class RedisProperties { + + /** + * 主机地址.不填则从solon容器内获取JedisPool + */ + 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/solon-plugins/wx-java-miniapp-solon-plugin/src/main/java/com/binarywang/solon/wxjava/miniapp/properties/WxMaProperties.java b/solon-plugins/wx-java-miniapp-solon-plugin/src/main/java/com/binarywang/solon/wxjava/miniapp/properties/WxMaProperties.java new file mode 100644 index 0000000000..4493b6aec5 --- /dev/null +++ b/solon-plugins/wx-java-miniapp-solon-plugin/src/main/java/com/binarywang/solon/wxjava/miniapp/properties/WxMaProperties.java @@ -0,0 +1,117 @@ +package com.binarywang.solon.wxjava.miniapp.properties; + +import com.binarywang.solon.wxjava.miniapp.enums.HttpClientType; +import com.binarywang.solon.wxjava.miniapp.enums.StorageType; +import lombok.Data; +import org.noear.solon.annotation.Configuration; +import org.noear.solon.annotation.Inject; + +import static com.binarywang.solon.wxjava.miniapp.properties.WxMaProperties.PREFIX; + +/** + * 属性配置类. + * + * @author Binary Wang + * created on 2019-08-10 + */ +@Data +@Configuration +@Inject("${" + PREFIX + "}") +public class WxMaProperties { + public static final String PREFIX = "wx.miniapp"; + + /** + * 设置微信小程序的appid. + */ + private String appid; + + /** + * 设置微信小程序的Secret. + */ + private String secret; + + /** + * 设置微信小程序消息服务器配置的token. + */ + private String token; + + /** + * 设置微信小程序消息服务器配置的EncodingAESKey. + */ + private String aesKey; + + /** + * 消息格式,XML或者JSON. + */ + private String msgDataFormat; + + /** + * 是否使用稳定版 Access Token + */ + private boolean useStableAccessToken = false; + + /** + * 存储策略 + */ + private final ConfigStorage configStorage = new ConfigStorage(); + + @Data + public static class ConfigStorage { + + /** + * 存储类型. + */ + private StorageType type = StorageType.Memory; + + /** + * 指定key前缀. + */ + private String keyPrefix = "wa"; + + /** + * redis连接配置. + */ + private final RedisProperties redis = new RedisProperties(); + + /** + * http客户端类型. + */ + private HttpClientType httpClientType = HttpClientType.HttpComponents; + + /** + * http代理主机. + */ + private String httpProxyHost; + + /** + * http代理端口. + */ + private Integer httpProxyPort; + + /** + * http代理用户名. + */ + private String httpProxyUsername; + + /** + * http代理密码. + */ + private String httpProxyPassword; + + /** + * http 请求重试间隔 + *
+     *   {@link cn.binarywang.wx.miniapp.api.impl.BaseWxMaServiceImpl#setRetrySleepMillis(int)}
+     * 
+ */ + private int retrySleepMillis = 1000; + /** + * http 请求最大重试次数 + *
+     *   {@link cn.binarywang.wx.miniapp.api.impl.BaseWxMaServiceImpl#setMaxRetryTimes(int)}
+     * 
+ */ + private int maxRetryTimes = 5; + } + +} diff --git a/solon-plugins/wx-java-miniapp-solon-plugin/src/main/resources/META-INF/solon/wx-java-miniapp-solon-plugin.properties b/solon-plugins/wx-java-miniapp-solon-plugin/src/main/resources/META-INF/solon/wx-java-miniapp-solon-plugin.properties new file mode 100644 index 0000000000..ba1049647e --- /dev/null +++ b/solon-plugins/wx-java-miniapp-solon-plugin/src/main/resources/META-INF/solon/wx-java-miniapp-solon-plugin.properties @@ -0,0 +1,2 @@ +solon.plugin=com.binarywang.solon.wxjava.miniapp.integration.WxMiniappPluginImpl +solon.plugin.priority=10 diff --git a/solon-plugins/wx-java-miniapp-solon-plugin/src/test/java/features/test/LoadTest.java b/solon-plugins/wx-java-miniapp-solon-plugin/src/test/java/features/test/LoadTest.java new file mode 100644 index 0000000000..d049f5a51a --- /dev/null +++ b/solon-plugins/wx-java-miniapp-solon-plugin/src/test/java/features/test/LoadTest.java @@ -0,0 +1,15 @@ +package features.test; + +import org.junit.jupiter.api.Test; +import org.noear.solon.test.SolonTest; + +/** + * @author noear 2024/9/4 created + */ +@SolonTest +public class LoadTest { + @Test + public void load(){ + + } +} diff --git a/solon-plugins/wx-java-miniapp-solon-plugin/src/test/resources/app.properties b/solon-plugins/wx-java-miniapp-solon-plugin/src/test/resources/app.properties new file mode 100644 index 0000000000..22a9a4e627 --- /dev/null +++ b/solon-plugins/wx-java-miniapp-solon-plugin/src/test/resources/app.properties @@ -0,0 +1,18 @@ +# ?????(??) +wx.miniapp.appid = appId +wx.miniapp.secret = @secret +wx.miniapp.token = @token +wx.miniapp.aesKey = @aesKey +wx.miniapp.msgDataFormat = @msgDataFormat # ?????XML??JSON. +# ????redis(??) +# ??: ??redis.host???????????redis??(JedisPool) +wx.miniapp.config-storage.type = Jedis # ????: Memory(??), Jedis, RedisTemplate +wx.miniapp.config-storage.key-prefix = wa # ??redis????: wa(??) +wx.miniapp.config-storage.redis.host = 127.0.0.1 +wx.miniapp.config-storage.redis.port = 6379 +# http????? +wx.miniapp.config-storage.http-client-type=HttpClient # http?????: HttpClient(??), OkHttp, JoddHttp +wx.miniapp.config-storage.http-proxy-host= +wx.miniapp.config-storage.http-proxy-port= +wx.miniapp.config-storage.http-proxy-username= +wx.miniapp.config-storage.http-proxy-password= diff --git a/solon-plugins/wx-java-mp-multi-solon-plugin/README.md b/solon-plugins/wx-java-mp-multi-solon-plugin/README.md new file mode 100644 index 0000000000..0d2b332d5a --- /dev/null +++ b/solon-plugins/wx-java-mp-multi-solon-plugin/README.md @@ -0,0 +1,100 @@ +# wx-java-mp-multi-solon-plugin + +## 快速开始 + +1. 引入依赖 + ```xml + + com.github.binarywang + wx-java-mp-multi-solon-plugin + ${version} + + ``` +2. 添加配置(app.properties) + ```properties + # 公众号配置 + ## 应用 1 配置(必填) + wx.mp.apps.tenantId1.app-id=appId + wx.mp.apps.tenantId1.app-secret=@secret + ## 选填 + wx.mp.apps.tenantId1.token=@token + wx.mp.apps.tenantId1.aes-key=@aesKey + wx.mp.apps.tenantId1.use-stable-access-token=@useStableAccessToken + ## 应用 2 配置(必填) + wx.mp.apps.tenantId2.app-id=@appId + wx.mp.apps.tenantId2.app-secret =@secret + ## 选填 + wx.mp.apps.tenantId2.token=@token + wx.mp.apps.tenantId2.aes-key=@aesKey + wx.mp.apps.tenantId2.use-stable-access-token=@useStableAccessToken + + # ConfigStorage 配置(选填) + ## 配置类型: memory(默认), jedis, redisson, redis_template + wx.mp.config-storage.type=memory + ## 相关redis前缀配置: wx:mp:multi(默认) + wx.mp.config-storage.key-prefix=wx:mp:multi + wx.mp.config-storage.redis.host=127.0.0.1 + wx.mp.config-storage.redis.port=6379 + ## 单机和 sentinel 同时存在时,优先使用sentinel配置 + # wx.mp.config-storage.redis.sentinel-ips=127.0.0.1:16379,127.0.0.1:26379 + # wx.mp.config-storage.redis.sentinel-name=mymaster + + # http 客户端配置(选填) + ## # http客户端类型: http_client(默认), ok_http, jodd_http + wx.mp.config-storage.http-client-type=http_client + wx.mp.config-storage.http-proxy-host= + wx.mp.config-storage.http-proxy-port= + wx.mp.config-storage.http-proxy-username= + wx.mp.config-storage.http-proxy-password= + ## 最大重试次数,默认:5 次,如果小于 0,则为 0 + wx.mp.config-storage.max-retry-times=5 + ## 重试时间间隔步进,默认:1000 毫秒,如果小于 0,则为 1000 + wx.mp.config-storage.retry-sleep-millis=1000 + + # 公众号地址 host 配置 + # wx.mp.hosts.api-host=http://proxy.com/ + # wx.mp.hosts.open-host=http://proxy.com/ + # wx.mp.hosts.mp-host=http://proxy.com/ + ``` +3. 自动注入的类型:`WxMpMultiServices` + +4. 使用样例 + +```java +import com.binarywang.solon.wxjava.mp_multi.service.WxMpMultiServices; +import me.chanjar.weixin.mp.api.WxMpService; +import me.chanjar.weixin.mp.api.WxMpUserService; +import org.noear.solon.annotation.Component; +import org.noear.solon.annotation.Inject; + +@Component +public class DemoService { + @Inject + private WxMpMultiServices wxMpMultiServices; + + public void test() { + // 应用 1 的 WxMpService + WxMpService wxMpService1 = wxMpMultiServices.getWxMpService("tenantId1"); + WxMpUserService userService1 = wxMpService1.getUserService(); + userService1.userInfo("xxx"); + // todo ... + + // 应用 2 的 WxMpService + WxMpService wxMpService2 = wxMpMultiServices.getWxMpService("tenantId2"); + WxMpUserService userService2 = wxMpService2.getUserService(); + userService2.userInfo("xxx"); + // todo ... + + // 应用 3 的 WxMpService + WxMpService wxMpService3 = wxMpMultiServices.getWxMpService("tenantId3"); + // 判断是否为空 + if (wxMpService3 == null) { + // todo wxMpService3 为空,请先配置 tenantId3 微信公众号应用参数 + return; + } + WxMpUserService userService3 = wxMpService3.getUserService(); + userService3.userInfo("xxx"); + // todo ... + } +} +``` diff --git a/solon-plugins/wx-java-mp-multi-solon-plugin/pom.xml b/solon-plugins/wx-java-mp-multi-solon-plugin/pom.xml new file mode 100644 index 0000000000..7c02acdfef --- /dev/null +++ b/solon-plugins/wx-java-mp-multi-solon-plugin/pom.xml @@ -0,0 +1,44 @@ + + + + wx-java-solon-plugins + com.github.binarywang + 4.8.0 + + 4.0.0 + + wx-java-mp-multi-solon-plugin + WxJava - Solon Plugin for MP::支持多账号配置 + 微信公众号开发的 Solon Plugin::支持多账号配置 + + + + com.github.binarywang + weixin-java-mp + ${project.version} + + + redis.clients + jedis + provided + + + org.redisson + redisson + provided + + + org.jodd + jodd-http + provided + + + com.squareup.okhttp3 + okhttp + provided + + + + diff --git a/solon-plugins/wx-java-mp-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/mp_multi/configuration/services/AbstractWxMpConfiguration.java b/solon-plugins/wx-java-mp-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/mp_multi/configuration/services/AbstractWxMpConfiguration.java new file mode 100644 index 0000000000..a51c6eaaea --- /dev/null +++ b/solon-plugins/wx-java-mp-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/mp_multi/configuration/services/AbstractWxMpConfiguration.java @@ -0,0 +1,169 @@ +package com.binarywang.solon.wxjava.mp_multi.configuration.services; + +import com.binarywang.solon.wxjava.mp_multi.properties.WxMpMultiProperties; +import com.binarywang.solon.wxjava.mp_multi.properties.WxMpSingleProperties; +import com.binarywang.solon.wxjava.mp_multi.service.WxMpMultiServices; +import com.binarywang.solon.wxjava.mp_multi.service.WxMpMultiServicesImpl; +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.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; +import me.chanjar.weixin.mp.config.WxMpConfigStorage; +import me.chanjar.weixin.mp.config.WxMpHostConfig; +import me.chanjar.weixin.mp.config.impl.WxMpDefaultConfigImpl; +import org.apache.commons.lang3.StringUtils; + +import java.util.Collection; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * WxMpConfigStorage 抽象配置类 + * + * @author yl + * created on 2024/1/23 + */ +@RequiredArgsConstructor +@Slf4j +public abstract class AbstractWxMpConfiguration { + + protected WxMpMultiServices wxMpMultiServices(WxMpMultiProperties wxCpMultiProperties) { + Map appsMap = wxCpMultiProperties.getApps(); + if (appsMap == null || appsMap.isEmpty()) { + log.warn("微信公众号应用参数未配置,通过 WxMpMultiServices#getWxMpService(\"tenantId\")获取实例将返回空"); + return new WxMpMultiServicesImpl(); + } + /** + * 校验 appId 是否唯一,避免使用 redis 缓存 token、ticket 时错乱。 + * + * 查看 {@link me.chanjar.weixin.mp.config.impl.WxMpRedisConfigImpl#setAppId(String)} + */ + Collection apps = appsMap.values(); + if (apps.size() > 1) { + // 校验 appId 是否唯一 + boolean multi = apps.stream() + // 没有 appId,如果不判断是否为空,这里会报 NPE 异常 + .collect(Collectors.groupingBy(c -> c.getAppId() == null ? 0 : c.getAppId(), Collectors.counting())) + .entrySet().stream().anyMatch(e -> e.getValue() > 1); + if (multi) { + throw new RuntimeException("请确保微信公众号配置 appId 的唯一性"); + } + } + WxMpMultiServicesImpl services = new WxMpMultiServicesImpl(); + + Set> entries = appsMap.entrySet(); + for (Map.Entry entry : entries) { + String tenantId = entry.getKey(); + WxMpSingleProperties wxMpSingleProperties = entry.getValue(); + WxMpDefaultConfigImpl storage = this.wxMpConfigStorage(wxCpMultiProperties); + this.configApp(storage, wxMpSingleProperties); + this.configHttp(storage, wxCpMultiProperties.getConfigStorage()); + this.configHost(storage, wxCpMultiProperties.getHosts()); + WxMpService wxCpService = this.wxMpService(storage, wxCpMultiProperties); + services.addWxMpService(tenantId, wxCpService); + } + return services; + } + + /** + * 配置 WxMpDefaultConfigImpl + * + * @param wxMpMultiProperties 参数 + * @return WxMpDefaultConfigImpl + */ + protected abstract WxMpDefaultConfigImpl wxMpConfigStorage(WxMpMultiProperties wxMpMultiProperties); + + public WxMpService wxMpService(WxMpConfigStorage configStorage, WxMpMultiProperties wxMpMultiProperties) { + WxMpMultiProperties.ConfigStorage storage = wxMpMultiProperties.getConfigStorage(); + WxMpMultiProperties.HttpClientType httpClientType = storage.getHttpClientType(); + WxMpService wxMpService; + switch (httpClientType) { + case OK_HTTP: + wxMpService = new WxMpServiceOkHttpImpl(); + break; + case JODD_HTTP: + wxMpService = new WxMpServiceJoddHttpImpl(); + break; + case HTTP_CLIENT: + wxMpService = new WxMpServiceHttpClientImpl(); + break; + case HTTP_COMPONENTS: + wxMpService = new WxMpServiceHttpComponentsImpl(); + break; + default: + wxMpService = new WxMpServiceImpl(); + break; + } + + wxMpService.setWxMpConfigStorage(configStorage); + int maxRetryTimes = storage.getMaxRetryTimes(); + if (maxRetryTimes < 0) { + maxRetryTimes = 0; + } + int retrySleepMillis = storage.getRetrySleepMillis(); + if (retrySleepMillis < 0) { + retrySleepMillis = 1000; + } + wxMpService.setRetrySleepMillis(retrySleepMillis); + wxMpService.setMaxRetryTimes(maxRetryTimes); + return wxMpService; + } + + private void configApp(WxMpDefaultConfigImpl config, WxMpSingleProperties corpProperties) { + String appId = corpProperties.getAppId(); + String appSecret = corpProperties.getAppSecret(); + String token = corpProperties.getToken(); + String aesKey = corpProperties.getAesKey(); + boolean useStableAccessToken = corpProperties.isUseStableAccessToken(); + + config.setAppId(appId); + config.setSecret(appSecret); + if (StringUtils.isNotBlank(token)) { + config.setToken(token); + } + if (StringUtils.isNotBlank(aesKey)) { + config.setAesKey(aesKey); + } + config.setUseStableAccessToken(useStableAccessToken); + } + + private void configHttp(WxMpDefaultConfigImpl config, WxMpMultiProperties.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); + } + } + } + + /** + * wx host config + */ + private void configHost(WxMpDefaultConfigImpl config, WxMpMultiProperties.HostConfig hostConfig) { + if (hostConfig != null) { + String apiHost = hostConfig.getApiHost(); + String mpHost = hostConfig.getMpHost(); + String openHost = hostConfig.getOpenHost(); + WxMpHostConfig wxMpHostConfig = new WxMpHostConfig(); + wxMpHostConfig.setApiHost(StringUtils.isNotBlank(apiHost) ? apiHost : null); + wxMpHostConfig.setMpHost(StringUtils.isNotBlank(mpHost) ? mpHost : null); + wxMpHostConfig.setOpenHost(StringUtils.isNotBlank(openHost) ? openHost : null); + config.setHostConfig(wxMpHostConfig); + } + } +} diff --git a/solon-plugins/wx-java-mp-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/mp_multi/configuration/services/WxMpInJedisConfiguration.java b/solon-plugins/wx-java-mp-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/mp_multi/configuration/services/WxMpInJedisConfiguration.java new file mode 100644 index 0000000000..c00898a82d --- /dev/null +++ b/solon-plugins/wx-java-mp-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/mp_multi/configuration/services/WxMpInJedisConfiguration.java @@ -0,0 +1,78 @@ +package com.binarywang.solon.wxjava.mp_multi.configuration.services; + +import com.binarywang.solon.wxjava.mp_multi.properties.WxMpMultiProperties; +import com.binarywang.solon.wxjava.mp_multi.properties.WxMpMultiRedisProperties; +import com.binarywang.solon.wxjava.mp_multi.service.WxMpMultiServices; +import lombok.RequiredArgsConstructor; +import me.chanjar.weixin.common.redis.JedisWxRedisOps; +import me.chanjar.weixin.mp.config.impl.WxMpDefaultConfigImpl; +import me.chanjar.weixin.mp.config.impl.WxMpRedisConfigImpl; +import org.apache.commons.lang3.StringUtils; +import org.noear.solon.annotation.Bean; +import org.noear.solon.annotation.Condition; +import org.noear.solon.annotation.Configuration; +import org.noear.solon.core.AppContext; +import redis.clients.jedis.JedisPool; +import redis.clients.jedis.JedisPoolConfig; + +/** + * 自动装配基于 jedis 策略配置 + * + * @author yl + * created on 2024/1/23 + */ +@Configuration +@Condition( + onProperty = "${"+WxMpMultiProperties.PREFIX + ".configStorage.type} = jedis", + onClass = JedisPool.class +) +@RequiredArgsConstructor +public class WxMpInJedisConfiguration extends AbstractWxMpConfiguration { + private final WxMpMultiProperties wxCpMultiProperties; + private final AppContext applicationContext; + + @Bean + public WxMpMultiServices wxMpMultiServices() { + return this.wxMpMultiServices(wxCpMultiProperties); + } + + @Override + protected WxMpDefaultConfigImpl wxMpConfigStorage(WxMpMultiProperties wxCpMultiProperties) { + return this.configRedis(wxCpMultiProperties); + } + + private WxMpDefaultConfigImpl configRedis(WxMpMultiProperties wxCpMultiProperties) { + WxMpMultiRedisProperties wxCpMultiRedisProperties = wxCpMultiProperties.getConfigStorage().getRedis(); + JedisPool jedisPool; + if (wxCpMultiRedisProperties != null && StringUtils.isNotEmpty(wxCpMultiRedisProperties.getHost())) { + jedisPool = getJedisPool(wxCpMultiProperties); + } else { + jedisPool = applicationContext.getBean(JedisPool.class); + } + return new WxMpRedisConfigImpl(new JedisWxRedisOps(jedisPool), wxCpMultiProperties.getConfigStorage().getKeyPrefix()); + } + + private JedisPool getJedisPool(WxMpMultiProperties wxCpMultiProperties) { + WxMpMultiProperties.ConfigStorage storage = wxCpMultiProperties.getConfigStorage(); + WxMpMultiRedisProperties redis = storage.getRedis(); + + JedisPoolConfig config = new JedisPoolConfig(); + if (redis.getMaxActive() != null) { + config.setMaxTotal(redis.getMaxActive()); + } + if (redis.getMaxIdle() != null) { + config.setMaxIdle(redis.getMaxIdle()); + } + if (redis.getMaxWaitMillis() != null) { + config.setMaxWaitMillis(redis.getMaxWaitMillis()); + } + if (redis.getMinIdle() != null) { + config.setMinIdle(redis.getMinIdle()); + } + config.setTestOnBorrow(true); + config.setTestWhileIdle(true); + + return new JedisPool(config, redis.getHost(), redis.getPort(), + redis.getTimeout(), redis.getPassword(), redis.getDatabase()); + } +} diff --git a/solon-plugins/wx-java-mp-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/mp_multi/configuration/services/WxMpInMemoryConfiguration.java b/solon-plugins/wx-java-mp-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/mp_multi/configuration/services/WxMpInMemoryConfiguration.java new file mode 100644 index 0000000000..74bc13e03e --- /dev/null +++ b/solon-plugins/wx-java-mp-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/mp_multi/configuration/services/WxMpInMemoryConfiguration.java @@ -0,0 +1,40 @@ +package com.binarywang.solon.wxjava.mp_multi.configuration.services; + +import com.binarywang.solon.wxjava.mp_multi.properties.WxMpMultiProperties; +import com.binarywang.solon.wxjava.mp_multi.service.WxMpMultiServices; +import lombok.RequiredArgsConstructor; +import me.chanjar.weixin.mp.config.impl.WxMpDefaultConfigImpl; +import me.chanjar.weixin.mp.config.impl.WxMpMapConfigImpl; +import org.noear.solon.annotation.Bean; +import org.noear.solon.annotation.Condition; +import org.noear.solon.annotation.Configuration; + +/** + * 自动装配基于内存策略配置 + * + * @author yl + * created on 2024/1/23 + */ +@Configuration +@Condition( + onProperty = "${"+WxMpMultiProperties.PREFIX + ".configStorage.type:memory} = memory" +) +@RequiredArgsConstructor +public class WxMpInMemoryConfiguration extends AbstractWxMpConfiguration { + private final WxMpMultiProperties wxCpMultiProperties; + + @Bean + public WxMpMultiServices wxCpMultiServices() { + return this.wxMpMultiServices(wxCpMultiProperties); + } + + @Override + protected WxMpDefaultConfigImpl wxMpConfigStorage(WxMpMultiProperties wxCpMultiProperties) { + return this.configInMemory(); + } + + private WxMpDefaultConfigImpl configInMemory() { + return new WxMpMapConfigImpl(); + // return new WxMpDefaultConfigImpl(); + } +} diff --git a/solon-plugins/wx-java-mp-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/mp_multi/configuration/services/WxMpInRedissonConfiguration.java b/solon-plugins/wx-java-mp-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/mp_multi/configuration/services/WxMpInRedissonConfiguration.java new file mode 100644 index 0000000000..89ffdfd912 --- /dev/null +++ b/solon-plugins/wx-java-mp-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/mp_multi/configuration/services/WxMpInRedissonConfiguration.java @@ -0,0 +1,68 @@ +package com.binarywang.solon.wxjava.mp_multi.configuration.services; + +import com.binarywang.solon.wxjava.mp_multi.properties.WxMpMultiProperties; +import com.binarywang.solon.wxjava.mp_multi.properties.WxMpMultiRedisProperties; +import com.binarywang.solon.wxjava.mp_multi.service.WxMpMultiServices; +import lombok.RequiredArgsConstructor; +import me.chanjar.weixin.mp.config.impl.WxMpDefaultConfigImpl; +import me.chanjar.weixin.mp.config.impl.WxMpRedissonConfigImpl; +import org.apache.commons.lang3.StringUtils; +import org.noear.solon.annotation.Bean; +import org.noear.solon.annotation.Condition; +import org.noear.solon.annotation.Configuration; +import org.noear.solon.core.AppContext; +import org.redisson.Redisson; +import org.redisson.api.RedissonClient; +import org.redisson.config.Config; +import org.redisson.config.TransportMode; + +/** + * 自动装配基于 redisson 策略配置 + * + * @author yl + * created on 2024/1/23 + */ +@Configuration +@Condition( + onProperty = "${"+WxMpMultiProperties.PREFIX + ".configStorage.type} = redisson", + onClass = Redisson.class +) +@RequiredArgsConstructor +public class WxMpInRedissonConfiguration extends AbstractWxMpConfiguration { + private final WxMpMultiProperties wxCpMultiProperties; + private final AppContext applicationContext; + + @Bean + public WxMpMultiServices wxMpMultiServices() { + return this.wxMpMultiServices(wxCpMultiProperties); + } + + @Override + protected WxMpDefaultConfigImpl wxMpConfigStorage(WxMpMultiProperties wxCpMultiProperties) { + return this.configRedisson(wxCpMultiProperties); + } + + private WxMpDefaultConfigImpl configRedisson(WxMpMultiProperties wxCpMultiProperties) { + WxMpMultiRedisProperties redisProperties = wxCpMultiProperties.getConfigStorage().getRedis(); + RedissonClient redissonClient; + if (redisProperties != null && StringUtils.isNotEmpty(redisProperties.getHost())) { + redissonClient = getRedissonClient(wxCpMultiProperties); + } else { + redissonClient = applicationContext.getBean(RedissonClient.class); + } + return new WxMpRedissonConfigImpl(redissonClient, wxCpMultiProperties.getConfigStorage().getKeyPrefix()); + } + + private RedissonClient getRedissonClient(WxMpMultiProperties wxCpMultiProperties) { + WxMpMultiProperties.ConfigStorage storage = wxCpMultiProperties.getConfigStorage(); + WxMpMultiRedisProperties 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/solon-plugins/wx-java-mp-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/mp_multi/integration/WxMpMultiPluginImpl.java b/solon-plugins/wx-java-mp-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/mp_multi/integration/WxMpMultiPluginImpl.java new file mode 100644 index 0000000000..3629a8f78f --- /dev/null +++ b/solon-plugins/wx-java-mp-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/mp_multi/integration/WxMpMultiPluginImpl.java @@ -0,0 +1,23 @@ +package com.binarywang.solon.wxjava.mp_multi.integration; + +import com.binarywang.solon.wxjava.mp_multi.configuration.services.WxMpInJedisConfiguration; +import com.binarywang.solon.wxjava.mp_multi.configuration.services.WxMpInMemoryConfiguration; +import com.binarywang.solon.wxjava.mp_multi.configuration.services.WxMpInRedissonConfiguration; +import com.binarywang.solon.wxjava.mp_multi.properties.WxMpMultiProperties; +import org.noear.solon.core.AppContext; +import org.noear.solon.core.Plugin; + +/** + * @author noear 2024/9/2 created + */ +public class WxMpMultiPluginImpl implements Plugin { + @Override + public void start(AppContext context) throws Throwable { + context.beanMake(WxMpMultiProperties.class); + + context.beanMake(WxMpInJedisConfiguration.class); + context.beanMake(WxMpInMemoryConfiguration.class); + context.beanMake(WxMpInRedissonConfiguration.class); + + } +} diff --git a/solon-plugins/wx-java-mp-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/mp_multi/properties/WxMpMultiProperties.java b/solon-plugins/wx-java-mp-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/mp_multi/properties/WxMpMultiProperties.java new file mode 100644 index 0000000000..3d47f71381 --- /dev/null +++ b/solon-plugins/wx-java-mp-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/mp_multi/properties/WxMpMultiProperties.java @@ -0,0 +1,158 @@ +package com.binarywang.solon.wxjava.mp_multi.properties; + +import lombok.Data; +import lombok.NoArgsConstructor; +import org.noear.solon.annotation.Configuration; +import org.noear.solon.annotation.Inject; + +import java.io.Serializable; +import java.util.HashMap; +import java.util.Map; + +/** + * @author yl + * created on 2024/1/23 + */ +@Data +@NoArgsConstructor +@Configuration +@Inject("${"+WxMpMultiProperties.PREFIX+"}") +public class WxMpMultiProperties implements Serializable { + private static final long serialVersionUID = -5358245184407791011L; + public static final String PREFIX = "wx.mp"; + + private Map apps = new HashMap<>(); + + /** + * 自定义host配置 + */ + private HostConfig hosts; + + /** + * 存储策略 + */ + private final ConfigStorage configStorage = new ConfigStorage(); + + @Data + @NoArgsConstructor + public static class HostConfig implements Serializable { + private static final long serialVersionUID = -4172767630740346001L; + + /** + * 对应于:https://api.weixin.qq.com + */ + private String apiHost; + + /** + * 对应于:https://open.weixin.qq.com + */ + private String openHost; + + /** + * 对应于:https://mp.weixin.qq.com + */ + private String mpHost; + } + + @Data + @NoArgsConstructor + public static class ConfigStorage implements Serializable { + private static final long serialVersionUID = 4815731027000065434L; + + /** + * 存储类型. + */ + private StorageType type = StorageType.MEMORY; + + /** + * 指定key前缀. + */ + private String keyPrefix = "wx:mp:multi"; + + /** + * redis连接配置. + */ + private final WxMpMultiRedisProperties redis = new WxMpMultiRedisProperties(); + + /** + * http客户端类型. + */ + private HttpClientType httpClientType = HttpClientType.HTTP_COMPONENTS; + + /** + * http代理主机. + */ + private String httpProxyHost; + + /** + * http代理端口. + */ + private Integer httpProxyPort; + + /** + * http代理用户名. + */ + private String httpProxyUsername; + + /** + * http代理密码. + */ + private String httpProxyPassword; + + /** + * http 请求最大重试次数 + *
+     *   {@link me.chanjar.weixin.mp.api.WxMpService#setMaxRetryTimes(int)}
+     *   {@link me.chanjar.weixin.mp.api.impl.BaseWxMpServiceImpl#setMaxRetryTimes(int)}
+     * 
+ */ + private int maxRetryTimes = 5; + + /** + * http 请求重试间隔 + *
+     *   {@link me.chanjar.weixin.mp.api.WxMpService#setRetrySleepMillis(int)}
+     *   {@link me.chanjar.weixin.mp.api.impl.BaseWxMpServiceImpl#setRetrySleepMillis(int)}
+     * 
+ */ + private int retrySleepMillis = 1000; + } + + public enum StorageType { + /** + * 内存 + */ + MEMORY, + /** + * jedis + */ + JEDIS, + /** + * redisson + */ + REDISSON, + /** + * redisTemplate + */ + REDIS_TEMPLATE + } + + public enum HttpClientType { + /** + * HttpClient + */ + HTTP_CLIENT, + /** + * OkHttp + */ + OK_HTTP, + /** + * JoddHttp + */ + JODD_HTTP, + /** + * HttpComponents + */ + HTTP_COMPONENTS + } +} diff --git a/solon-plugins/wx-java-mp-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/mp_multi/properties/WxMpMultiRedisProperties.java b/solon-plugins/wx-java-mp-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/mp_multi/properties/WxMpMultiRedisProperties.java new file mode 100644 index 0000000000..12646d4eaf --- /dev/null +++ b/solon-plugins/wx-java-mp-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/mp_multi/properties/WxMpMultiRedisProperties.java @@ -0,0 +1,56 @@ +package com.binarywang.solon.wxjava.mp_multi.properties; + +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * @author yl + * created on 2024/1/23 + */ +@Data +@NoArgsConstructor +public class WxMpMultiRedisProperties implements Serializable { + private static final long serialVersionUID = -5924815351660074401L; + + /** + * 主机地址. + */ + private String host = "127.0.0.1"; + + /** + * 端口号. + */ + private int port = 6379; + + /** + * 密码. + */ + private String password; + + /** + * 超时. + */ + private int timeout = 2000; + + /** + * 数据库. + */ + private int database = 0; + + /** + * sentinel ips + */ + private String sentinelIps; + + /** + * sentinel name + */ + private String sentinelName; + + private Integer maxActive; + private Integer maxIdle; + private Integer maxWaitMillis; + private Integer minIdle; +} diff --git a/solon-plugins/wx-java-mp-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/mp_multi/properties/WxMpSingleProperties.java b/solon-plugins/wx-java-mp-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/mp_multi/properties/WxMpSingleProperties.java new file mode 100644 index 0000000000..22938cb67c --- /dev/null +++ b/solon-plugins/wx-java-mp-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/mp_multi/properties/WxMpSingleProperties.java @@ -0,0 +1,40 @@ +package com.binarywang.solon.wxjava.mp_multi.properties; + +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * @author yl + * created on 2024/1/23 + */ +@Data +@NoArgsConstructor +public class WxMpSingleProperties implements Serializable { + private static final long serialVersionUID = 1980986361098922525L; + /** + * 设置微信公众号的 appid. + */ + private String appId; + + /** + * 设置微信公众号的 app secret. + */ + private String appSecret; + + /** + * 设置微信公众号的 token. + */ + private String token; + + /** + * 设置微信公众号的 EncodingAESKey. + */ + private String aesKey; + + /** + * 是否使用稳定版 Access Token + */ + private boolean useStableAccessToken = false; +} diff --git a/solon-plugins/wx-java-mp-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/mp_multi/service/WxMpMultiServices.java b/solon-plugins/wx-java-mp-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/mp_multi/service/WxMpMultiServices.java new file mode 100644 index 0000000000..a59b5962ad --- /dev/null +++ b/solon-plugins/wx-java-mp-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/mp_multi/service/WxMpMultiServices.java @@ -0,0 +1,27 @@ +package com.binarywang.solon.wxjava.mp_multi.service; + + +import me.chanjar.weixin.mp.api.WxMpService; + +/** + * 企业微信 {@link WxMpService} 所有实例存放类. + * + * @author yl + * created on 2024/1/23 + */ +public interface WxMpMultiServices { + /** + * 通过租户 Id 获取 WxMpService + * + * @param tenantId 租户 Id + * @return WxMpService + */ + WxMpService getWxMpService(String tenantId); + + /** + * 根据租户 Id,从列表中移除一个 WxMpService 实例 + * + * @param tenantId 租户 Id + */ + void removeWxMpService(String tenantId); +} diff --git a/solon-plugins/wx-java-mp-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/mp_multi/service/WxMpMultiServicesImpl.java b/solon-plugins/wx-java-mp-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/mp_multi/service/WxMpMultiServicesImpl.java new file mode 100644 index 0000000000..d87cd4e8df --- /dev/null +++ b/solon-plugins/wx-java-mp-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/mp_multi/service/WxMpMultiServicesImpl.java @@ -0,0 +1,36 @@ +package com.binarywang.solon.wxjava.mp_multi.service; + +import me.chanjar.weixin.mp.api.WxMpService; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * 企业微信 {@link WxMpMultiServices} 默认实现 + * + * @author yl + * created on 2024/1/23 + */ +public class WxMpMultiServicesImpl implements WxMpMultiServices { + private final Map services = new ConcurrentHashMap<>(); + + @Override + public WxMpService getWxMpService(String tenantId) { + return this.services.get(tenantId); + } + + /** + * 根据租户 Id,添加一个 WxMpService 到列表 + * + * @param tenantId 租户 Id + * @param wxMpService WxMpService 实例 + */ + public void addWxMpService(String tenantId, WxMpService wxMpService) { + this.services.put(tenantId, wxMpService); + } + + @Override + public void removeWxMpService(String tenantId) { + this.services.remove(tenantId); + } +} diff --git a/solon-plugins/wx-java-mp-multi-solon-plugin/src/main/resources/META-INF/solon/wx-java-mp-multi-solon-plugin.properties b/solon-plugins/wx-java-mp-multi-solon-plugin/src/main/resources/META-INF/solon/wx-java-mp-multi-solon-plugin.properties new file mode 100644 index 0000000000..11c68ccc81 --- /dev/null +++ b/solon-plugins/wx-java-mp-multi-solon-plugin/src/main/resources/META-INF/solon/wx-java-mp-multi-solon-plugin.properties @@ -0,0 +1,2 @@ +solon.plugin=com.binarywang.solon.wxjava.mp_multi.integration.WxMpMultiPluginImpl +solon.plugin.priority=10 diff --git a/solon-plugins/wx-java-mp-multi-solon-plugin/src/test/java/features/test/LoadTest.java b/solon-plugins/wx-java-mp-multi-solon-plugin/src/test/java/features/test/LoadTest.java new file mode 100644 index 0000000000..d049f5a51a --- /dev/null +++ b/solon-plugins/wx-java-mp-multi-solon-plugin/src/test/java/features/test/LoadTest.java @@ -0,0 +1,15 @@ +package features.test; + +import org.junit.jupiter.api.Test; +import org.noear.solon.test.SolonTest; + +/** + * @author noear 2024/9/4 created + */ +@SolonTest +public class LoadTest { + @Test + public void load(){ + + } +} diff --git a/solon-plugins/wx-java-mp-multi-solon-plugin/src/test/resources/app.properties b/solon-plugins/wx-java-mp-multi-solon-plugin/src/test/resources/app.properties new file mode 100644 index 0000000000..3f3b21657c --- /dev/null +++ b/solon-plugins/wx-java-mp-multi-solon-plugin/src/test/resources/app.properties @@ -0,0 +1,23 @@ +# ????? +## ?? 1 ??(??) +wx.mp.apps.tenantId1.app-id=appId +wx.mp.apps.tenantId1.app-secret=@secret +## ?? +wx.mp.apps.tenantId1.token=@token +wx.mp.apps.tenantId1.aes-key=@aesKey +wx.mp.apps.tenantId1.use-stable-access-token=@useStableAccessToken +## ?? 2 ??(??) +wx.mp.apps.tenantId2.app-id=@appId +wx.mp.apps.tenantId2.app-secret =@secret +## ?? +wx.mp.apps.tenantId2.token=@token +wx.mp.apps.tenantId2.aes-key=@aesKey +wx.mp.apps.tenantId2.use-stable-access-token=@useStableAccessToken + +# ConfigStorage ?????? +## ????: memory(??), jedis, redisson, redis_template +wx.mp.config-storage.type=memory +## ??redis????: wx:mp:multi(??) +wx.mp.config-storage.key-prefix=wx:mp:multi +wx.mp.config-storage.redis.host=127.0.0.1 +wx.mp.config-storage.redis.port=6379 diff --git a/solon-plugins/wx-java-mp-solon-plugin/README.md b/solon-plugins/wx-java-mp-solon-plugin/README.md new file mode 100644 index 0000000000..58dcbfddbe --- /dev/null +++ b/solon-plugins/wx-java-mp-solon-plugin/README.md @@ -0,0 +1,46 @@ +# wx-java-mp-solon-plugin + +## 快速开始 + +1. 引入依赖 + ```xml + + com.github.binarywang + wx-java-mp-solon-plugin + ${version} + + ``` +2. 添加配置(app.properties) + ```properties + # 公众号配置(必填) + wx.mp.app-id=appId + wx.mp.secret=@secret + wx.mp.token=@token + wx.mp.aes-key=@aesKey + wx.mp.use-stable-access-token=@useStableAccessToken + # 存储配置redis(可选) + wx.mp.config-storage.type= edis # 配置类型: Memory(默认), Jedis, RedisTemplate + wx.mp.config-storage.key-prefix=wx # 相关redis前缀配置: wx(默认) + wx.mp.config-storage.redis.host=127.0.0.1 + wx.mp.config-storage.redis.port=6379 + #单机和sentinel同时存在时,优先使用sentinel配置 + #wx.mp.config-storage.redis.sentinel-ips=127.0.0.1:16379,127.0.0.1:26379 + #wx.mp.config-storage.redis.sentinel-name=mymaster + # http客户端配置 + wx.mp.config-storage.http-client-type=httpclient # http客户端类型: HttpClient(默认), OkHttp, JoddHttp + wx.mp.config-storage.http-proxy-host= + wx.mp.config-storage.http-proxy-port= + wx.mp.config-storage.http-proxy-username= + wx.mp.config-storage.http-proxy-password= + # 公众号地址host配置 + #wx.mp.hosts.api-host=http://proxy.com/ + #wx.mp.hosts.open-host=http://proxy.com/ + #wx.mp.hosts.mp-host=http://proxy.com/ + ``` +3. 自动注入的类型 + +- `WxMpService` +- `WxMpConfigStorage` + +4、参考demo: +https://github.com/binarywang/wx-java-mp-demo diff --git a/solon-plugins/wx-java-mp-solon-plugin/pom.xml b/solon-plugins/wx-java-mp-solon-plugin/pom.xml new file mode 100644 index 0000000000..d72a5f7fc4 --- /dev/null +++ b/solon-plugins/wx-java-mp-solon-plugin/pom.xml @@ -0,0 +1,44 @@ + + + + wx-java-solon-plugins + com.github.binarywang + 4.8.0 + + 4.0.0 + + wx-java-mp-solon-plugin + WxJava - Solon Plugin for MP + 微信公众号开发的 Solon Plugin + + + + com.github.binarywang + weixin-java-mp + ${project.version} + + + redis.clients + jedis + provided + + + org.redisson + redisson + provided + + + org.jodd + jodd-http + provided + + + com.squareup.okhttp3 + okhttp + provided + + + + diff --git a/solon-plugins/wx-java-mp-solon-plugin/src/main/java/com/binarywang/solon/wxjava/mp/config/WxMpServiceAutoConfiguration.java b/solon-plugins/wx-java-mp-solon-plugin/src/main/java/com/binarywang/solon/wxjava/mp/config/WxMpServiceAutoConfiguration.java new file mode 100644 index 0000000000..334ccf7abe --- /dev/null +++ b/solon-plugins/wx-java-mp-solon-plugin/src/main/java/com/binarywang/solon/wxjava/mp/config/WxMpServiceAutoConfiguration.java @@ -0,0 +1,71 @@ +package com.binarywang.solon.wxjava.mp.config; + +import com.binarywang.solon.wxjava.mp.enums.HttpClientType; +import com.binarywang.solon.wxjava.mp.properties.WxMpProperties; +import me.chanjar.weixin.mp.api.WxMpService; +import me.chanjar.weixin.mp.api.impl.WxMpServiceHttpClientImpl; +import me.chanjar.weixin.mp.api.impl.WxMpServiceHttpComponentsImpl; +import me.chanjar.weixin.mp.api.impl.WxMpServiceImpl; +import me.chanjar.weixin.mp.api.impl.WxMpServiceJoddHttpImpl; +import me.chanjar.weixin.mp.api.impl.WxMpServiceOkHttpImpl; +import me.chanjar.weixin.mp.config.WxMpConfigStorage; +import org.noear.solon.annotation.Bean; +import org.noear.solon.annotation.Condition; +import org.noear.solon.annotation.Configuration; + +/** + * 微信公众号相关服务自动注册. + * + * @author someone + */ +@Configuration +public class WxMpServiceAutoConfiguration { + + @Bean + @Condition(onMissingBean = WxMpService.class) + public WxMpService wxMpService(WxMpConfigStorage configStorage, WxMpProperties wxMpProperties) { + HttpClientType httpClientType = wxMpProperties.getConfigStorage().getHttpClientType(); + WxMpService wxMpService; + switch (httpClientType) { + case OkHttp: + wxMpService = newWxMpServiceOkHttpImpl(); + break; + case JoddHttp: + wxMpService = newWxMpServiceJoddHttpImpl(); + break; + case HttpClient: + wxMpService = newWxMpServiceHttpClientImpl(); + break; + case HttpComponents: + wxMpService = newWxMpServiceHttpComponentsImpl(); + break; + default: + wxMpService = newWxMpServiceImpl(); + break; + } + + wxMpService.setWxMpConfigStorage(configStorage); + return wxMpService; + } + + private WxMpService newWxMpServiceImpl() { + return new WxMpServiceImpl(); + } + + private WxMpService newWxMpServiceHttpClientImpl() { + return new WxMpServiceHttpClientImpl(); + } + + private WxMpService newWxMpServiceOkHttpImpl() { + return new WxMpServiceOkHttpImpl(); + } + + private WxMpService newWxMpServiceJoddHttpImpl() { + return new WxMpServiceJoddHttpImpl(); + } + + private WxMpService newWxMpServiceHttpComponentsImpl() { + return new WxMpServiceHttpComponentsImpl(); + } + +} diff --git a/solon-plugins/wx-java-mp-solon-plugin/src/main/java/com/binarywang/solon/wxjava/mp/config/storage/AbstractWxMpConfigStorageConfiguration.java b/solon-plugins/wx-java-mp-solon-plugin/src/main/java/com/binarywang/solon/wxjava/mp/config/storage/AbstractWxMpConfigStorageConfiguration.java new file mode 100644 index 0000000000..663bb13340 --- /dev/null +++ b/solon-plugins/wx-java-mp-solon-plugin/src/main/java/com/binarywang/solon/wxjava/mp/config/storage/AbstractWxMpConfigStorageConfiguration.java @@ -0,0 +1,27 @@ +package com.binarywang.solon.wxjava.mp.config.storage; + +import com.binarywang.solon.wxjava.mp.properties.WxMpProperties; +import me.chanjar.weixin.mp.config.impl.WxMpDefaultConfigImpl; + +/** + * @author zhangyl + */ +public abstract class AbstractWxMpConfigStorageConfiguration { + + protected WxMpDefaultConfigImpl config(WxMpDefaultConfigImpl config, WxMpProperties properties) { + config.setAppId(properties.getAppId()); + config.setSecret(properties.getSecret()); + config.setToken(properties.getToken()); + config.setAesKey(properties.getAesKey()); + config.setUseStableAccessToken(properties.isUseStableAccessToken()); + + WxMpProperties.ConfigStorage configStorageProperties = properties.getConfigStorage(); + config.setHttpProxyHost(configStorageProperties.getHttpProxyHost()); + config.setHttpProxyUsername(configStorageProperties.getHttpProxyUsername()); + config.setHttpProxyPassword(configStorageProperties.getHttpProxyPassword()); + if (configStorageProperties.getHttpProxyPort() != null) { + config.setHttpProxyPort(configStorageProperties.getHttpProxyPort()); + } + return config; + } +} diff --git a/solon-plugins/wx-java-mp-solon-plugin/src/main/java/com/binarywang/solon/wxjava/mp/config/storage/WxMpInJedisConfigStorageConfiguration.java b/solon-plugins/wx-java-mp-solon-plugin/src/main/java/com/binarywang/solon/wxjava/mp/config/storage/WxMpInJedisConfigStorageConfiguration.java new file mode 100644 index 0000000000..a949ccfaca --- /dev/null +++ b/solon-plugins/wx-java-mp-solon-plugin/src/main/java/com/binarywang/solon/wxjava/mp/config/storage/WxMpInJedisConfigStorageConfiguration.java @@ -0,0 +1,76 @@ +package com.binarywang.solon.wxjava.mp.config.storage; + +import com.binarywang.solon.wxjava.mp.properties.RedisProperties; +import com.binarywang.solon.wxjava.mp.properties.WxMpProperties; +import lombok.RequiredArgsConstructor; +import me.chanjar.weixin.common.redis.JedisWxRedisOps; +import me.chanjar.weixin.common.redis.WxRedisOps; +import me.chanjar.weixin.mp.config.WxMpConfigStorage; +import me.chanjar.weixin.mp.config.impl.WxMpRedisConfigImpl; +import org.apache.commons.lang3.StringUtils; +import org.noear.solon.annotation.Bean; +import org.noear.solon.annotation.Condition; +import org.noear.solon.annotation.Configuration; +import org.noear.solon.core.AppContext; +import redis.clients.jedis.Jedis; +import redis.clients.jedis.JedisPool; +import redis.clients.jedis.JedisPoolConfig; + +/** + * @author zhangyl + */ +@Configuration +@Condition( + onProperty = "${" + WxMpProperties.PREFIX + ".config-storage.type} = jedis", + onClass = Jedis.class +) +@RequiredArgsConstructor +public class WxMpInJedisConfigStorageConfiguration extends AbstractWxMpConfigStorageConfiguration { + private final WxMpProperties properties; + private final AppContext applicationContext; + + @Bean + @Condition(onMissingBean = WxMpConfigStorage.class) + public WxMpConfigStorage wxMpConfigStorage() { + WxMpRedisConfigImpl config = getWxMpRedisConfigImpl(); + return this.config(config, properties); + } + + private WxMpRedisConfigImpl getWxMpRedisConfigImpl() { + RedisProperties redisProperties = properties.getConfigStorage().getRedis(); + JedisPool jedisPool; + if (redisProperties != null && StringUtils.isNotEmpty(redisProperties.getHost())) { + jedisPool = applicationContext.getBean("wxMpJedisPool"); + } else { + jedisPool = applicationContext.getBean(JedisPool.class); + } + WxRedisOps redisOps = new JedisWxRedisOps(jedisPool); + return new WxMpRedisConfigImpl(redisOps, properties.getConfigStorage().getKeyPrefix()); + } + + @Bean + @Condition(onProperty = "${" + WxMpProperties.PREFIX + ".config-storage.redis.host}") + public JedisPool wxMpJedisPool() { + WxMpProperties.ConfigStorage storage = properties.getConfigStorage(); + RedisProperties redis = storage.getRedis(); + + JedisPoolConfig config = new JedisPoolConfig(); + if (redis.getMaxActive() != null) { + config.setMaxTotal(redis.getMaxActive()); + } + if (redis.getMaxIdle() != null) { + config.setMaxIdle(redis.getMaxIdle()); + } + if (redis.getMaxWaitMillis() != null) { + config.setMaxWaitMillis(redis.getMaxWaitMillis()); + } + if (redis.getMinIdle() != null) { + config.setMinIdle(redis.getMinIdle()); + } + config.setTestOnBorrow(true); + config.setTestWhileIdle(true); + + return new JedisPool(config, redis.getHost(), redis.getPort(), redis.getTimeout(), redis.getPassword(), + redis.getDatabase()); + } +} diff --git a/solon-plugins/wx-java-mp-solon-plugin/src/main/java/com/binarywang/solon/wxjava/mp/config/storage/WxMpInMemoryConfigStorageConfiguration.java b/solon-plugins/wx-java-mp-solon-plugin/src/main/java/com/binarywang/solon/wxjava/mp/config/storage/WxMpInMemoryConfigStorageConfiguration.java new file mode 100644 index 0000000000..88994fcf42 --- /dev/null +++ b/solon-plugins/wx-java-mp-solon-plugin/src/main/java/com/binarywang/solon/wxjava/mp/config/storage/WxMpInMemoryConfigStorageConfiguration.java @@ -0,0 +1,29 @@ +package com.binarywang.solon.wxjava.mp.config.storage; + +import com.binarywang.solon.wxjava.mp.properties.WxMpProperties; +import lombok.RequiredArgsConstructor; +import me.chanjar.weixin.mp.config.WxMpConfigStorage; +import me.chanjar.weixin.mp.config.impl.WxMpDefaultConfigImpl; +import org.noear.solon.annotation.Bean; +import org.noear.solon.annotation.Condition; +import org.noear.solon.annotation.Configuration; + +/** + * @author zhangyl + */ +@Configuration +@Condition( + onProperty = "${" + WxMpProperties.PREFIX + ".config-storage.type:memory} = memory" +) +@RequiredArgsConstructor +public class WxMpInMemoryConfigStorageConfiguration extends AbstractWxMpConfigStorageConfiguration { + private final WxMpProperties properties; + + @Bean + @Condition(onMissingBean = WxMpConfigStorage.class) + public WxMpConfigStorage wxMpConfigStorage() { + WxMpDefaultConfigImpl config = new WxMpDefaultConfigImpl(); + config(config, properties); + return config; + } +} diff --git a/solon-plugins/wx-java-mp-solon-plugin/src/main/java/com/binarywang/solon/wxjava/mp/config/storage/WxMpInRedissonConfigStorageConfiguration.java b/solon-plugins/wx-java-mp-solon-plugin/src/main/java/com/binarywang/solon/wxjava/mp/config/storage/WxMpInRedissonConfigStorageConfiguration.java new file mode 100644 index 0000000000..c1f5ebf0f3 --- /dev/null +++ b/solon-plugins/wx-java-mp-solon-plugin/src/main/java/com/binarywang/solon/wxjava/mp/config/storage/WxMpInRedissonConfigStorageConfiguration.java @@ -0,0 +1,65 @@ +package com.binarywang.solon.wxjava.mp.config.storage; + +import com.binarywang.solon.wxjava.mp.properties.RedisProperties; +import com.binarywang.solon.wxjava.mp.properties.WxMpProperties; +import lombok.RequiredArgsConstructor; +import me.chanjar.weixin.mp.config.WxMpConfigStorage; +import me.chanjar.weixin.mp.config.impl.WxMpRedissonConfigImpl; +import org.apache.commons.lang3.StringUtils; +import org.noear.solon.annotation.Bean; +import org.noear.solon.annotation.Condition; +import org.noear.solon.annotation.Configuration; +import org.noear.solon.core.AppContext; +import org.redisson.Redisson; +import org.redisson.api.RedissonClient; +import org.redisson.config.Config; +import org.redisson.config.TransportMode; + +/** + * @author zhangyl + */ +@Configuration +@Condition( + onProperty = "${" + WxMpProperties.PREFIX + ".config-storage.type} = redisson", + onClass = Redisson.class +) +@RequiredArgsConstructor +public class WxMpInRedissonConfigStorageConfiguration extends AbstractWxMpConfigStorageConfiguration { + private final WxMpProperties properties; + private final AppContext applicationContext; + + @Bean + @Condition(onMissingBean = WxMpConfigStorage.class) + public WxMpConfigStorage wxMaConfig() { + WxMpRedissonConfigImpl config = getWxMpInRedissonConfigStorage(); + return this.config(config, properties); + } + + private WxMpRedissonConfigImpl getWxMpInRedissonConfigStorage() { + RedisProperties redisProperties = properties.getConfigStorage().getRedis(); + RedissonClient redissonClient; + if (redisProperties != null && StringUtils.isNotEmpty(redisProperties.getHost())) { + redissonClient = applicationContext.getBean("wxMpRedissonClient"); + } else { + redissonClient = applicationContext.getBean(RedissonClient.class); + } + return new WxMpRedissonConfigImpl(redissonClient, properties.getConfigStorage().getKeyPrefix()); + } + + @Bean + @Condition(onProperty = "${" + WxMpProperties.PREFIX + ".config-storage.redis.host}") + public RedissonClient wxMpRedissonClient() { + WxMpProperties.ConfigStorage storage = properties.getConfigStorage(); + RedisProperties redis = storage.getRedis(); + + Config config = new Config(); + config.useSingleServer() + .setAddress("redis://" + redis.getHost() + ":" + redis.getPort()) + .setDatabase(redis.getDatabase()); + if (StringUtils.isNotBlank(redis.getPassword())) { + config.useSingleServer().setPassword(redis.getPassword()); + } + config.setTransportMode(TransportMode.NIO); + return Redisson.create(config); + } +} diff --git a/solon-plugins/wx-java-mp-solon-plugin/src/main/java/com/binarywang/solon/wxjava/mp/enums/HttpClientType.java b/solon-plugins/wx-java-mp-solon-plugin/src/main/java/com/binarywang/solon/wxjava/mp/enums/HttpClientType.java new file mode 100644 index 0000000000..2858d77e43 --- /dev/null +++ b/solon-plugins/wx-java-mp-solon-plugin/src/main/java/com/binarywang/solon/wxjava/mp/enums/HttpClientType.java @@ -0,0 +1,26 @@ +package com.binarywang.solon.wxjava.mp.enums; + +/** + * httpclient类型. + * + * @author Binary Wang + * created on 2020-08-30 + */ +public enum HttpClientType { + /** + * HttpClient. + */ + HttpClient, + /** + * OkHttp. + */ + OkHttp, + /** + * JoddHttp. + */ + JoddHttp, + /** + * HttpComponents. + */ + HttpComponents, +} diff --git a/solon-plugins/wx-java-mp-solon-plugin/src/main/java/com/binarywang/solon/wxjava/mp/enums/StorageType.java b/solon-plugins/wx-java-mp-solon-plugin/src/main/java/com/binarywang/solon/wxjava/mp/enums/StorageType.java new file mode 100644 index 0000000000..34433a8230 --- /dev/null +++ b/solon-plugins/wx-java-mp-solon-plugin/src/main/java/com/binarywang/solon/wxjava/mp/enums/StorageType.java @@ -0,0 +1,26 @@ +package com.binarywang.solon.wxjava.mp.enums; + +/** + * storage类型. + * + * @author Binary Wang + * created on 2020-08-30 + */ +public enum StorageType { + /** + * 内存. + */ + Memory, + /** + * redis(JedisClient). + */ + Jedis, + /** + * redis(Redisson). + */ + Redisson, + /** + * redis(RedisTemplate). + */ + RedisTemplate +} diff --git a/solon-plugins/wx-java-mp-solon-plugin/src/main/java/com/binarywang/solon/wxjava/mp/integration/WxMpPluginImpl.java b/solon-plugins/wx-java-mp-solon-plugin/src/main/java/com/binarywang/solon/wxjava/mp/integration/WxMpPluginImpl.java new file mode 100644 index 0000000000..285d871f25 --- /dev/null +++ b/solon-plugins/wx-java-mp-solon-plugin/src/main/java/com/binarywang/solon/wxjava/mp/integration/WxMpPluginImpl.java @@ -0,0 +1,29 @@ +package com.binarywang.solon.wxjava.mp.integration; + +import com.binarywang.solon.wxjava.mp.config.WxMpServiceAutoConfiguration; +import com.binarywang.solon.wxjava.mp.config.storage.WxMpInJedisConfigStorageConfiguration; +import com.binarywang.solon.wxjava.mp.config.storage.WxMpInMemoryConfigStorageConfiguration; +import com.binarywang.solon.wxjava.mp.config.storage.WxMpInRedissonConfigStorageConfiguration; +import com.binarywang.solon.wxjava.mp.properties.WxMpProperties; +import org.noear.solon.core.AppContext; +import org.noear.solon.core.Plugin; +import org.noear.solon.core.util.ClassUtil; + +/** + * @author noear 2024/9/2 created + */ +public class WxMpPluginImpl implements Plugin { + @Override + public void start(AppContext context) throws Throwable { + context.beanMake(WxMpProperties.class); + context.beanMake(WxMpServiceAutoConfiguration.class); + + context.beanMake(WxMpInMemoryConfigStorageConfiguration.class); + if (ClassUtil.loadClass("redis.clients.jedis.Jedis") != null) { + context.beanMake(WxMpInJedisConfigStorageConfiguration.class); + } + if (ClassUtil.loadClass("org.redisson.api.RedissonClient") != null) { + context.beanMake(WxMpInRedissonConfigStorageConfiguration.class); + } + } +} diff --git a/solon-plugins/wx-java-mp-solon-plugin/src/main/java/com/binarywang/solon/wxjava/mp/properties/HostConfig.java b/solon-plugins/wx-java-mp-solon-plugin/src/main/java/com/binarywang/solon/wxjava/mp/properties/HostConfig.java new file mode 100644 index 0000000000..8ccedf9294 --- /dev/null +++ b/solon-plugins/wx-java-mp-solon-plugin/src/main/java/com/binarywang/solon/wxjava/mp/properties/HostConfig.java @@ -0,0 +1,27 @@ +package com.binarywang.solon.wxjava.mp.properties; + +import lombok.Data; + +import java.io.Serializable; + +@Data +public class HostConfig implements Serializable { + + private static final long serialVersionUID = -4172767630740346001L; + + /** + * 对应于:https://api.weixin.qq.com + */ + private String apiHost; + + /** + * 对应于:https://open.weixin.qq.com + */ + private String openHost; + + /** + * 对应于:https://mp.weixin.qq.com + */ + private String mpHost; + +} diff --git a/solon-plugins/wx-java-mp-solon-plugin/src/main/java/com/binarywang/solon/wxjava/mp/properties/RedisProperties.java b/solon-plugins/wx-java-mp-solon-plugin/src/main/java/com/binarywang/solon/wxjava/mp/properties/RedisProperties.java new file mode 100644 index 0000000000..0376f947a7 --- /dev/null +++ b/solon-plugins/wx-java-mp-solon-plugin/src/main/java/com/binarywang/solon/wxjava/mp/properties/RedisProperties.java @@ -0,0 +1,56 @@ +package com.binarywang.solon.wxjava.mp.properties; + +import lombok.Data; + +import java.io.Serializable; + +/** + * redis 配置属性. + * + * @author Binary Wang + * created on 2020-08-30 + */ +@Data +public class RedisProperties implements Serializable { + private static final long serialVersionUID = -5924815351660074401L; + + /** + * 主机地址. + */ + private String host = "127.0.0.1"; + + /** + * 端口号. + */ + private int port = 6379; + + /** + * 密码. + */ + private String password; + + /** + * 超时. + */ + private int timeout = 2000; + + /** + * 数据库. + */ + private int database = 0; + + /** + * sentinel ips + */ + private String sentinelIps; + + /** + * sentinel name + */ + private String sentinelName; + + private Integer maxActive; + private Integer maxIdle; + private Integer maxWaitMillis; + private Integer minIdle; +} diff --git a/solon-plugins/wx-java-mp-solon-plugin/src/main/java/com/binarywang/solon/wxjava/mp/properties/WxMpProperties.java b/solon-plugins/wx-java-mp-solon-plugin/src/main/java/com/binarywang/solon/wxjava/mp/properties/WxMpProperties.java new file mode 100644 index 0000000000..0dcc6b41d3 --- /dev/null +++ b/solon-plugins/wx-java-mp-solon-plugin/src/main/java/com/binarywang/solon/wxjava/mp/properties/WxMpProperties.java @@ -0,0 +1,106 @@ +package com.binarywang.solon.wxjava.mp.properties; + +import com.binarywang.solon.wxjava.mp.enums.HttpClientType; +import com.binarywang.solon.wxjava.mp.enums.StorageType; +import lombok.Data; +import org.noear.solon.annotation.Configuration; +import org.noear.solon.annotation.Inject; + +import java.io.Serializable; + +import static com.binarywang.solon.wxjava.mp.enums.StorageType.Memory; +import static com.binarywang.solon.wxjava.mp.properties.WxMpProperties.PREFIX; + +/** + * 微信接入相关配置属性. + * + * @author someone + */ +@Data +@Configuration +@Inject("${" + PREFIX + "}") +public class WxMpProperties { + public static final String PREFIX = "wx.mp"; + + /** + * 设置微信公众号的appid. + */ + private String appId; + + /** + * 设置微信公众号的app secret. + */ + private String secret; + + /** + * 设置微信公众号的token. + */ + private String token; + + /** + * 设置微信公众号的EncodingAESKey. + */ + private String aesKey; + + /** + * 是否使用稳定版 Access Token + */ + private boolean useStableAccessToken = false; + + /** + * 自定义host配置 + */ + private HostConfig hosts; + + /** + * 存储策略 + */ + private final ConfigStorage configStorage = new ConfigStorage(); + + @Data + public static class ConfigStorage implements Serializable { + private static final long serialVersionUID = 4815731027000065434L; + + /** + * 存储类型. + */ + private StorageType type = Memory; + + /** + * 指定key前缀. + */ + private String keyPrefix = "wx"; + + /** + * redis连接配置. + */ + private final RedisProperties redis = new RedisProperties(); + + /** + * http客户端类型. + */ + private HttpClientType httpClientType = HttpClientType.HttpComponents; + + /** + * http代理主机. + */ + private String httpProxyHost; + + /** + * http代理端口. + */ + private Integer httpProxyPort; + + /** + * http代理用户名. + */ + private String httpProxyUsername; + + /** + * http代理密码. + */ + private String httpProxyPassword; + + } + +} diff --git a/solon-plugins/wx-java-mp-solon-plugin/src/main/resources/META-INF/solon/wx-java-mp-solon-plugin.properties b/solon-plugins/wx-java-mp-solon-plugin/src/main/resources/META-INF/solon/wx-java-mp-solon-plugin.properties new file mode 100644 index 0000000000..c80357184c --- /dev/null +++ b/solon-plugins/wx-java-mp-solon-plugin/src/main/resources/META-INF/solon/wx-java-mp-solon-plugin.properties @@ -0,0 +1,2 @@ +solon.plugin=com.binarywang.solon.wxjava.mp.integration.WxMpPluginImpl +solon.plugin.priority=10 diff --git a/solon-plugins/wx-java-mp-solon-plugin/src/test/java/features/test/LoadTest.java b/solon-plugins/wx-java-mp-solon-plugin/src/test/java/features/test/LoadTest.java new file mode 100644 index 0000000000..d049f5a51a --- /dev/null +++ b/solon-plugins/wx-java-mp-solon-plugin/src/test/java/features/test/LoadTest.java @@ -0,0 +1,15 @@ +package features.test; + +import org.junit.jupiter.api.Test; +import org.noear.solon.test.SolonTest; + +/** + * @author noear 2024/9/4 created + */ +@SolonTest +public class LoadTest { + @Test + public void load(){ + + } +} diff --git a/solon-plugins/wx-java-mp-solon-plugin/src/test/resources/app.properties b/solon-plugins/wx-java-mp-solon-plugin/src/test/resources/app.properties new file mode 100644 index 0000000000..06abfa5bb8 --- /dev/null +++ b/solon-plugins/wx-java-mp-solon-plugin/src/test/resources/app.properties @@ -0,0 +1,11 @@ +# ?????(??) +wx.mp.app-id=appId +wx.mp.secret=@secret +wx.mp.token=@token +wx.mp.aes-key=@aesKey +wx.mp.use-stable-access-token=@useStableAccessToken +# ????redis(??) # ????: Memory(??), Jedis, RedisTemplate +wx.mp.config-storage.type=memory +wx.mp.config-storage.key-prefix=wx +wx.mp.config-storage.redis.host=127.0.0.1 +wx.mp.config-storage.redis.port=6379 diff --git a/solon-plugins/wx-java-open-solon-plugin/README.md b/solon-plugins/wx-java-open-solon-plugin/README.md new file mode 100644 index 0000000000..619e28dbdd --- /dev/null +++ b/solon-plugins/wx-java-open-solon-plugin/README.md @@ -0,0 +1,39 @@ +# wx-java-open-solon-plugin +## 快速开始 +1. 引入依赖 + ```xml + + com.github.binarywang + wx-java-open-solon-plugin + ${version} + + ``` +2. 添加配置(app.properties) + ```properties + # 公众号配置(必填) + wx.open.appId = appId + wx.open.secret = @secret + wx.open.token = @token + wx.open.aesKey = @aesKey + # 存储配置redis(可选) + # 优先注入容器的(JedisPool, RedissonClient), 当配置了wx.open.config-storage.redis.host, 不会使用容器注入redis连接配置 + wx.open.config-storage.type = redis # 配置类型: memory(默认), redis(jedis), jedis, redisson, redistemplate + wx.open.config-storage.key-prefix = wx # 相关redis前缀配置: wx(默认) + wx.open.config-storage.redis.host = 127.0.0.1 + wx.open.config-storage.redis.port = 6379 + # http客户端配置 + wx.open.config-storage.http-client-type=httpclient # http客户端类型: httpclient(默认) + wx.open.config-storage.http-proxy-host= + wx.open.config-storage.http-proxy-port= + wx.open.config-storage.http-proxy-username= + wx.open.config-storage.http-proxy-password= + # 最大重试次数,默认:5 次,如果小于 0,则为 0 + wx.open.config-storage.max-retry-times=5 + # 重试时间间隔步进,默认:1000 毫秒,如果小于 0,则为 1000 + wx.open.config-storage.retry-sleep-millis=1000 + ``` +3. 支持自动注入的类型: `WxOpenService, WxOpenMessageRouter, WxOpenComponentService` + +4. 覆盖自动配置: 自定义注入的bean会覆盖自动注入的 + - WxOpenConfigStorage + - WxOpenService diff --git a/solon-plugins/wx-java-open-solon-plugin/pom.xml b/solon-plugins/wx-java-open-solon-plugin/pom.xml new file mode 100644 index 0000000000..0f0527183f --- /dev/null +++ b/solon-plugins/wx-java-open-solon-plugin/pom.xml @@ -0,0 +1,32 @@ + + + + wx-java-solon-plugins + com.github.binarywang + 4.8.0 + + 4.0.0 + + wx-java-open-solon-plugin + WxJava - Solon Plugin for WxOpen + 微信开放平台开发的 Solon Plugin + + + + com.github.binarywang + weixin-java-open + ${project.version} + + + redis.clients + jedis + + + org.redisson + redisson + + + + diff --git a/solon-plugins/wx-java-open-solon-plugin/src/main/java/com/binarywang/solon/wxjava/open/config/WxOpenServiceAutoConfiguration.java b/solon-plugins/wx-java-open-solon-plugin/src/main/java/com/binarywang/solon/wxjava/open/config/WxOpenServiceAutoConfiguration.java new file mode 100644 index 0000000000..7bda6816ed --- /dev/null +++ b/solon-plugins/wx-java-open-solon-plugin/src/main/java/com/binarywang/solon/wxjava/open/config/WxOpenServiceAutoConfiguration.java @@ -0,0 +1,37 @@ +package com.binarywang.solon.wxjava.open.config; + +import me.chanjar.weixin.open.api.WxOpenComponentService; +import me.chanjar.weixin.open.api.WxOpenConfigStorage; +import me.chanjar.weixin.open.api.WxOpenService; +import me.chanjar.weixin.open.api.impl.WxOpenMessageRouter; +import me.chanjar.weixin.open.api.impl.WxOpenServiceImpl; +import org.noear.solon.annotation.Bean; +import org.noear.solon.annotation.Condition; +import org.noear.solon.annotation.Configuration; + +/** + * 微信开放平台相关服务自动注册. + * + * @author someone + */ +@Configuration +public class WxOpenServiceAutoConfiguration { + + @Bean + @Condition(onMissingBean = WxOpenService.class, onBean = WxOpenConfigStorage.class) + public WxOpenService wxOpenService(WxOpenConfigStorage wxOpenConfigStorage) { + WxOpenService wxOpenService = new WxOpenServiceImpl(); + wxOpenService.setWxOpenConfigStorage(wxOpenConfigStorage); + return wxOpenService; + } + + @Bean + public WxOpenMessageRouter wxOpenMessageRouter(WxOpenService wxOpenService) { + return new WxOpenMessageRouter(wxOpenService); + } + + @Bean + public WxOpenComponentService wxOpenComponentService(WxOpenService wxOpenService) { + return wxOpenService.getWxOpenComponentService(); + } +} diff --git a/solon-plugins/wx-java-open-solon-plugin/src/main/java/com/binarywang/solon/wxjava/open/config/storage/AbstractWxOpenConfigStorageConfiguration.java b/solon-plugins/wx-java-open-solon-plugin/src/main/java/com/binarywang/solon/wxjava/open/config/storage/AbstractWxOpenConfigStorageConfiguration.java new file mode 100644 index 0000000000..4a65b311d9 --- /dev/null +++ b/solon-plugins/wx-java-open-solon-plugin/src/main/java/com/binarywang/solon/wxjava/open/config/storage/AbstractWxOpenConfigStorageConfiguration.java @@ -0,0 +1,33 @@ +package com.binarywang.solon.wxjava.open.config.storage; + +import com.binarywang.solon.wxjava.open.properties.WxOpenProperties; +import me.chanjar.weixin.open.api.impl.WxOpenInMemoryConfigStorage; + +/** + * @author yl + */ +public abstract class AbstractWxOpenConfigStorageConfiguration { + + protected WxOpenInMemoryConfigStorage config(WxOpenInMemoryConfigStorage config, WxOpenProperties properties) { + WxOpenProperties.ConfigStorage storage = properties.getConfigStorage(); + config.setWxOpenInfo(properties.getAppId(), properties.getSecret(), properties.getToken(), properties.getAesKey()); + config.setHttpProxyHost(storage.getHttpProxyHost()); + config.setHttpProxyUsername(storage.getHttpProxyUsername()); + config.setHttpProxyPassword(storage.getHttpProxyPassword()); + Integer httpProxyPort = storage.getHttpProxyPort(); + if (httpProxyPort != null) { + config.setHttpProxyPort(httpProxyPort); + } + int maxRetryTimes = storage.getMaxRetryTimes(); + if (maxRetryTimes < 0) { + maxRetryTimes = 0; + } + int retrySleepMillis = storage.getRetrySleepMillis(); + if (retrySleepMillis < 0) { + retrySleepMillis = 1000; + } + config.setRetrySleepMillis(retrySleepMillis); + config.setMaxRetryTimes(maxRetryTimes); + return config; + } +} diff --git a/solon-plugins/wx-java-open-solon-plugin/src/main/java/com/binarywang/solon/wxjava/open/config/storage/WxOpenInJedisConfigStorageConfiguration.java b/solon-plugins/wx-java-open-solon-plugin/src/main/java/com/binarywang/solon/wxjava/open/config/storage/WxOpenInJedisConfigStorageConfiguration.java new file mode 100644 index 0000000000..59e65ef48c --- /dev/null +++ b/solon-plugins/wx-java-open-solon-plugin/src/main/java/com/binarywang/solon/wxjava/open/config/storage/WxOpenInJedisConfigStorageConfiguration.java @@ -0,0 +1,71 @@ +package com.binarywang.solon.wxjava.open.config.storage; + +import com.binarywang.solon.wxjava.open.properties.WxOpenProperties; +import com.binarywang.solon.wxjava.open.properties.WxOpenRedisProperties; +import lombok.RequiredArgsConstructor; +import me.chanjar.weixin.open.api.WxOpenConfigStorage; +import me.chanjar.weixin.open.api.impl.WxOpenInMemoryConfigStorage; +import me.chanjar.weixin.open.api.impl.WxOpenInRedisConfigStorage; +import org.apache.commons.lang3.StringUtils; +import org.noear.solon.annotation.Bean; +import org.noear.solon.annotation.Condition; +import org.noear.solon.annotation.Configuration; +import org.noear.solon.core.AppContext; +import redis.clients.jedis.JedisPool; +import redis.clients.jedis.JedisPoolConfig; + +/** + * @author yl + */ +@Configuration +@Condition( + onProperty = "${"+WxOpenProperties.PREFIX + ".configStorage.type} = jedis", + onClass = JedisPool.class +) +@RequiredArgsConstructor +public class WxOpenInJedisConfigStorageConfiguration extends AbstractWxOpenConfigStorageConfiguration { + private final WxOpenProperties properties; + private final AppContext applicationContext; + + @Bean + @Condition(onMissingBean=WxOpenConfigStorage.class) + public WxOpenConfigStorage wxOpenConfigStorage() { + WxOpenInMemoryConfigStorage config = getWxOpenInRedisConfigStorage(); + return this.config(config, properties); + } + + private WxOpenInRedisConfigStorage getWxOpenInRedisConfigStorage() { + WxOpenRedisProperties wxOpenRedisProperties = properties.getConfigStorage().getRedis(); + JedisPool jedisPool; + if (wxOpenRedisProperties != null && StringUtils.isNotEmpty(wxOpenRedisProperties.getHost())) { + jedisPool = getJedisPool(); + } else { + jedisPool = applicationContext.getBean(JedisPool.class); + } + return new WxOpenInRedisConfigStorage(jedisPool, properties.getConfigStorage().getKeyPrefix()); + } + + private JedisPool getJedisPool() { + WxOpenProperties.ConfigStorage storage = properties.getConfigStorage(); + WxOpenRedisProperties redis = storage.getRedis(); + + JedisPoolConfig config = new JedisPoolConfig(); + if (redis.getMaxActive() != null) { + config.setMaxTotal(redis.getMaxActive()); + } + if (redis.getMaxIdle() != null) { + config.setMaxIdle(redis.getMaxIdle()); + } + if (redis.getMaxWaitMillis() != null) { + config.setMaxWaitMillis(redis.getMaxWaitMillis()); + } + if (redis.getMinIdle() != null) { + config.setMinIdle(redis.getMinIdle()); + } + config.setTestOnBorrow(true); + config.setTestWhileIdle(true); + + return new JedisPool(config, redis.getHost(), redis.getPort(), + redis.getTimeout(), redis.getPassword(), redis.getDatabase()); + } +} diff --git a/solon-plugins/wx-java-open-solon-plugin/src/main/java/com/binarywang/solon/wxjava/open/config/storage/WxOpenInMemoryConfigStorageConfiguration.java b/solon-plugins/wx-java-open-solon-plugin/src/main/java/com/binarywang/solon/wxjava/open/config/storage/WxOpenInMemoryConfigStorageConfiguration.java new file mode 100644 index 0000000000..756b6525fc --- /dev/null +++ b/solon-plugins/wx-java-open-solon-plugin/src/main/java/com/binarywang/solon/wxjava/open/config/storage/WxOpenInMemoryConfigStorageConfiguration.java @@ -0,0 +1,28 @@ +package com.binarywang.solon.wxjava.open.config.storage; + +import com.binarywang.solon.wxjava.open.properties.WxOpenProperties; +import lombok.RequiredArgsConstructor; +import me.chanjar.weixin.open.api.WxOpenConfigStorage; +import me.chanjar.weixin.open.api.impl.WxOpenInMemoryConfigStorage; +import org.noear.solon.annotation.Bean; +import org.noear.solon.annotation.Condition; +import org.noear.solon.annotation.Configuration; + +/** + * @author yl + */ +@Configuration +@Condition( + onProperty = "${"+WxOpenProperties.PREFIX + ".configStorage.type:memory} = memory" +) +@RequiredArgsConstructor +public class WxOpenInMemoryConfigStorageConfiguration extends AbstractWxOpenConfigStorageConfiguration { + private final WxOpenProperties properties; + + @Bean + @Condition(onMissingBean=WxOpenConfigStorage.class) + public WxOpenConfigStorage wxOpenConfigStorage() { + WxOpenInMemoryConfigStorage config = new WxOpenInMemoryConfigStorage(); + return this.config(config, properties); + } +} diff --git a/solon-plugins/wx-java-open-solon-plugin/src/main/java/com/binarywang/solon/wxjava/open/config/storage/WxOpenInRedissonConfigStorageConfiguration.java b/solon-plugins/wx-java-open-solon-plugin/src/main/java/com/binarywang/solon/wxjava/open/config/storage/WxOpenInRedissonConfigStorageConfiguration.java new file mode 100644 index 0000000000..70844e2888 --- /dev/null +++ b/solon-plugins/wx-java-open-solon-plugin/src/main/java/com/binarywang/solon/wxjava/open/config/storage/WxOpenInRedissonConfigStorageConfiguration.java @@ -0,0 +1,62 @@ +package com.binarywang.solon.wxjava.open.config.storage; + +import com.binarywang.solon.wxjava.open.properties.WxOpenProperties; +import com.binarywang.solon.wxjava.open.properties.WxOpenRedisProperties; +import lombok.RequiredArgsConstructor; +import me.chanjar.weixin.open.api.WxOpenConfigStorage; +import me.chanjar.weixin.open.api.impl.WxOpenInMemoryConfigStorage; +import me.chanjar.weixin.open.api.impl.WxOpenInRedissonConfigStorage; +import org.apache.commons.lang3.StringUtils; +import org.noear.solon.annotation.Bean; +import org.noear.solon.annotation.Condition; +import org.noear.solon.annotation.Configuration; +import org.noear.solon.core.AppContext; +import org.redisson.Redisson; +import org.redisson.api.RedissonClient; +import org.redisson.config.Config; +import org.redisson.config.TransportMode; + +/** + * @author yl + */ +@Configuration +@Condition( + onProperty = "${"+WxOpenProperties.PREFIX + ".configStorage.type} = redisson", + onClass = Redisson.class +) +@RequiredArgsConstructor +public class WxOpenInRedissonConfigStorageConfiguration extends AbstractWxOpenConfigStorageConfiguration { + private final WxOpenProperties properties; + private final AppContext applicationContext; + + @Bean + @Condition(onMissingBean=WxOpenConfigStorage.class) + public WxOpenConfigStorage wxOpenConfigStorage() { + WxOpenInMemoryConfigStorage config = getWxOpenInRedissonConfigStorage(); + return this.config(config, properties); + } + + private WxOpenInRedissonConfigStorage getWxOpenInRedissonConfigStorage() { + WxOpenRedisProperties wxOpenRedisProperties = properties.getConfigStorage().getRedis(); + RedissonClient redissonClient; + if (wxOpenRedisProperties != null && StringUtils.isNotEmpty(wxOpenRedisProperties.getHost())) { + redissonClient = getRedissonClient(); + } else { + redissonClient = applicationContext.getBean(RedissonClient.class); + } + return new WxOpenInRedissonConfigStorage(redissonClient, properties.getConfigStorage().getKeyPrefix()); + } + + private RedissonClient getRedissonClient() { + WxOpenProperties.ConfigStorage storage = properties.getConfigStorage(); + WxOpenRedisProperties 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/solon-plugins/wx-java-open-solon-plugin/src/main/java/com/binarywang/solon/wxjava/open/integration/WxOpenPluginImpl.java b/solon-plugins/wx-java-open-solon-plugin/src/main/java/com/binarywang/solon/wxjava/open/integration/WxOpenPluginImpl.java new file mode 100644 index 0000000000..29352d81f0 --- /dev/null +++ b/solon-plugins/wx-java-open-solon-plugin/src/main/java/com/binarywang/solon/wxjava/open/integration/WxOpenPluginImpl.java @@ -0,0 +1,25 @@ +package com.binarywang.solon.wxjava.open.integration; + +import com.binarywang.solon.wxjava.open.config.WxOpenServiceAutoConfiguration; +import com.binarywang.solon.wxjava.open.config.storage.WxOpenInJedisConfigStorageConfiguration; +import com.binarywang.solon.wxjava.open.config.storage.WxOpenInMemoryConfigStorageConfiguration; +import com.binarywang.solon.wxjava.open.config.storage.WxOpenInRedissonConfigStorageConfiguration; +import com.binarywang.solon.wxjava.open.properties.WxOpenProperties; +import org.noear.solon.core.AppContext; +import org.noear.solon.core.Plugin; + +/** + * @author noear 2024/9/2 created + */ +public class WxOpenPluginImpl implements Plugin { + @Override + public void start(AppContext context) throws Throwable { + context.beanMake(WxOpenProperties.class); + + context.beanMake(WxOpenServiceAutoConfiguration.class); + + context.beanMake(WxOpenInMemoryConfigStorageConfiguration.class); + context.beanMake(WxOpenInJedisConfigStorageConfiguration.class); + context.beanMake(WxOpenInRedissonConfigStorageConfiguration.class); + } +} diff --git a/solon-plugins/wx-java-open-solon-plugin/src/main/java/com/binarywang/solon/wxjava/open/properties/WxOpenProperties.java b/solon-plugins/wx-java-open-solon-plugin/src/main/java/com/binarywang/solon/wxjava/open/properties/WxOpenProperties.java new file mode 100644 index 0000000000..4ec34c02b8 --- /dev/null +++ b/solon-plugins/wx-java-open-solon-plugin/src/main/java/com/binarywang/solon/wxjava/open/properties/WxOpenProperties.java @@ -0,0 +1,138 @@ +package com.binarywang.solon.wxjava.open.properties; + +import lombok.Data; +import org.noear.solon.annotation.Configuration; +import org.noear.solon.annotation.Inject; + +import java.io.Serializable; + +import static com.binarywang.solon.wxjava.open.properties.WxOpenProperties.PREFIX; +import static com.binarywang.solon.wxjava.open.properties.WxOpenProperties.StorageType.memory; + + +/** + * 微信接入相关配置属性. + * + * @author someone + */ +@Data +@Configuration +@Inject("${"+PREFIX+"}") +public class WxOpenProperties { + public static final String PREFIX = "wx.open"; + + /** + * 设置微信开放平台的appid. + */ + private String appId; + + /** + * 设置微信开放平台的app secret. + */ + private String secret; + + /** + * 设置微信开放平台的token. + */ + private String token; + + /** + * 设置微信开放平台的EncodingAESKey. + */ + private String aesKey; + + /** + * 存储策略. + */ + private ConfigStorage configStorage = new ConfigStorage(); + + + @Data + public static class ConfigStorage implements Serializable { + private static final long serialVersionUID = 4815731027000065434L; + + /** + * 存储类型. + */ + private StorageType type = memory; + + /** + * 指定key前缀. + */ + private String keyPrefix = "wx:open"; + + /** + * redis连接配置. + */ + private WxOpenRedisProperties redis = new WxOpenRedisProperties(); + + /** + * http客户端类型. + */ + private HttpClientType httpClientType = HttpClientType.httpclient; + + /** + * http代理主机. + */ + private String httpProxyHost; + + /** + * http代理端口. + */ + private Integer httpProxyPort; + + /** + * http代理用户名. + */ + private String httpProxyUsername; + + /** + * http代理密码. + */ + private String httpProxyPassword; + + /** + * http 请求重试间隔 + *
+     *   {@link me.chanjar.weixin.mp.api.impl.BaseWxMpServiceImpl#setRetrySleepMillis(int)}
+     *   {@link cn.binarywang.wx.miniapp.api.impl.BaseWxMaServiceImpl#setRetrySleepMillis(int)}
+     * 
+ */ + private int retrySleepMillis = 1000; + /** + * http 请求最大重试次数 + *
+     *   {@link me.chanjar.weixin.mp.api.impl.BaseWxMpServiceImpl#setMaxRetryTimes(int)}
+     *   {@link cn.binarywang.wx.miniapp.api.impl.BaseWxMaServiceImpl#setMaxRetryTimes(int)}
+     * 
+ */ + private int maxRetryTimes = 5; + + } + + public enum StorageType { + /** + * 内存. + */ + memory, + /** + * jedis. + */ + jedis, + /** + * redisson. + */ + redisson, + /** + * redistemplate + */ + redistemplate + } + + public enum HttpClientType { + /** + * HttpClient. + */ + httpclient + } +} diff --git a/solon-plugins/wx-java-open-solon-plugin/src/main/java/com/binarywang/solon/wxjava/open/properties/WxOpenRedisProperties.java b/solon-plugins/wx-java-open-solon-plugin/src/main/java/com/binarywang/solon/wxjava/open/properties/WxOpenRedisProperties.java new file mode 100644 index 0000000000..6b7a2d8654 --- /dev/null +++ b/solon-plugins/wx-java-open-solon-plugin/src/main/java/com/binarywang/solon/wxjava/open/properties/WxOpenRedisProperties.java @@ -0,0 +1,45 @@ +package com.binarywang.solon.wxjava.open.properties; + +import lombok.Data; + +import java.io.Serializable; + +/** + * Redis配置. + * + * @author someone + */ +@Data +public class WxOpenRedisProperties 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/solon-plugins/wx-java-open-solon-plugin/src/main/resources/META-INF/solon/wx-java-open-solon-plugin.properties b/solon-plugins/wx-java-open-solon-plugin/src/main/resources/META-INF/solon/wx-java-open-solon-plugin.properties new file mode 100644 index 0000000000..289aca5eeb --- /dev/null +++ b/solon-plugins/wx-java-open-solon-plugin/src/main/resources/META-INF/solon/wx-java-open-solon-plugin.properties @@ -0,0 +1,2 @@ +solon.plugin=com.binarywang.solon.wxjava.open.integration.WxOpenPluginImpl +solon.plugin.priority=10 diff --git a/solon-plugins/wx-java-open-solon-plugin/src/test/java/features/test/LoadTest.java b/solon-plugins/wx-java-open-solon-plugin/src/test/java/features/test/LoadTest.java new file mode 100644 index 0000000000..d049f5a51a --- /dev/null +++ b/solon-plugins/wx-java-open-solon-plugin/src/test/java/features/test/LoadTest.java @@ -0,0 +1,15 @@ +package features.test; + +import org.junit.jupiter.api.Test; +import org.noear.solon.test.SolonTest; + +/** + * @author noear 2024/9/4 created + */ +@SolonTest +public class LoadTest { + @Test + public void load(){ + + } +} diff --git a/solon-plugins/wx-java-open-solon-plugin/src/test/resources/app.properties b/solon-plugins/wx-java-open-solon-plugin/src/test/resources/app.properties new file mode 100644 index 0000000000..fc2e79c95b --- /dev/null +++ b/solon-plugins/wx-java-open-solon-plugin/src/test/resources/app.properties @@ -0,0 +1,11 @@ +# ?????(??) +wx.open.appId = appId +wx.open.secret = @secret +wx.open.token = @token +wx.open.aesKey = @aesKey +# ????redis(??) +# ???????(JedisPool, RedissonClient), ????wx.open.config-storage.redis.host, ????????redis???? +wx.open.config-storage.type = redis # ????: memory(??), redis(jedis), jedis, redisson, redistemplate +wx.open.config-storage.key-prefix = wx # ??redis????: wx(??) +wx.open.config-storage.redis.host = 127.0.0.1 +wx.open.config-storage.redis.port = 6379 diff --git a/solon-plugins/wx-java-pay-solon-plugin/README.md b/solon-plugins/wx-java-pay-solon-plugin/README.md new file mode 100644 index 0000000000..b0e212593b --- /dev/null +++ b/solon-plugins/wx-java-pay-solon-plugin/README.md @@ -0,0 +1,43 @@ +# 使用说明 +1. 在自己的Solon项目里,引入maven依赖 +```xml + + com.github.binarywang + wx-java-pay-solon-plugin + ${version} + + ``` +2. 添加配置(app.yml) +###### 1)V2版本 +```yml +wx: + pay: + appId: + mchId: + mchKey: + keyPath: +``` +###### 2)V3版本 +```yml +wx: + pay: + appId: xxxxxxxxxxx + mchId: 15xxxxxxxxx #商户id + apiV3Key: Dc1DBwSc094jACxxxxxxxxxxxxxxx #V3密钥 + certSerialNo: 62C6CEAA360BCxxxxxxxxxxxxxxx + privateKeyPath: classpath:cert/apiclient_key.pem #apiclient_key.pem证书文件的绝对路径或者以classpath:开头的类路径 + privateCertPath: classpath:cert/apiclient_cert.pem #apiclient_cert.pem证书文件的绝对路径或者以classpath:开头的类路径 +``` +###### 3)V3服务商版本 +```yml +wx: + pay: #微信服务商支付 + configs: + - appId: wxe97b2x9c2b3d #spAppId + mchId: 16486610 #服务商商户 + subAppId: wx118cexxe3c07679 #子appId + subMchId: 16496705 #子商户 + apiV3Key: Dc1DBwSc094jAKDGR5aqqb7PTHr #apiV3密钥 + privateKeyPath: classpath:cert/apiclient_key.pem #服务商证书文件,apiclient_key.pem证书文件的绝对路径或者以classpath:开头的类路径(可以配置绝对路径) + privateCertPath: classpath:cert/apiclient_cert.pem #apiclient_cert.pem证书文件的绝对路径或者以classpath:开头的类路径 +``` diff --git a/solon-plugins/wx-java-pay-solon-plugin/pom.xml b/solon-plugins/wx-java-pay-solon-plugin/pom.xml new file mode 100644 index 0000000000..7c1cb4e850 --- /dev/null +++ b/solon-plugins/wx-java-pay-solon-plugin/pom.xml @@ -0,0 +1,24 @@ + + + + wx-java-solon-plugins + com.github.binarywang + 4.8.0 + + 4.0.0 + + wx-java-pay-solon-plugin + WxJava - Solon Plugin for WxPay + 微信支付开发的 Solon Plugin + + + + com.github.binarywang + weixin-java-pay + ${project.version} + + + + 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 new file mode 100644 index 0000000000..3ef7456daa --- /dev/null +++ b/solon-plugins/wx-java-pay-solon-plugin/src/main/java/com/binarywang/solon/wxjava/pay/config/WxPayAutoConfiguration.java @@ -0,0 +1,69 @@ +package com.binarywang.solon.wxjava.pay.config; + +import com.binarywang.solon.wxjava.pay.properties.WxPayProperties; +import com.github.binarywang.wxpay.config.WxPayConfig; +import com.github.binarywang.wxpay.service.WxPayService; +import com.github.binarywang.wxpay.service.impl.WxPayServiceImpl; +import org.apache.commons.lang3.StringUtils; +import org.noear.solon.annotation.Bean; +import org.noear.solon.annotation.Condition; +import org.noear.solon.annotation.Configuration; + +/** + *
+ *  微信支付自动配置
+ *  Created by BinaryWang on 2019/4/17.
+ * 
+ * + * @author Binary Wang + */ +@Configuration +@Condition( + onProperty = "${wx.pay.enabled:true} = true", + onClass=WxPayService.class +) +public class WxPayAutoConfiguration { + private WxPayProperties properties; + + public WxPayAutoConfiguration(WxPayProperties properties) { + this.properties = properties; + } + + /** + * 构造微信支付服务对象. + * + * @return 微信支付service + */ + @Bean + @Condition(onMissingBean=WxPayService.class) + public WxPayService wxPayService() { + final WxPayServiceImpl wxPayService = new WxPayServiceImpl(); + WxPayConfig payConfig = new WxPayConfig(); + payConfig.setAppId(StringUtils.trimToNull(this.properties.getAppId())); + payConfig.setMchId(StringUtils.trimToNull(this.properties.getMchId())); + payConfig.setMchKey(StringUtils.trimToNull(this.properties.getMchKey())); + payConfig.setSubAppId(StringUtils.trimToNull(this.properties.getSubAppId())); + payConfig.setSubMchId(StringUtils.trimToNull(this.properties.getSubMchId())); + payConfig.setKeyPath(StringUtils.trimToNull(this.properties.getKeyPath())); + payConfig.setUseSandboxEnv(this.properties.isUseSandboxEnv()); + payConfig.setNotifyUrl(StringUtils.trimToNull(this.properties.getNotifyUrl())); + payConfig.setRefundNotifyUrl(StringUtils.trimToNull(this.properties.getRefundNotifyUrl())); + //以下是apiv3以及支付分相关 + payConfig.setServiceId(StringUtils.trimToNull(this.properties.getServiceId())); + payConfig.setPayScoreNotifyUrl(StringUtils.trimToNull(this.properties.getPayScoreNotifyUrl())); + 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/integration/WxPayPluginImpl.java b/solon-plugins/wx-java-pay-solon-plugin/src/main/java/com/binarywang/solon/wxjava/pay/integration/WxPayPluginImpl.java new file mode 100644 index 0000000000..e7ba275ca0 --- /dev/null +++ b/solon-plugins/wx-java-pay-solon-plugin/src/main/java/com/binarywang/solon/wxjava/pay/integration/WxPayPluginImpl.java @@ -0,0 +1,18 @@ +package com.binarywang.solon.wxjava.pay.integration; + +import com.binarywang.solon.wxjava.pay.config.WxPayAutoConfiguration; +import com.binarywang.solon.wxjava.pay.properties.WxPayProperties; +import org.noear.solon.core.AppContext; +import org.noear.solon.core.Plugin; + +/** + * @author noear 2024/9/2 created + */ +public class WxPayPluginImpl implements Plugin { + @Override + public void start(AppContext context) throws Throwable { + context.beanMake(WxPayProperties.class); + + context.beanMake(WxPayAutoConfiguration.class); + } +} 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 new file mode 100644 index 0000000000..d394fefbd1 --- /dev/null +++ b/solon-plugins/wx-java-pay-solon-plugin/src/main/java/com/binarywang/solon/wxjava/pay/properties/WxPayProperties.java @@ -0,0 +1,126 @@ +package com.binarywang.solon.wxjava.pay.properties; + +import lombok.Data; +import org.noear.solon.annotation.Configuration; +import org.noear.solon.annotation.Inject; + +/** + *
+ *  微信支付属性配置类
+ * Created by Binary Wang on 2019/4/17.
+ * 
+ * + * @author Binary Wang + */ +@Data +@Configuration +@Inject("${wx.pay}") +public class WxPayProperties { + /** + * 设置微信公众号或者小程序等的appid. + */ + private String appId; + + /** + * 微信支付商户号. + */ + private String mchId; + + /** + * 微信支付商户密钥. + */ + private String mchKey; + + /** + * 服务商模式下的子商户公众账号ID,普通模式请不要配置,请在配置文件中将对应项删除. + */ + private String subAppId; + + /** + * 服务商模式下的子商户号,普通模式请不要配置,最好是请在配置文件中将对应项删除. + */ + private String subMchId; + + /** + * apiclient_cert.p12文件的绝对路径,或者如果放在项目中,请以classpath:开头指定. + */ + private String keyPath; + + /** + * 微信支付分serviceId + */ + private String serviceId; + + /** + * 证书序列号 + */ + private String certSerialNo; + + /** + * apiV3秘钥 + */ + private String apiv3Key; + + /** + * 微信支付分回调地址 + */ + private String payScoreNotifyUrl; + + /** + * apiv3 商户apiclient_key.pem + */ + private String privateKeyPath; + + /** + * apiv3 商户apiclient_cert.pem + */ + private String privateCertPath; + + /** + * 微信支付是否使用仿真测试环境. + * 默认不使用 + */ + private boolean useSandboxEnv; + + /** + * 微信支付异步回调地址,通知url必须为直接可访问的url,不能携带参数 + */ + private String notifyUrl; + + /** + * 退款结果异步回调地址,通知url必须为直接可访问的url,不能携带参数. + */ + private String refundNotifyUrl; + + /** + * 微信支付分授权回调地址 + */ + 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-pay-solon-plugin/src/main/resources/META-INF/solon/wx-java-pay-solon-plugin.properties b/solon-plugins/wx-java-pay-solon-plugin/src/main/resources/META-INF/solon/wx-java-pay-solon-plugin.properties new file mode 100644 index 0000000000..98783176e2 --- /dev/null +++ b/solon-plugins/wx-java-pay-solon-plugin/src/main/resources/META-INF/solon/wx-java-pay-solon-plugin.properties @@ -0,0 +1,2 @@ +solon.plugin=com.binarywang.solon.wxjava.pay.integration.WxPayPluginImpl +solon.plugin.priority=10 diff --git a/solon-plugins/wx-java-pay-solon-plugin/src/test/java/features/test/LoadTest.java b/solon-plugins/wx-java-pay-solon-plugin/src/test/java/features/test/LoadTest.java new file mode 100644 index 0000000000..d049f5a51a --- /dev/null +++ b/solon-plugins/wx-java-pay-solon-plugin/src/test/java/features/test/LoadTest.java @@ -0,0 +1,15 @@ +package features.test; + +import org.junit.jupiter.api.Test; +import org.noear.solon.test.SolonTest; + +/** + * @author noear 2024/9/4 created + */ +@SolonTest +public class LoadTest { + @Test + public void load(){ + + } +} diff --git a/solon-plugins/wx-java-pay-solon-plugin/src/test/resources/app.yml b/solon-plugins/wx-java-pay-solon-plugin/src/test/resources/app.yml new file mode 100644 index 0000000000..1d6a61d7e5 --- /dev/null +++ b/solon-plugins/wx-java-pay-solon-plugin/src/test/resources/app.yml @@ -0,0 +1,6 @@ +wx: + pay: + appId: + mchId: + mchKey: + keyPath: diff --git a/solon-plugins/wx-java-qidian-solon-plugin/README.md b/solon-plugins/wx-java-qidian-solon-plugin/README.md new file mode 100644 index 0000000000..42daa3e4c8 --- /dev/null +++ b/solon-plugins/wx-java-qidian-solon-plugin/README.md @@ -0,0 +1,45 @@ +# wx-java-qidian-solon-plugin + +## 快速开始 + +1. 引入依赖 + ```xml + + com.github.binarywang + wx-java-qidian-solon-plugin + ${version} + + ``` +2. 添加配置(app.properties) + ```properties + # 公众号配置(必填) + wx.qidian.appId = appId + wx.qidian.secret = @secret + wx.qidian.token = @token + wx.qidian.aesKey = @aesKey + # 存储配置redis(可选) + wx.qidian.config-storage.type = Jedis # 配置类型: Memory(默认), Jedis, RedisTemplate + wx.qidian.config-storage.key-prefix = wx # 相关redis前缀配置: wx(默认) + wx.qidian.config-storage.redis.host = 127.0.0.1 + wx.qidian.config-storage.redis.port = 6379 + #单机和sentinel同时存在时,优先使用sentinel配置 + #wx.qidian.config-storage.redis.sentinel-ips=127.0.0.1:16379,127.0.0.1:26379 + #wx.qidian.config-storage.redis.sentinel-name=mymaster + # http客户端配置 + wx.qidian.config-storage.http-client-type=httpclient # http客户端类型: HttpClient(默认), OkHttp, JoddHttp + wx.qidian.config-storage.http-proxy-host= + wx.qidian.config-storage.http-proxy-port= + wx.qidian.config-storage.http-proxy-username= + wx.qidian.config-storage.http-proxy-password= + # 公众号地址host配置 + #wx.qidian.hosts.api-host=http://proxy.com/ + #wx.qidian.hosts.open-host=http://proxy.com/ + #wx.qidian.hosts.mp-host=http://proxy.com/ + ``` +3. 自动注入的类型 + +- `WxQidianService` +- `WxQidianConfigStorage` + +4、参考 demo: +https://github.com/binarywang/wx-java-mp-demo diff --git a/solon-plugins/wx-java-qidian-solon-plugin/pom.xml b/solon-plugins/wx-java-qidian-solon-plugin/pom.xml new file mode 100644 index 0000000000..724bdf4ac5 --- /dev/null +++ b/solon-plugins/wx-java-qidian-solon-plugin/pom.xml @@ -0,0 +1,38 @@ + + + + wx-java-solon-plugins + com.github.binarywang + 4.8.0 + + 4.0.0 + + wx-java-qidian-solon-plugin + WxJava - Solon Plugin for QiDian + 腾讯企点的 Solon Plugin + + + + com.github.binarywang + weixin-java-qidian + ${project.version} + + + redis.clients + jedis + 4.3.2 + compile + + + org.jodd + jodd-http + provided + + + com.squareup.okhttp3 + okhttp + provided + + + + diff --git a/solon-plugins/wx-java-qidian-solon-plugin/src/main/java/com/binarywang/solon/wxjava/qidian/config/WxQidianServiceAutoConfiguration.java b/solon-plugins/wx-java-qidian-solon-plugin/src/main/java/com/binarywang/solon/wxjava/qidian/config/WxQidianServiceAutoConfiguration.java new file mode 100644 index 0000000000..02ec06cd25 --- /dev/null +++ b/solon-plugins/wx-java-qidian-solon-plugin/src/main/java/com/binarywang/solon/wxjava/qidian/config/WxQidianServiceAutoConfiguration.java @@ -0,0 +1,71 @@ +package com.binarywang.solon.wxjava.qidian.config; + +import com.binarywang.solon.wxjava.qidian.enums.HttpClientType; +import com.binarywang.solon.wxjava.qidian.properties.WxQidianProperties; +import me.chanjar.weixin.qidian.api.WxQidianService; +import me.chanjar.weixin.qidian.api.impl.WxQidianServiceHttpClientImpl; +import me.chanjar.weixin.qidian.api.impl.WxQidianServiceHttpComponentsImpl; +import me.chanjar.weixin.qidian.api.impl.WxQidianServiceImpl; +import me.chanjar.weixin.qidian.api.impl.WxQidianServiceJoddHttpImpl; +import me.chanjar.weixin.qidian.api.impl.WxQidianServiceOkHttpImpl; +import me.chanjar.weixin.qidian.config.WxQidianConfigStorage; +import org.noear.solon.annotation.Bean; +import org.noear.solon.annotation.Condition; +import org.noear.solon.annotation.Configuration; + +/** + * 腾讯企点相关服务自动注册. + * + * @author alegria + */ +@Configuration +public class WxQidianServiceAutoConfiguration { + + @Bean + @Condition(onMissingBean = WxQidianService.class) + public WxQidianService wxQidianService(WxQidianConfigStorage configStorage, WxQidianProperties wxQidianProperties) { + HttpClientType httpClientType = wxQidianProperties.getConfigStorage().getHttpClientType(); + WxQidianService wxQidianService; + switch (httpClientType) { + case OkHttp: + wxQidianService = newWxQidianServiceOkHttpImpl(); + break; + case JoddHttp: + wxQidianService = newWxQidianServiceJoddHttpImpl(); + break; + case HttpClient: + wxQidianService = newWxQidianServiceHttpClientImpl(); + break; + case HttpComponents: + wxQidianService = newWxQidianServiceHttpComponentsImpl(); + break; + default: + wxQidianService = newWxQidianServiceImpl(); + break; + } + + wxQidianService.setWxMpConfigStorage(configStorage); + return wxQidianService; + } + + private WxQidianService newWxQidianServiceImpl() { + return new WxQidianServiceImpl(); + } + + private WxQidianService newWxQidianServiceHttpClientImpl() { + return new WxQidianServiceHttpClientImpl(); + } + + private WxQidianService newWxQidianServiceOkHttpImpl() { + return new WxQidianServiceOkHttpImpl(); + } + + private WxQidianService newWxQidianServiceJoddHttpImpl() { + return new WxQidianServiceJoddHttpImpl(); + } + + private WxQidianService newWxQidianServiceHttpComponentsImpl() { + return new WxQidianServiceHttpComponentsImpl(); + } + +} diff --git a/solon-plugins/wx-java-qidian-solon-plugin/src/main/java/com/binarywang/solon/wxjava/qidian/config/WxQidianStorageAutoConfiguration.java b/solon-plugins/wx-java-qidian-solon-plugin/src/main/java/com/binarywang/solon/wxjava/qidian/config/WxQidianStorageAutoConfiguration.java new file mode 100644 index 0000000000..a99a8178c9 --- /dev/null +++ b/solon-plugins/wx-java-qidian-solon-plugin/src/main/java/com/binarywang/solon/wxjava/qidian/config/WxQidianStorageAutoConfiguration.java @@ -0,0 +1,137 @@ +package com.binarywang.solon.wxjava.qidian.config; + +import com.binarywang.solon.wxjava.qidian.enums.StorageType; +import com.binarywang.solon.wxjava.qidian.properties.RedisProperties; +import com.binarywang.solon.wxjava.qidian.properties.WxQidianProperties; +import com.google.common.collect.Sets; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import me.chanjar.weixin.common.redis.JedisWxRedisOps; +import me.chanjar.weixin.common.redis.WxRedisOps; +import me.chanjar.weixin.qidian.bean.WxQidianHostConfig; +import me.chanjar.weixin.qidian.config.WxQidianConfigStorage; +import me.chanjar.weixin.qidian.config.impl.WxQidianDefaultConfigImpl; +import me.chanjar.weixin.qidian.config.impl.WxQidianRedisConfigImpl; +import org.apache.commons.lang3.StringUtils; +import org.noear.solon.annotation.Bean; +import org.noear.solon.annotation.Condition; +import org.noear.solon.annotation.Configuration; +import org.noear.solon.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; + +/** + * 腾讯企点存储策略自动配置. + * + * @author alegria + */ +@Slf4j +@Configuration +@RequiredArgsConstructor +public class WxQidianStorageAutoConfiguration { + private final AppContext applicationContext; + + private final WxQidianProperties wxQidianProperties; + + @Inject("${wx.mp.config-storage.redis.host:") + private String redisHost; + + @Inject("${wx.mp.configStorage.redis.host:") + private String redisHost2; + + @Bean + @Condition(onMissingBean=WxQidianConfigStorage.class) + public WxQidianConfigStorage wxQidianConfigStorage() { + StorageType type = wxQidianProperties.getConfigStorage().getType(); + WxQidianConfigStorage config; + switch (type) { + case Jedis: + config = jedisConfigStorage(); + break; + default: + config = defaultConfigStorage(); + break; + } + // wx host config + if (null != wxQidianProperties.getHosts() && StringUtils.isNotEmpty(wxQidianProperties.getHosts().getApiHost())) { + WxQidianHostConfig hostConfig = new WxQidianHostConfig(); + hostConfig.setApiHost(wxQidianProperties.getHosts().getApiHost()); + hostConfig.setQidianHost(wxQidianProperties.getHosts().getQidianHost()); + hostConfig.setOpenHost(wxQidianProperties.getHosts().getOpenHost()); + config.setHostConfig(hostConfig); + } + return config; + } + + private WxQidianConfigStorage defaultConfigStorage() { + WxQidianDefaultConfigImpl config = new WxQidianDefaultConfigImpl(); + setWxMpInfo(config); + return config; + } + + private WxQidianConfigStorage jedisConfigStorage() { + Pool jedisPool; + if (StringUtils.isNotEmpty(redisHost) || StringUtils.isNotEmpty(redisHost2)) { + jedisPool = getJedisPool(); + } else { + jedisPool = applicationContext.getBean(JedisPool.class); + } + WxRedisOps redisOps = new JedisWxRedisOps(jedisPool); + WxQidianRedisConfigImpl wxQidianRedisConfig = new WxQidianRedisConfigImpl(redisOps, + wxQidianProperties.getConfigStorage().getKeyPrefix()); + setWxMpInfo(wxQidianRedisConfig); + return wxQidianRedisConfig; + } + + private void setWxMpInfo(WxQidianDefaultConfigImpl config) { + WxQidianProperties properties = wxQidianProperties; + WxQidianProperties.ConfigStorage configStorageProperties = properties.getConfigStorage(); + config.setAppId(properties.getAppId()); + config.setSecret(properties.getSecret()); + config.setToken(properties.getToken()); + config.setAesKey(properties.getAesKey()); + + config.setHttpProxyHost(configStorageProperties.getHttpProxyHost()); + config.setHttpProxyUsername(configStorageProperties.getHttpProxyUsername()); + config.setHttpProxyPassword(configStorageProperties.getHttpProxyPassword()); + if (configStorageProperties.getHttpProxyPort() != null) { + config.setHttpProxyPort(configStorageProperties.getHttpProxyPort()); + } + } + + private Pool getJedisPool() { + WxQidianProperties.ConfigStorage storage = wxQidianProperties.getConfigStorage(); + RedisProperties redis = storage.getRedis(); + + JedisPoolConfig config = new JedisPoolConfig(); + if (redis.getMaxActive() != null) { + config.setMaxTotal(redis.getMaxActive()); + } + if (redis.getMaxIdle() != null) { + config.setMaxIdle(redis.getMaxIdle()); + } + if (redis.getMaxWaitMillis() != null) { + config.setMaxWait(Duration.ofMillis(redis.getMaxWaitMillis())); + } + if (redis.getMinIdle() != null) { + config.setMinIdle(redis.getMinIdle()); + } + config.setTestOnBorrow(true); + config.setTestWhileIdle(true); + if (StringUtils.isNotEmpty(redis.getSentinelIps())) { + + Set sentinels = Sets.newHashSet(redis.getSentinelIps().split(",")); + return new JedisSentinelPool(redis.getSentinelName(), sentinels,config); + } + + return new JedisPool(config, redis.getHost(), redis.getPort(), redis.getTimeout(), redis.getPassword(), + redis.getDatabase()); + } +} diff --git a/solon-plugins/wx-java-qidian-solon-plugin/src/main/java/com/binarywang/solon/wxjava/qidian/enums/HttpClientType.java b/solon-plugins/wx-java-qidian-solon-plugin/src/main/java/com/binarywang/solon/wxjava/qidian/enums/HttpClientType.java new file mode 100644 index 0000000000..5729ab8fda --- /dev/null +++ b/solon-plugins/wx-java-qidian-solon-plugin/src/main/java/com/binarywang/solon/wxjava/qidian/enums/HttpClientType.java @@ -0,0 +1,26 @@ +package com.binarywang.solon.wxjava.qidian.enums; + +/** + * httpclient类型. + * + * @author Binary Wang + * created on 2020-08-30 + */ +public enum HttpClientType { + /** + * HttpClient. + */ + HttpClient, + /** + * OkHttp. + */ + OkHttp, + /** + * JoddHttp. + */ + JoddHttp, + /** + * HttpComponents. + */ + HttpComponents, +} diff --git a/solon-plugins/wx-java-qidian-solon-plugin/src/main/java/com/binarywang/solon/wxjava/qidian/enums/StorageType.java b/solon-plugins/wx-java-qidian-solon-plugin/src/main/java/com/binarywang/solon/wxjava/qidian/enums/StorageType.java new file mode 100644 index 0000000000..c4bd2f72cf --- /dev/null +++ b/solon-plugins/wx-java-qidian-solon-plugin/src/main/java/com/binarywang/solon/wxjava/qidian/enums/StorageType.java @@ -0,0 +1,26 @@ +package com.binarywang.solon.wxjava.qidian.enums; + +/** + * storage类型. + * + * @author Binary Wang + * created on 2020-08-30 + */ +public enum StorageType { + /** + * 内存. + */ + Memory, + /** + * redis(JedisClient). + */ + Jedis, + /** + * redis(Redisson). + */ + Redisson, + /** + * redis(RedisTemplate). + */ + RedisTemplate +} diff --git a/solon-plugins/wx-java-qidian-solon-plugin/src/main/java/com/binarywang/solon/wxjava/qidian/integration/WxQidianPluginImpl.java b/solon-plugins/wx-java-qidian-solon-plugin/src/main/java/com/binarywang/solon/wxjava/qidian/integration/WxQidianPluginImpl.java new file mode 100644 index 0000000000..2a97b512fd --- /dev/null +++ b/solon-plugins/wx-java-qidian-solon-plugin/src/main/java/com/binarywang/solon/wxjava/qidian/integration/WxQidianPluginImpl.java @@ -0,0 +1,20 @@ +package com.binarywang.solon.wxjava.qidian.integration; + +import com.binarywang.solon.wxjava.qidian.config.WxQidianServiceAutoConfiguration; +import com.binarywang.solon.wxjava.qidian.config.WxQidianStorageAutoConfiguration; +import com.binarywang.solon.wxjava.qidian.properties.WxQidianProperties; +import org.noear.solon.core.AppContext; +import org.noear.solon.core.Plugin; + +/** + * @author noear 2024/9/2 created + */ +public class WxQidianPluginImpl implements Plugin{ + @Override + public void start(AppContext context) throws Throwable { + context.beanMake(WxQidianProperties.class); + + context.beanMake(WxQidianStorageAutoConfiguration.class); + context.beanMake(WxQidianServiceAutoConfiguration.class); + } +} diff --git a/solon-plugins/wx-java-qidian-solon-plugin/src/main/java/com/binarywang/solon/wxjava/qidian/properties/HostConfig.java b/solon-plugins/wx-java-qidian-solon-plugin/src/main/java/com/binarywang/solon/wxjava/qidian/properties/HostConfig.java new file mode 100644 index 0000000000..08546d8da6 --- /dev/null +++ b/solon-plugins/wx-java-qidian-solon-plugin/src/main/java/com/binarywang/solon/wxjava/qidian/properties/HostConfig.java @@ -0,0 +1,18 @@ +package com.binarywang.solon.wxjava.qidian.properties; + +import lombok.Data; + +import java.io.Serializable; + +@Data +public class HostConfig implements Serializable { + + private static final long serialVersionUID = -4172767630740346001L; + + private String apiHost; + + private String openHost; + + private String qidianHost; + +} diff --git a/solon-plugins/wx-java-qidian-solon-plugin/src/main/java/com/binarywang/solon/wxjava/qidian/properties/RedisProperties.java b/solon-plugins/wx-java-qidian-solon-plugin/src/main/java/com/binarywang/solon/wxjava/qidian/properties/RedisProperties.java new file mode 100644 index 0000000000..a6b10a9e0f --- /dev/null +++ b/solon-plugins/wx-java-qidian-solon-plugin/src/main/java/com/binarywang/solon/wxjava/qidian/properties/RedisProperties.java @@ -0,0 +1,56 @@ +package com.binarywang.solon.wxjava.qidian.properties; + +import lombok.Data; + +import java.io.Serializable; + +/** + * redis 配置属性. + * + * @author Binary Wang + * created on 2020-08-30 + */ +@Data +public class RedisProperties implements Serializable { + private static final long serialVersionUID = -5924815351660074401L; + + /** + * 主机地址. + */ + private String host = "127.0.0.1"; + + /** + * 端口号. + */ + private int port = 6379; + + /** + * 密码. + */ + private String password; + + /** + * 超时. + */ + private int timeout = 2000; + + /** + * 数据库. + */ + private int database = 0; + + /** + * sentinel ips + */ + private String sentinelIps; + + /** + * sentinel name + */ + private String sentinelName; + + private Integer maxActive; + private Integer maxIdle; + private Integer maxWaitMillis; + private Integer minIdle; +} diff --git a/solon-plugins/wx-java-qidian-solon-plugin/src/main/java/com/binarywang/solon/wxjava/qidian/properties/WxQidianProperties.java b/solon-plugins/wx-java-qidian-solon-plugin/src/main/java/com/binarywang/solon/wxjava/qidian/properties/WxQidianProperties.java new file mode 100644 index 0000000000..e99f882e6f --- /dev/null +++ b/solon-plugins/wx-java-qidian-solon-plugin/src/main/java/com/binarywang/solon/wxjava/qidian/properties/WxQidianProperties.java @@ -0,0 +1,101 @@ +package com.binarywang.solon.wxjava.qidian.properties; + +import com.binarywang.solon.wxjava.qidian.enums.HttpClientType; +import com.binarywang.solon.wxjava.qidian.enums.StorageType; +import lombok.Data; +import org.noear.solon.annotation.Configuration; +import org.noear.solon.annotation.Inject; + +import java.io.Serializable; + +import static com.binarywang.solon.wxjava.qidian.enums.StorageType.Memory; +import static com.binarywang.solon.wxjava.qidian.properties.WxQidianProperties.PREFIX; + +/** + * 企点接入相关配置属性. + * + * @author someone + */ +@Data +@Configuration +@Inject("${" + PREFIX + "}") +public class WxQidianProperties { + public static final String PREFIX = "wx.qidian"; + + /** + * 设置腾讯企点的appid. + */ + private String appId; + + /** + * 设置腾讯企点的app secret. + */ + private String secret; + + /** + * 设置腾讯企点的token. + */ + private String token; + + /** + * 设置腾讯企点的EncodingAESKey. + */ + private String aesKey; + + /** + * 自定义host配置 + */ + private HostConfig hosts; + + /** + * 存储策略 + */ + private ConfigStorage configStorage = new ConfigStorage(); + + @Data + public static class ConfigStorage implements Serializable { + private static final long serialVersionUID = 4815731027000065434L; + + /** + * 存储类型. + */ + private StorageType type = Memory; + + /** + * 指定key前缀. + */ + private String keyPrefix = "wx"; + + /** + * redis连接配置. + */ + private RedisProperties redis = new RedisProperties(); + + /** + * http客户端类型. + */ + private HttpClientType httpClientType = HttpClientType.HttpComponents; + + /** + * http代理主机. + */ + private String httpProxyHost; + + /** + * http代理端口. + */ + private Integer httpProxyPort; + + /** + * http代理用户名. + */ + private String httpProxyUsername; + + /** + * http代理密码. + */ + private String httpProxyPassword; + + } + +} diff --git a/solon-plugins/wx-java-qidian-solon-plugin/src/main/resources/META-INF/solon/wx-java-qidian-solon-plugin.properties b/solon-plugins/wx-java-qidian-solon-plugin/src/main/resources/META-INF/solon/wx-java-qidian-solon-plugin.properties new file mode 100644 index 0000000000..a60c450b06 --- /dev/null +++ b/solon-plugins/wx-java-qidian-solon-plugin/src/main/resources/META-INF/solon/wx-java-qidian-solon-plugin.properties @@ -0,0 +1,2 @@ +solon.plugin=com.binarywang.solon.wxjava.qidian.integration.WxQidianPluginImpl +solon.plugin.priority=10 diff --git a/solon-plugins/wx-java-qidian-solon-plugin/src/test/java/features/test/LoadTest.java b/solon-plugins/wx-java-qidian-solon-plugin/src/test/java/features/test/LoadTest.java new file mode 100644 index 0000000000..d049f5a51a --- /dev/null +++ b/solon-plugins/wx-java-qidian-solon-plugin/src/test/java/features/test/LoadTest.java @@ -0,0 +1,15 @@ +package features.test; + +import org.junit.jupiter.api.Test; +import org.noear.solon.test.SolonTest; + +/** + * @author noear 2024/9/4 created + */ +@SolonTest +public class LoadTest { + @Test + public void load(){ + + } +} diff --git a/solon-plugins/wx-java-qidian-solon-plugin/src/test/resources/app.yml b/solon-plugins/wx-java-qidian-solon-plugin/src/test/resources/app.yml new file mode 100644 index 0000000000..e69de29bb2 diff --git a/spring-boot-starters/pom.xml b/spring-boot-starters/pom.xml index a04dc13547..e145e5fd66 100644 --- a/spring-boot-starters/pom.xml +++ b/spring-boot-starters/pom.xml @@ -6,7 +6,7 @@ com.github.binarywang wx-java - 3.6.0 + 4.8.0 pom wx-java-spring-boot-starters @@ -14,14 +14,23 @@ WxJava 各个模块的 Spring Boot Starter - 2.1.4.RELEASE + 2.5.15 + wx-java-miniapp-multi-spring-boot-starter wx-java-miniapp-spring-boot-starter + wx-java-mp-multi-spring-boot-starter wx-java-mp-spring-boot-starter wx-java-pay-spring-boot-starter + wx-java-open-multi-spring-boot-starter wx-java-open-spring-boot-starter + wx-java-qidian-spring-boot-starter + wx-java-cp-multi-spring-boot-starter + 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/README.md b/spring-boot-starters/wx-java-channel-multi-spring-boot-starter/README.md new file mode 100644 index 0000000000..c2f082bec8 --- /dev/null +++ b/spring-boot-starters/wx-java-channel-multi-spring-boot-starter/README.md @@ -0,0 +1,123 @@ +# wx-java-channel-multi-spring-boot-starter + +## 快速开始 + +1. 引入依赖 + ```xml + + + com.github.binarywang + wx-java-channel-multi-spring-boot-starter + ${version} + + + + + redis.clients + jedis + ${jedis.version} + + + + + org.redisson + redisson + ${redisson.version} + + + + + org.springframework.boot + spring-boot-starter-data-redis + + + ``` +2. 添加配置(application.properties) + ```properties + # 视频号配置 + ## 应用 1 配置(必填) + wx.channel.apps.tenantId1.app-id=@appId + wx.channel.apps.tenantId1.secret=@secret + ## 选填 + wx.channel.apps.tenantId1.use-stable-access-token=false + wx.channel.apps.tenantId1.token= + wx.channel.apps.tenantId1.aes-key= + ## 应用 2 配置(必填) + wx.channel.apps.tenantId2.app-id=@appId + wx.channel.apps.tenantId2.secret=@secret + ## 选填 + wx.channel.apps.tenantId2.use-stable-access-token=false + wx.channel.apps.tenantId2.token= + wx.channel.apps.tenantId2.aes-key= + + # ConfigStorage 配置(选填) + ## 配置类型: memory(默认), jedis, redisson, redis_template + wx.channel.config-storage.type=memory + ## 相关redis前缀配置: wx:channel:multi(默认) + wx.channel.config-storage.key-prefix=wx:channel:multi + wx.channel.config-storage.redis.host=127.0.0.1 + wx.channel.config-storage.redis.port=6379 + wx.channel.config-storage.redis.password=123456 + + # redis_template 方式使用spring data redis配置 + spring.data.redis.database=0 + spring.data.redis.host=127.0.0.1 + spring.data.redis.password=123456 + spring.data.redis.port=6379 + + # 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. 自动注入的类型:`WxChannelMultiServices` + +4. 使用样例 + + ```java + import com.binarywang.spring.starter.wxjava.channel.service.WxChannelMultiServices; + import me.chanjar.weixin.channel.api.WxChannelService; + import me.chanjar.weixin.channel.api.WxFinderLiveService; + import me.chanjar.weixin.channel.bean.lead.component.response.FinderAttrResponse; + import me.chanjar.weixin.common.error.WxErrorException; + import org.springframework.beans.factory.annotation.Autowired; + import org.springframework.stereotype.Service; + + @Service + public class DemoService { + @Autowired + private WxChannelMultiServices wxChannelMultiServices; + + public void test() throws WxErrorException { + // 应用 1 的 WxChannelService + WxChannelService wxChannelService1 = wxChannelMultiServices.getWxChannelService("tenantId1"); + WxFinderLiveService finderLiveService = wxChannelService1.getFinderLiveService(); + FinderAttrResponse response1 = finderLiveService.getFinderAttrByAppid(); + // todo ... + + // 应用 2 的 WxChannelService + WxChannelService wxChannelService2 = wxChannelMultiServices.getWxChannelService("tenantId2"); + WxFinderLiveService finderLiveService2 = wxChannelService2.getFinderLiveService(); + FinderAttrResponse response2 = finderLiveService2.getFinderAttrByAppid(); + // todo ... + + // 应用 3 的 WxChannelService + WxChannelService wxChannelService3 = wxChannelMultiServices.getWxChannelService("tenantId3"); + // 判断是否为空 + if (wxChannelService3 == null) { + // todo wxChannelService3 为空,请先配置 tenantId3 微信视频号应用参数 + return; + } + WxFinderLiveService finderLiveService3 = wxChannelService3.getFinderLiveService(); + FinderAttrResponse response3 = finderLiveService3.getFinderAttrByAppid(); + // todo ... + } + } + ``` 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 new file mode 100644 index 0000000000..b44f597d22 --- /dev/null +++ b/spring-boot-starters/wx-java-channel-multi-spring-boot-starter/pom.xml @@ -0,0 +1,71 @@ + + + + wx-java-spring-boot-starters + com.github.binarywang + 4.8.0 + + 4.0.0 + + wx-java-channel-multi-spring-boot-starter + WxJava - Spring Boot Starter for Channel::支持多账号配置 + 微信视频号开发的 Spring Boot Starter::支持多账号配置 + + + + com.github.binarywang + weixin-java-channel + ${project.version} + + + redis.clients + jedis + provided + + + org.redisson + redisson + provided + + + org.springframework.data + spring-data-redis + provided + + + org.jodd + jodd-http + provided + + + com.squareup.okhttp3 + okhttp + 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-channel-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/channel/autoconfigure/WxChannelMultiAutoConfiguration.java b/spring-boot-starters/wx-java-channel-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/channel/autoconfigure/WxChannelMultiAutoConfiguration.java new file mode 100644 index 0000000000..e6ef922b43 --- /dev/null +++ b/spring-boot-starters/wx-java-channel-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/channel/autoconfigure/WxChannelMultiAutoConfiguration.java @@ -0,0 +1,15 @@ +package com.binarywang.spring.starter.wxjava.channel.autoconfigure; + +import com.binarywang.spring.starter.wxjava.channel.configuration.WxChannelMultiServiceConfiguration; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; + +/** + * 微信视频号自动注册 + * + * @author Winnie + * @date 2024/9/13 + */ +@Configuration +@Import(WxChannelMultiServiceConfiguration.class) +public class WxChannelMultiAutoConfiguration {} diff --git a/spring-boot-starters/wx-java-channel-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/channel/configuration/WxChannelMultiServiceConfiguration.java b/spring-boot-starters/wx-java-channel-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/channel/configuration/WxChannelMultiServiceConfiguration.java new file mode 100644 index 0000000000..17cd0da723 --- /dev/null +++ b/spring-boot-starters/wx-java-channel-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/channel/configuration/WxChannelMultiServiceConfiguration.java @@ -0,0 +1,21 @@ +package com.binarywang.spring.starter.wxjava.channel.configuration; + +import com.binarywang.spring.starter.wxjava.channel.configuration.services.WxChannelInJedisConfiguration; +import com.binarywang.spring.starter.wxjava.channel.configuration.services.WxChannelInMemoryConfiguration; +import com.binarywang.spring.starter.wxjava.channel.configuration.services.WxChannelInRedisTemplateConfiguration; +import com.binarywang.spring.starter.wxjava.channel.configuration.services.WxChannelInRedissonConfiguration; +import com.binarywang.spring.starter.wxjava.channel.properties.WxChannelMultiProperties; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; + +/** + * 微信视频号相关服务自动注册 + * + * @author Winnie + * @date 2024/9/13 + */ +@Configuration +@EnableConfigurationProperties(WxChannelMultiProperties.class) +@Import({WxChannelInJedisConfiguration.class, WxChannelInMemoryConfiguration.class, WxChannelInRedissonConfiguration.class, WxChannelInRedisTemplateConfiguration.class}) +public class WxChannelMultiServiceConfiguration {} 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 new file mode 100644 index 0000000000..e2f9f87f5a --- /dev/null +++ b/spring-boot-starters/wx-java-channel-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/channel/configuration/services/AbstractWxChannelConfiguration.java @@ -0,0 +1,148 @@ +package com.binarywang.spring.starter.wxjava.channel.configuration.services; + +import com.binarywang.spring.starter.wxjava.channel.enums.HttpClientType; +import com.binarywang.spring.starter.wxjava.channel.properties.WxChannelMultiProperties; +import com.binarywang.spring.starter.wxjava.channel.properties.WxChannelSingleProperties; +import com.binarywang.spring.starter.wxjava.channel.service.WxChannelMultiServices; +import com.binarywang.spring.starter.wxjava.channel.service.WxChannelMultiServicesImpl; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import me.chanjar.weixin.channel.api.WxChannelService; +import me.chanjar.weixin.channel.api.impl.WxChannelServiceHttpClientImpl; +import me.chanjar.weixin.channel.api.impl.WxChannelServiceHttpComponentsImpl; +import me.chanjar.weixin.channel.api.impl.WxChannelServiceImpl; +import me.chanjar.weixin.channel.config.WxChannelConfig; +import me.chanjar.weixin.channel.config.impl.WxChannelDefaultConfigImpl; +import org.apache.commons.lang3.StringUtils; + +import java.util.Collection; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * WxChannelConfigStorage 抽象配置类 + * + * @author Winnie + * @date 2024/9/13 + */ +@RequiredArgsConstructor +@Slf4j +public abstract class AbstractWxChannelConfiguration { + protected WxChannelMultiServices wxChannelMultiServices(WxChannelMultiProperties wxChannelMultiProperties) { + Map appsMap = wxChannelMultiProperties.getApps(); + if (appsMap == null || appsMap.isEmpty()) { + log.warn("微信视频号应用参数未配置,通过 WxChannelMultiServices#getWxChannelService(\"tenantId\")获取实例将返回空"); + return new WxChannelMultiServicesImpl(); + } + /** + * 校验 appId 是否唯一,避免使用 redis 缓存 token、ticket 时错乱。 + * + * 查看 {@link me.chanjar.weixin.channel.config.impl.WxChannelRedisConfigImpl#setAppid(String)} + */ + Collection apps = appsMap.values(); + if (apps.size() > 1) { + // 校验 appId 是否唯一 + boolean multi = apps.stream() + // 没有 appId,如果不判断是否为空,这里会报 NPE 异常 + .collect(Collectors.groupingBy(c -> c.getAppId() == null ? 0 : c.getAppId(), Collectors.counting())) + .entrySet().stream().anyMatch(e -> e.getValue() > 1); + if (multi) { + throw new RuntimeException("请确保微信视频号配置 appId 的唯一性"); + } + } + WxChannelMultiServicesImpl services = new WxChannelMultiServicesImpl(); + + Set> entries = appsMap.entrySet(); + for (Map.Entry entry : entries) { + String tenantId = entry.getKey(); + WxChannelSingleProperties wxChannelSingleProperties = entry.getValue(); + WxChannelDefaultConfigImpl storage = this.wxChannelConfigStorage(wxChannelMultiProperties); + this.configApp(storage, wxChannelSingleProperties); + this.configHttp(storage, wxChannelMultiProperties.getConfigStorage()); + WxChannelService wxChannelService = this.wxChannelService(storage, wxChannelMultiProperties); + services.addWxChannelService(tenantId, wxChannelService); + } + return services; + } + + /** + * 配置 WxChannelDefaultConfigImpl + * + * @param wxChannelMultiProperties 参数 + * @return WxChannelDefaultConfigImpl + */ + protected abstract WxChannelDefaultConfigImpl wxChannelConfigStorage(WxChannelMultiProperties wxChannelMultiProperties); + + public WxChannelService wxChannelService(WxChannelConfig wxChannelConfig, WxChannelMultiProperties wxChannelMultiProperties) { + WxChannelMultiProperties.ConfigStorage storage = wxChannelMultiProperties.getConfigStorage(); + HttpClientType httpClientType = storage.getHttpClientType(); + WxChannelService wxChannelService; + switch (httpClientType) { +// case OK_HTTP: +// wxChannelService = new WxChannelServiceOkHttpImpl(false, false); +// break; + case HTTP_CLIENT: + wxChannelService = new WxChannelServiceHttpClientImpl(); + break; + case HTTP_COMPONENTS: + wxChannelService = new WxChannelServiceHttpComponentsImpl(); + break; + default: + wxChannelService = new WxChannelServiceImpl(); + break; + } + + wxChannelService.setConfig(wxChannelConfig); + int maxRetryTimes = storage.getMaxRetryTimes(); + if (maxRetryTimes < 0) { + maxRetryTimes = 0; + } + int retrySleepMillis = storage.getRetrySleepMillis(); + if (retrySleepMillis < 0) { + retrySleepMillis = 1000; + } + wxChannelService.setRetrySleepMillis(retrySleepMillis); + wxChannelService.setMaxRetryTimes(maxRetryTimes); + return wxChannelService; + } + + private void configApp(WxChannelDefaultConfigImpl config, WxChannelSingleProperties wxChannelSingleProperties) { + String appId = wxChannelSingleProperties.getAppId(); + String appSecret = wxChannelSingleProperties.getSecret(); + String token = wxChannelSingleProperties.getToken(); + String aesKey = wxChannelSingleProperties.getAesKey(); + boolean useStableAccessToken = wxChannelSingleProperties.isUseStableAccessToken(); + + config.setAppid(appId); + config.setSecret(appSecret); + if (StringUtils.isNotBlank(token)) { + config.setToken(token); + } + if (StringUtils.isNotBlank(aesKey)) { + 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) { + 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-channel-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/channel/configuration/services/WxChannelInJedisConfiguration.java b/spring-boot-starters/wx-java-channel-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/channel/configuration/services/WxChannelInJedisConfiguration.java new file mode 100644 index 0000000000..d19b0fc4b5 --- /dev/null +++ b/spring-boot-starters/wx-java-channel-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/channel/configuration/services/WxChannelInJedisConfiguration.java @@ -0,0 +1,74 @@ +package com.binarywang.spring.starter.wxjava.channel.configuration.services; + +import com.binarywang.spring.starter.wxjava.channel.properties.WxChannelMultiProperties; +import com.binarywang.spring.starter.wxjava.channel.properties.WxChannelMultiRedisProperties; +import com.binarywang.spring.starter.wxjava.channel.service.WxChannelMultiServices; +import lombok.RequiredArgsConstructor; +import me.chanjar.weixin.channel.config.impl.WxChannelDefaultConfigImpl; +import me.chanjar.weixin.channel.config.impl.WxChannelRedisConfigImpl; +import me.chanjar.weixin.common.redis.JedisWxRedisOps; +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 Winnie + * @date 2024/9/13 + */ +@Configuration +@ConditionalOnProperty(prefix = WxChannelMultiProperties.PREFIX + ".config-storage", name = "type", havingValue = "jedis") +@RequiredArgsConstructor +public class WxChannelInJedisConfiguration extends AbstractWxChannelConfiguration { + private final WxChannelMultiProperties wxChannelMultiProperties; + private final ApplicationContext applicationContext; + + @Bean + public WxChannelMultiServices wxChannelMultiServices() { + return this.wxChannelMultiServices(wxChannelMultiProperties); + } + + @Override + protected WxChannelDefaultConfigImpl wxChannelConfigStorage(WxChannelMultiProperties wxChannelMultiProperties) { + return this.configRedis(wxChannelMultiProperties); + } + + private WxChannelDefaultConfigImpl configRedis(WxChannelMultiProperties wxChannelMultiProperties) { + WxChannelMultiRedisProperties wxChannelMultiRedisProperties = wxChannelMultiProperties.getConfigStorage().getRedis(); + JedisPool jedisPool; + if (wxChannelMultiRedisProperties != null && StringUtils.isNotEmpty(wxChannelMultiRedisProperties.getHost())) { + jedisPool = getJedisPool(wxChannelMultiProperties); + } else { + jedisPool = applicationContext.getBean(JedisPool.class); + } + return new WxChannelRedisConfigImpl(new JedisWxRedisOps(jedisPool), wxChannelMultiProperties.getConfigStorage().getKeyPrefix()); + } + + private JedisPool getJedisPool(WxChannelMultiProperties wxChannelMultiProperties) { + WxChannelMultiProperties.ConfigStorage storage = wxChannelMultiProperties.getConfigStorage(); + WxChannelMultiRedisProperties 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-channel-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/channel/configuration/services/WxChannelInMemoryConfiguration.java b/spring-boot-starters/wx-java-channel-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/channel/configuration/services/WxChannelInMemoryConfiguration.java new file mode 100644 index 0000000000..77bb221f25 --- /dev/null +++ b/spring-boot-starters/wx-java-channel-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/channel/configuration/services/WxChannelInMemoryConfiguration.java @@ -0,0 +1,36 @@ +package com.binarywang.spring.starter.wxjava.channel.configuration.services; + +import com.binarywang.spring.starter.wxjava.channel.properties.WxChannelMultiProperties; +import com.binarywang.spring.starter.wxjava.channel.service.WxChannelMultiServices; +import lombok.RequiredArgsConstructor; +import me.chanjar.weixin.channel.config.impl.WxChannelDefaultConfigImpl; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * 自动装配基于内存策略配置 + * + * @author Winnie + * @date 2024/9/13 + */ +@Configuration +@ConditionalOnProperty(prefix = WxChannelMultiProperties.PREFIX + ".config-storage", name = "type", havingValue = "memory", matchIfMissing = true) +@RequiredArgsConstructor +public class WxChannelInMemoryConfiguration extends AbstractWxChannelConfiguration { + private final WxChannelMultiProperties wxChannelMultiProperties; + + @Bean + public WxChannelMultiServices wxChannelMultiServices() { + return this.wxChannelMultiServices(wxChannelMultiProperties); + } + + @Override + protected WxChannelDefaultConfigImpl wxChannelConfigStorage(WxChannelMultiProperties wxChannelMultiProperties) { + return this.configInMemory(); + } + + private WxChannelDefaultConfigImpl configInMemory() { + return new WxChannelDefaultConfigImpl(); + } +} diff --git a/spring-boot-starters/wx-java-channel-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/channel/configuration/services/WxChannelInRedisTemplateConfiguration.java b/spring-boot-starters/wx-java-channel-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/channel/configuration/services/WxChannelInRedisTemplateConfiguration.java new file mode 100644 index 0000000000..f9defdb94a --- /dev/null +++ b/spring-boot-starters/wx-java-channel-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/channel/configuration/services/WxChannelInRedisTemplateConfiguration.java @@ -0,0 +1,42 @@ +package com.binarywang.spring.starter.wxjava.channel.configuration.services; + +import com.binarywang.spring.starter.wxjava.channel.properties.WxChannelMultiProperties; +import com.binarywang.spring.starter.wxjava.channel.service.WxChannelMultiServices; +import lombok.RequiredArgsConstructor; +import me.chanjar.weixin.channel.config.impl.WxChannelDefaultConfigImpl; +import me.chanjar.weixin.channel.config.impl.WxChannelRedisConfigImpl; +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 Winnie + * @date 2024/9/13 + */ +@Configuration +@ConditionalOnProperty(prefix = WxChannelMultiProperties.PREFIX + ".config-storage", name = "type", havingValue = "redis_template") +@RequiredArgsConstructor +public class WxChannelInRedisTemplateConfiguration extends AbstractWxChannelConfiguration { + private final WxChannelMultiProperties wxChannelMultiProperties; + private final ApplicationContext applicationContext; + + @Bean + public WxChannelMultiServices wxChannelMultiServices() { + return this.wxChannelMultiServices(wxChannelMultiProperties); + } + + @Override + protected WxChannelDefaultConfigImpl wxChannelConfigStorage(WxChannelMultiProperties wxChannelMultiProperties) { + return this.configRedisTemplate(wxChannelMultiProperties); + } + + private WxChannelDefaultConfigImpl configRedisTemplate(WxChannelMultiProperties wxChannelMultiProperties) { + StringRedisTemplate redisTemplate = applicationContext.getBean(StringRedisTemplate.class); + return new WxChannelRedisConfigImpl(new RedisTemplateWxRedisOps(redisTemplate), wxChannelMultiProperties.getConfigStorage().getKeyPrefix()); + } +} diff --git a/spring-boot-starters/wx-java-channel-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/channel/configuration/services/WxChannelInRedissonConfiguration.java b/spring-boot-starters/wx-java-channel-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/channel/configuration/services/WxChannelInRedissonConfiguration.java new file mode 100644 index 0000000000..fa4798a18b --- /dev/null +++ b/spring-boot-starters/wx-java-channel-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/channel/configuration/services/WxChannelInRedissonConfiguration.java @@ -0,0 +1,62 @@ +package com.binarywang.spring.starter.wxjava.channel.configuration.services; + +import com.binarywang.spring.starter.wxjava.channel.properties.WxChannelMultiProperties; +import com.binarywang.spring.starter.wxjava.channel.properties.WxChannelMultiRedisProperties; +import com.binarywang.spring.starter.wxjava.channel.service.WxChannelMultiServices; +import lombok.RequiredArgsConstructor; +import me.chanjar.weixin.channel.config.impl.WxChannelDefaultConfigImpl; +import me.chanjar.weixin.channel.config.impl.WxChannelRedissonConfigImpl; +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 Winnie + * @date 2024/9/13 + */ +@Configuration +@ConditionalOnProperty(prefix = WxChannelMultiProperties.PREFIX + ".config-storage", name = "type", havingValue = "redisson") +@RequiredArgsConstructor +public class WxChannelInRedissonConfiguration extends AbstractWxChannelConfiguration { + private final WxChannelMultiProperties wxChannelMultiProperties; + private final ApplicationContext applicationContext; + + @Bean + public WxChannelMultiServices wxChannelMultiServices() { + return this.wxChannelMultiServices(wxChannelMultiProperties); + } + + @Override + protected WxChannelDefaultConfigImpl wxChannelConfigStorage(WxChannelMultiProperties wxChannelMultiProperties) { + return this.configRedisson(wxChannelMultiProperties); + } + + private WxChannelDefaultConfigImpl configRedisson(WxChannelMultiProperties wxChannelMultiProperties) { + WxChannelMultiRedisProperties redisProperties = wxChannelMultiProperties.getConfigStorage().getRedis(); + RedissonClient redissonClient; + if (redisProperties != null && StringUtils.isNotEmpty(redisProperties.getHost())) { + redissonClient = getRedissonClient(wxChannelMultiProperties); + } else { + redissonClient = applicationContext.getBean(RedissonClient.class); + } + return new WxChannelRedissonConfigImpl(redissonClient, wxChannelMultiProperties.getConfigStorage().getKeyPrefix()); + } + + private RedissonClient getRedissonClient(WxChannelMultiProperties wxChannelMultiProperties) { + WxChannelMultiProperties.ConfigStorage storage = wxChannelMultiProperties.getConfigStorage(); + WxChannelMultiRedisProperties 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-channel-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/channel/enums/HttpClientType.java b/spring-boot-starters/wx-java-channel-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/channel/enums/HttpClientType.java new file mode 100644 index 0000000000..adc8a2b52b --- /dev/null +++ b/spring-boot-starters/wx-java-channel-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/channel/enums/HttpClientType.java @@ -0,0 +1,23 @@ +package com.binarywang.spring.starter.wxjava.channel.enums; + +/** + * httpclient类型 + * + * @author Winnie + * @date 2024/9/13 + */ +public enum HttpClientType { + /** + * HttpClient. + */ + HTTP_CLIENT, + // WxChannelServiceOkHttpImpl 实现经测试无法正常完成业务固暂不支持OK_HTTP方式 +// /** +// * OkHttp. +// */ +// OK_HTTP, + /** + * HttpComponents. + */ + HTTP_COMPONENTS, +} diff --git a/spring-boot-starters/wx-java-channel-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/channel/enums/StorageType.java b/spring-boot-starters/wx-java-channel-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/channel/enums/StorageType.java new file mode 100644 index 0000000000..0ee69eca73 --- /dev/null +++ b/spring-boot-starters/wx-java-channel-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/channel/enums/StorageType.java @@ -0,0 +1,26 @@ +package com.binarywang.spring.starter.wxjava.channel.enums; + +/** + * storage类型 + * + * @author Winnie + * @date 2024/9/13 + */ +public enum StorageType { + /** + * 内存 + */ + MEMORY, + /** + * redis(JedisClient) + */ + JEDIS, + /** + * redis(Redisson) + */ + REDISSON, + /** + * redis(RedisTemplate) + */ + REDIS_TEMPLATE +} diff --git a/spring-boot-starters/wx-java-channel-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/channel/properties/WxChannelMultiProperties.java b/spring-boot-starters/wx-java-channel-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/channel/properties/WxChannelMultiProperties.java new file mode 100644 index 0000000000..d22f560282 --- /dev/null +++ b/spring-boot-starters/wx-java-channel-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/channel/properties/WxChannelMultiProperties.java @@ -0,0 +1,96 @@ +package com.binarywang.spring.starter.wxjava.channel.properties; + +import com.binarywang.spring.starter.wxjava.channel.enums.HttpClientType; +import com.binarywang.spring.starter.wxjava.channel.enums.StorageType; +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 Winnie + * @date 2024/9/13 + */ +@Data +@NoArgsConstructor +@ConfigurationProperties(WxChannelMultiProperties.PREFIX) +public class WxChannelMultiProperties implements Serializable { + private static final long serialVersionUID = - 8361973118805546037L; + public static final String PREFIX = "wx.channel"; + + private Map apps = new HashMap<>(); + + /** + * 存储策略 + */ + private final ConfigStorage configStorage = new ConfigStorage(); + + @Data + @NoArgsConstructor + public static class ConfigStorage implements Serializable { + private static final long serialVersionUID = - 5152619132544179942L; + + /** + * 存储类型. + */ + private StorageType type = StorageType.MEMORY; + + /** + * 指定key前缀. + */ + private String keyPrefix = "wx:channel:multi"; + + /** + * redis连接配置. + */ + @NestedConfigurationProperty + private final WxChannelMultiRedisProperties redis = new WxChannelMultiRedisProperties(); + + /** + * 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.channel.api.WxChannelService#setMaxRetryTimes(int)}

+ *

{@link me.chanjar.weixin.channel.api.impl.BaseWxChannelServiceImpl#setMaxRetryTimes(int)}

+ */ + private int maxRetryTimes = 5; + + /** + * http 请求重试间隔 + * + *

{@link me.chanjar.weixin.channel.api.WxChannelService#setRetrySleepMillis(int)}

+ *

{@link me.chanjar.weixin.channel.api.impl.BaseWxChannelServiceImpl#setRetrySleepMillis(int)}

+ */ + private int retrySleepMillis = 1000; + } +} diff --git a/spring-boot-starters/wx-java-channel-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/channel/properties/WxChannelMultiRedisProperties.java b/spring-boot-starters/wx-java-channel-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/channel/properties/WxChannelMultiRedisProperties.java new file mode 100644 index 0000000000..99c426765c --- /dev/null +++ b/spring-boot-starters/wx-java-channel-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/channel/properties/WxChannelMultiRedisProperties.java @@ -0,0 +1,63 @@ +package com.binarywang.spring.starter.wxjava.channel.properties; + +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * Redis配置 + * + * @author Winnie + * @date 2024/9/13 + */ +@Data +@NoArgsConstructor +public class WxChannelMultiRedisProperties implements Serializable { + private static final long serialVersionUID = 9061055444734277357L; + + /** + * 主机地址. + */ + private String host = "127.0.0.1"; + + /** + * 端口号. + */ + private int port = 6379; + + /** + * 密码. + */ + private String password; + + /** + * 超时. + */ + private int timeout = 2000; + + /** + * 数据库. + */ + private int database = 0; + + /** + * 最大活动连接数 + */ + private Integer maxActive; + + /** + * 最大空闲连接数 + */ + private Integer maxIdle; + + /** + * 最小空闲连接数 + */ + private Integer minIdle; + + /** + * 最大等待时间 + */ + private Integer maxWaitMillis; +} 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 new file mode 100644 index 0000000000..4b613e3bf6 --- /dev/null +++ b/spring-boot-starters/wx-java-channel-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/channel/properties/WxChannelSingleProperties.java @@ -0,0 +1,55 @@ +package com.binarywang.spring.starter.wxjava.channel.properties; + +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * 微信视频号相关配置属性 + * + * @author Winnie + * @date 2024/9/13 + */ +@Data +@NoArgsConstructor +public class WxChannelSingleProperties implements Serializable { + private static final long serialVersionUID = 5306630351265124825L; + + /** + * 设置微信视频号的 appid. + */ + private String appId; + + /** + * 设置微信视频号的 secret. + */ + private String secret; + + /** + * 设置微信视频号的 token. + */ + private String token; + + /** + * 设置微信视频号的 EncodingAESKey. + */ + private String aesKey; + + /** + * 是否使用稳定版 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-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/channel/service/WxChannelMultiServices.java b/spring-boot-starters/wx-java-channel-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/channel/service/WxChannelMultiServices.java new file mode 100644 index 0000000000..acd4ebf20b --- /dev/null +++ b/spring-boot-starters/wx-java-channel-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/channel/service/WxChannelMultiServices.java @@ -0,0 +1,26 @@ +package com.binarywang.spring.starter.wxjava.channel.service; + +import me.chanjar.weixin.channel.api.WxChannelService; + +/** + * 视频号 {@link WxChannelService} 所有实例存放类. + * + * @author Winnie + * @date 2024/9/13 + */ +public interface WxChannelMultiServices { + /** + * 通过租户 Id 获取 WxChannelService + * + * @param tenantId 租户 Id + * @return WxChannelService + */ + WxChannelService getWxChannelService(String tenantId); + + /** + * 根据租户 Id,从列表中移除一个 WxChannelService 实例 + * + * @param tenantId 租户 Id + */ + void removeWxChannelService(String tenantId); +} diff --git a/spring-boot-starters/wx-java-channel-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/channel/service/WxChannelMultiServicesImpl.java b/spring-boot-starters/wx-java-channel-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/channel/service/WxChannelMultiServicesImpl.java new file mode 100644 index 0000000000..1673289cb5 --- /dev/null +++ b/spring-boot-starters/wx-java-channel-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/channel/service/WxChannelMultiServicesImpl.java @@ -0,0 +1,36 @@ +package com.binarywang.spring.starter.wxjava.channel.service; + +import me.chanjar.weixin.channel.api.WxChannelService; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * 视频号 {@link WxChannelMultiServices} 实现 + * + * @author Winnie + * @date 2024/9/13 + */ +public class WxChannelMultiServicesImpl implements WxChannelMultiServices { + private final Map services = new ConcurrentHashMap<>(); + + @Override + public WxChannelService getWxChannelService(String tenantId) { + return this.services.get(tenantId); + } + + /** + * 根据租户 Id,添加一个 WxChannelService 到列表 + * + * @param tenantId 租户 Id + * @param wxChannelService WxChannelService 实例 + */ + public void addWxChannelService(String tenantId, WxChannelService wxChannelService) { + this.services.put(tenantId, wxChannelService); + } + + @Override + public void removeWxChannelService(String tenantId) { + this.services.remove(tenantId); + } +} diff --git a/spring-boot-starters/wx-java-channel-multi-spring-boot-starter/src/main/resources/META-INF/spring.factories b/spring-boot-starters/wx-java-channel-multi-spring-boot-starter/src/main/resources/META-INF/spring.factories new file mode 100644 index 0000000000..2c5a939c32 --- /dev/null +++ b/spring-boot-starters/wx-java-channel-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.channel.autoconfigure.WxChannelMultiAutoConfiguration diff --git a/spring-boot-starters/wx-java-channel-multi-spring-boot-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/spring-boot-starters/wx-java-channel-multi-spring-boot-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 0000000000..d21a2cdc8d --- /dev/null +++ b/spring-boot-starters/wx-java-channel-multi-spring-boot-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1 @@ +com.binarywang.spring.starter.wxjava.channel.autoconfigure.WxChannelMultiAutoConfiguration 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 new file mode 100644 index 0000000000..398001a286 --- /dev/null +++ b/spring-boot-starters/wx-java-channel-spring-boot-starter/README.md @@ -0,0 +1,103 @@ +# wx-java-channel-spring-boot-starter + +## 快速开始 +1. 引入依赖 + ```xml + + + com.github.binarywang + wx-java-channel-spring-boot-starter + ${version} + + + + + redis.clients + jedis + ${jedis.version} + + + + + org.redisson + redisson + ${redisson.version} + + + + + org.springframework.boot + spring-boot-starter-data-redis + + + ``` +2. 添加配置(application.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 + + # redis_template 方式使用spring data redis配置 + spring.data.redis.database=0 + spring.data.redis.host=127.0.0.1 + spring.data.redis.password=123456 + spring.data.redis.port=6379 + + # 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.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +@Service +public class DemoService { + @Autowired + private WxChannelService wxChannelService; + + public String getShopInfo() throws WxErrorException { + // 获取店铺基本信息 + ShopInfoResponse response = wxChannelService.getBasicService().getShopInfo(); + // 此处为演示,如果要返回response的结果,建议自己封装一个VO,避免直接返回response + return JsonUtils.encode(response); + } +} +``` + 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 new file mode 100644 index 0000000000..95021e2d22 --- /dev/null +++ b/spring-boot-starters/wx-java-channel-spring-boot-starter/pom.xml @@ -0,0 +1,59 @@ + + + wx-java-spring-boot-starters + com.github.binarywang + 4.8.0 + + 4.0.0 + + wx-java-channel-spring-boot-starter + WxJava - Spring Boot Starter for Channel + 微信视频号开发的 Spring Boot Starter + + + + com.github.binarywang + weixin-java-channel + ${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-channel-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/channel/config/WxChannelAutoConfiguration.java b/spring-boot-starters/wx-java-channel-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/channel/config/WxChannelAutoConfiguration.java new file mode 100644 index 0000000000..ad9d90b28d --- /dev/null +++ b/spring-boot-starters/wx-java-channel-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/channel/config/WxChannelAutoConfiguration.java @@ -0,0 +1,20 @@ +package com.binarywang.spring.starter.wxjava.channel.config; + +import com.binarywang.spring.starter.wxjava.channel.properties.WxChannelProperties; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; + +/** + * 自动配置 + * + * @author Zeyes + */ +@Configuration +@EnableConfigurationProperties(WxChannelProperties.class) +@Import({ + WxChannelStorageAutoConfiguration.class, + WxChannelServiceAutoConfiguration.class +}) +public class WxChannelAutoConfiguration { +} diff --git a/spring-boot-starters/wx-java-channel-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/channel/config/WxChannelServiceAutoConfiguration.java b/spring-boot-starters/wx-java-channel-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/channel/config/WxChannelServiceAutoConfiguration.java new file mode 100644 index 0000000000..5276a803e7 --- /dev/null +++ b/spring-boot-starters/wx-java-channel-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/channel/config/WxChannelServiceAutoConfiguration.java @@ -0,0 +1,37 @@ +package com.binarywang.spring.starter.wxjava.channel.config; + + +import com.binarywang.spring.starter.wxjava.channel.properties.WxChannelProperties; +import lombok.AllArgsConstructor; +import me.chanjar.weixin.channel.api.WxChannelService; +import me.chanjar.weixin.channel.api.impl.WxChannelServiceImpl; +import me.chanjar.weixin.channel.config.WxChannelConfig; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * 微信小程序平台相关服务自动注册 + * + * @author Zeyes + */ +@Configuration +@AllArgsConstructor +public class WxChannelServiceAutoConfiguration { + private final WxChannelProperties properties; + + /** + * Channel Service + * + * @return Channel Service + */ + @Bean + @ConditionalOnMissingBean(WxChannelService.class) + @ConditionalOnBean(WxChannelConfig.class) + public WxChannelService wxChannelService(WxChannelConfig wxChannelConfig) { + WxChannelService wxChannelService = new WxChannelServiceImpl(); + wxChannelService.setConfig(wxChannelConfig); + return wxChannelService; + } +} diff --git a/spring-boot-starters/wx-java-channel-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/channel/config/WxChannelStorageAutoConfiguration.java b/spring-boot-starters/wx-java-channel-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/channel/config/WxChannelStorageAutoConfiguration.java new file mode 100644 index 0000000000..66f2276a35 --- /dev/null +++ b/spring-boot-starters/wx-java-channel-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/channel/config/WxChannelStorageAutoConfiguration.java @@ -0,0 +1,23 @@ +package com.binarywang.spring.starter.wxjava.channel.config; + +import com.binarywang.spring.starter.wxjava.channel.config.storage.WxChannelInJedisConfigStorageConfiguration; +import com.binarywang.spring.starter.wxjava.channel.config.storage.WxChannelInMemoryConfigStorageConfiguration; +import com.binarywang.spring.starter.wxjava.channel.config.storage.WxChannelInRedisTemplateConfigStorageConfiguration; +import com.binarywang.spring.starter.wxjava.channel.config.storage.WxChannelInRedissonConfigStorageConfiguration; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; + +/** + * 微信小程序存储策略自动配置 + * + * @author Zeyes + */ +@Configuration +@Import({ + WxChannelInMemoryConfigStorageConfiguration.class, + WxChannelInJedisConfigStorageConfiguration.class, + WxChannelInRedisTemplateConfigStorageConfiguration.class, + WxChannelInRedissonConfigStorageConfiguration.class +}) +public class WxChannelStorageAutoConfiguration { +} 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 new file mode 100644 index 0000000000..2a7978640d --- /dev/null +++ b/spring-boot-starters/wx-java-channel-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/channel/config/storage/AbstractWxChannelConfigStorageConfiguration.java @@ -0,0 +1,42 @@ +package com.binarywang.spring.starter.wxjava.channel.config.storage; + +import com.binarywang.spring.starter.wxjava.channel.properties.WxChannelProperties; +import me.chanjar.weixin.channel.config.impl.WxChannelDefaultConfigImpl; +import org.apache.commons.lang3.StringUtils; + +/** + * @author Zeyes + */ +public abstract class AbstractWxChannelConfigStorageConfiguration { + + protected WxChannelDefaultConfigImpl config(WxChannelDefaultConfigImpl config, WxChannelProperties properties) { + 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.setStableAccessToken(properties.isUseStableAccessToken()); + config.setApiHostUrl(StringUtils.trimToNull(properties.getApiHostUrl())); + config.setAccessTokenUrl(StringUtils.trimToNull(properties.getAccessTokenUrl())); + + WxChannelProperties.ConfigStorage configStorageProperties = properties.getConfigStorage(); + config.setHttpProxyHost(configStorageProperties.getHttpProxyHost()); + config.setHttpProxyUsername(configStorageProperties.getHttpProxyUsername()); + config.setHttpProxyPassword(configStorageProperties.getHttpProxyPassword()); + if (configStorageProperties.getHttpProxyPort() != null) { + config.setHttpProxyPort(configStorageProperties.getHttpProxyPort()); + } + + int maxRetryTimes = configStorageProperties.getMaxRetryTimes(); + if (configStorageProperties.getMaxRetryTimes() < 0) { + maxRetryTimes = 0; + } + int retrySleepMillis = configStorageProperties.getRetrySleepMillis(); + if (retrySleepMillis < 0) { + retrySleepMillis = 1000; + } + config.setRetrySleepMillis(retrySleepMillis); + config.setMaxRetryTimes(maxRetryTimes); + return config; + } +} diff --git a/spring-boot-starters/wx-java-channel-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/channel/config/storage/WxChannelInJedisConfigStorageConfiguration.java b/spring-boot-starters/wx-java-channel-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/channel/config/storage/WxChannelInJedisConfigStorageConfiguration.java new file mode 100644 index 0000000000..f88548c3e9 --- /dev/null +++ b/spring-boot-starters/wx-java-channel-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/channel/config/storage/WxChannelInJedisConfigStorageConfiguration.java @@ -0,0 +1,73 @@ +package com.binarywang.spring.starter.wxjava.channel.config.storage; + + +import com.binarywang.spring.starter.wxjava.channel.properties.RedisProperties; +import com.binarywang.spring.starter.wxjava.channel.properties.WxChannelProperties; +import lombok.RequiredArgsConstructor; +import me.chanjar.weixin.channel.config.WxChannelConfig; +import me.chanjar.weixin.channel.config.impl.WxChannelRedisConfigImpl; +import me.chanjar.weixin.common.redis.JedisWxRedisOps; +import me.chanjar.weixin.common.redis.WxRedisOps; +import org.apache.commons.lang3.StringUtils; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import redis.clients.jedis.JedisPool; +import redis.clients.jedis.JedisPoolConfig; + +/** + * @author Zeyes + */ +@Configuration +@ConditionalOnProperty(prefix = WxChannelProperties.PREFIX + ".config-storage", name = "type", havingValue = "jedis") +@ConditionalOnClass({JedisPool.class, JedisPoolConfig.class}) +@RequiredArgsConstructor +public class WxChannelInJedisConfigStorageConfiguration extends AbstractWxChannelConfigStorageConfiguration { + private final WxChannelProperties properties; + private final ApplicationContext applicationContext; + + @Bean + @ConditionalOnMissingBean(WxChannelConfig.class) + public WxChannelConfig wxChannelConfig() { + WxChannelRedisConfigImpl config = getWxChannelRedisConfig(); + return this.config(config, properties); + } + + private WxChannelRedisConfigImpl getWxChannelRedisConfig() { + RedisProperties redisProperties = properties.getConfigStorage().getRedis(); + JedisPool jedisPool; + if (redisProperties != null && StringUtils.isNotEmpty(redisProperties.getHost())) { + jedisPool = getJedisPool(); + } else { + jedisPool = applicationContext.getBean(JedisPool.class); + } + WxRedisOps redisOps = new JedisWxRedisOps(jedisPool); + return new WxChannelRedisConfigImpl(redisOps, properties.getConfigStorage().getKeyPrefix()); + } + + private JedisPool getJedisPool() { + WxChannelProperties.ConfigStorage storage = properties.getConfigStorage(); + RedisProperties redis = storage.getRedis(); + + JedisPoolConfig config = new JedisPoolConfig(); + if (redis.getMaxActive() != null) { + config.setMaxTotal(redis.getMaxActive()); + } + if (redis.getMaxIdle() != null) { + config.setMaxIdle(redis.getMaxIdle()); + } + if (redis.getMaxWaitMillis() != null) { + config.setMaxWaitMillis(redis.getMaxWaitMillis()); + } + if (redis.getMinIdle() != null) { + config.setMinIdle(redis.getMinIdle()); + } + config.setTestOnBorrow(true); + config.setTestWhileIdle(true); + + return new JedisPool(config, redis.getHost(), redis.getPort(), redis.getTimeout(), redis.getPassword(), redis.getDatabase()); + } +} diff --git a/spring-boot-starters/wx-java-channel-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/channel/config/storage/WxChannelInMemoryConfigStorageConfiguration.java b/spring-boot-starters/wx-java-channel-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/channel/config/storage/WxChannelInMemoryConfigStorageConfiguration.java new file mode 100644 index 0000000000..deb586ae7b --- /dev/null +++ b/spring-boot-starters/wx-java-channel-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/channel/config/storage/WxChannelInMemoryConfigStorageConfiguration.java @@ -0,0 +1,29 @@ +package com.binarywang.spring.starter.wxjava.channel.config.storage; + + +import com.binarywang.spring.starter.wxjava.channel.properties.WxChannelProperties; +import lombok.RequiredArgsConstructor; +import me.chanjar.weixin.channel.config.WxChannelConfig; +import me.chanjar.weixin.channel.config.impl.WxChannelDefaultConfigImpl; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * @author Zeyes + */ +@Configuration +@ConditionalOnProperty(prefix = WxChannelProperties.PREFIX + ".config-storage", name = "type", + matchIfMissing = true, havingValue = "memory") +@RequiredArgsConstructor +public class WxChannelInMemoryConfigStorageConfiguration extends AbstractWxChannelConfigStorageConfiguration { + private final WxChannelProperties properties; + + @Bean + @ConditionalOnMissingBean(WxChannelProperties.class) + public WxChannelConfig wxChannelConfig() { + WxChannelDefaultConfigImpl config = new WxChannelDefaultConfigImpl(); + return this.config(config, properties); + } +} diff --git a/spring-boot-starters/wx-java-channel-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/channel/config/storage/WxChannelInRedisTemplateConfigStorageConfiguration.java b/spring-boot-starters/wx-java-channel-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/channel/config/storage/WxChannelInRedisTemplateConfigStorageConfiguration.java new file mode 100644 index 0000000000..e190fbd755 --- /dev/null +++ b/spring-boot-starters/wx-java-channel-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/channel/config/storage/WxChannelInRedisTemplateConfigStorageConfiguration.java @@ -0,0 +1,40 @@ +package com.binarywang.spring.starter.wxjava.channel.config.storage; + +import com.binarywang.spring.starter.wxjava.channel.properties.WxChannelProperties; +import lombok.RequiredArgsConstructor; +import me.chanjar.weixin.channel.config.WxChannelConfig; +import me.chanjar.weixin.channel.config.impl.WxChannelRedisConfigImpl; +import me.chanjar.weixin.common.redis.RedisTemplateWxRedisOps; +import me.chanjar.weixin.common.redis.WxRedisOps; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.core.StringRedisTemplate; + +/** + * @author Zeyes + */ +@Configuration +@ConditionalOnProperty(prefix = WxChannelProperties.PREFIX + ".config-storage", name = "type", havingValue = "redistemplate") +@ConditionalOnClass(StringRedisTemplate.class) +@RequiredArgsConstructor +public class WxChannelInRedisTemplateConfigStorageConfiguration extends AbstractWxChannelConfigStorageConfiguration { + private final WxChannelProperties properties; + private final ApplicationContext applicationContext; + + @Bean + @ConditionalOnMissingBean(WxChannelConfig.class) + public WxChannelConfig wxChannelConfig() { + WxChannelRedisConfigImpl config = getWxChannelInRedisTemplateConfig(); + return this.config(config, properties); + } + + private WxChannelRedisConfigImpl getWxChannelInRedisTemplateConfig() { + StringRedisTemplate redisTemplate = applicationContext.getBean(StringRedisTemplate.class); + WxRedisOps redisOps = new RedisTemplateWxRedisOps(redisTemplate); + return new WxChannelRedisConfigImpl(redisOps, properties.getConfigStorage().getKeyPrefix()); + } +} diff --git a/spring-boot-starters/wx-java-channel-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/channel/config/storage/WxChannelInRedissonConfigStorageConfiguration.java b/spring-boot-starters/wx-java-channel-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/channel/config/storage/WxChannelInRedissonConfigStorageConfiguration.java new file mode 100644 index 0000000000..16db4395a7 --- /dev/null +++ b/spring-boot-starters/wx-java-channel-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/channel/config/storage/WxChannelInRedissonConfigStorageConfiguration.java @@ -0,0 +1,62 @@ +package com.binarywang.spring.starter.wxjava.channel.config.storage; + + +import com.binarywang.spring.starter.wxjava.channel.properties.RedisProperties; +import com.binarywang.spring.starter.wxjava.channel.properties.WxChannelProperties; +import lombok.RequiredArgsConstructor; +import me.chanjar.weixin.channel.config.WxChannelConfig; +import me.chanjar.weixin.channel.config.impl.WxChannelRedissonConfigImpl; +import org.apache.commons.lang3.StringUtils; +import org.redisson.Redisson; +import org.redisson.api.RedissonClient; +import org.redisson.config.Config; +import org.redisson.config.TransportMode; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * @author Zeyes + */ +@Configuration +@ConditionalOnProperty(prefix = WxChannelProperties.PREFIX + ".config-storage", name = "type", havingValue = "redisson") +@ConditionalOnClass({Redisson.class, RedissonClient.class}) +@RequiredArgsConstructor +public class WxChannelInRedissonConfigStorageConfiguration extends AbstractWxChannelConfigStorageConfiguration { + private final WxChannelProperties properties; + private final ApplicationContext applicationContext; + + @Bean + @ConditionalOnMissingBean(WxChannelConfig.class) + public WxChannelConfig wxChannelConfig() { + WxChannelRedissonConfigImpl config = getWxChannelRedissonConfig(); + return this.config(config, properties); + } + + private WxChannelRedissonConfigImpl getWxChannelRedissonConfig() { + RedisProperties redisProperties = properties.getConfigStorage().getRedis(); + RedissonClient redissonClient; + if (redisProperties != null && StringUtils.isNotEmpty(redisProperties.getHost())) { + redissonClient = getRedissonClient(); + } else { + redissonClient = applicationContext.getBean(RedissonClient.class); + } + return new WxChannelRedissonConfigImpl(redissonClient, properties.getConfigStorage().getKeyPrefix()); + } + + private RedissonClient getRedissonClient() { + WxChannelProperties.ConfigStorage storage = properties.getConfigStorage(); + RedisProperties 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-channel-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/channel/enums/HttpClientType.java b/spring-boot-starters/wx-java-channel-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/channel/enums/HttpClientType.java new file mode 100644 index 0000000000..e4b3f3ad16 --- /dev/null +++ b/spring-boot-starters/wx-java-channel-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/channel/enums/HttpClientType.java @@ -0,0 +1,17 @@ +package com.binarywang.spring.starter.wxjava.channel.enums; + +/** + * httpclient类型 + * + * @author Zeyes + */ +public enum HttpClientType { + /** + * HttpClient. + */ + HttpClient, + /** + * HttpComponents. + */ + HttpComponents, +} diff --git a/spring-boot-starters/wx-java-channel-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/channel/enums/StorageType.java b/spring-boot-starters/wx-java-channel-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/channel/enums/StorageType.java new file mode 100644 index 0000000000..59b27fc022 --- /dev/null +++ b/spring-boot-starters/wx-java-channel-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/channel/enums/StorageType.java @@ -0,0 +1,25 @@ +package com.binarywang.spring.starter.wxjava.channel.enums; + +/** + * storage类型 + * + * @author Zeyes + */ +public enum StorageType { + /** + * 内存 + */ + Memory, + /** + * redis(JedisClient) + */ + Jedis, + /** + * redis(Redisson) + */ + Redisson, + /** + * redis(RedisTemplate) + */ + RedisTemplate +} diff --git a/spring-boot-starters/wx-java-channel-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/channel/properties/RedisProperties.java b/spring-boot-starters/wx-java-channel-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/channel/properties/RedisProperties.java new file mode 100644 index 0000000000..19f27d0682 --- /dev/null +++ b/spring-boot-starters/wx-java-channel-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/channel/properties/RedisProperties.java @@ -0,0 +1,42 @@ +package com.binarywang.spring.starter.wxjava.channel.properties; + +import lombok.Data; + +/** + * redis 配置 + * + * @author Zeyes + */ +@Data +public class RedisProperties { + + /** + * 主机地址,不填则从spring容器内获取JedisPool + */ + 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-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 new file mode 100644 index 0000000000..f43d297e0b --- /dev/null +++ b/spring-boot-starters/wx-java-channel-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/channel/properties/WxChannelProperties.java @@ -0,0 +1,126 @@ +package com.binarywang.spring.starter.wxjava.channel.properties; + +import com.binarywang.spring.starter.wxjava.channel.enums.HttpClientType; +import com.binarywang.spring.starter.wxjava.channel.enums.StorageType; +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.NestedConfigurationProperty; + +/** + * 属性配置类 + * + * @author Zeyes + */ +@Data +@ConfigurationProperties(prefix = WxChannelProperties.PREFIX) +public class WxChannelProperties { + public static final String PREFIX = "wx.channel"; + + /** + * 设置视频号小店的appid + */ + private String appid; + + /** + * 设置视频号小店的Secret + */ + private String secret; + + /** + * 设置视频号小店消息服务器配置的token. + */ + private String token; + + /** + * 设置视频号小店消息服务器配置的EncodingAESKey + */ + private String aesKey; + + /** + * 消息格式,XML或者JSON + */ + private String msgDataFormat = "JSON"; + + /** + * 是否使用稳定版 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; + + /** + * 存储策略 + */ + private final ConfigStorage configStorage = new ConfigStorage(); + + @Data + public static class ConfigStorage { + + /** + * 存储类型 + */ + private StorageType type = StorageType.Memory; + + /** + * 指定key前缀 + */ + private String keyPrefix = "wh"; + + /** + * redis连接配置 + */ + @NestedConfigurationProperty + private final RedisProperties redis = new RedisProperties(); + + /** + * http客户端类型 + */ + private HttpClientType httpClientType = HttpClientType.HttpComponents; + + /** + * http代理主机 + */ + private String httpProxyHost; + + /** + * http代理端口 + */ + private Integer httpProxyPort; + + /** + * http代理用户名 + */ + private String httpProxyUsername; + + /** + * http代理密码 + */ + private String httpProxyPassword; + + /** + * http 请求重试间隔 + *
+     *   {@link me.chanjar.weixin.channel.api.BaseWxChannelService#setRetrySleepMillis(int)}
+     * 
+ */ + private int retrySleepMillis = 1000; + /** + * http 请求最大重试次数 + *
+     *   {@link me.chanjar.weixin.channel.api.BaseWxChannelService#setMaxRetryTimes(int)}
+     * 
+ */ + private int maxRetryTimes = 5; + } + +} diff --git a/spring-boot-starters/wx-java-channel-spring-boot-starter/src/main/resources/META-INF/spring.factories b/spring-boot-starters/wx-java-channel-spring-boot-starter/src/main/resources/META-INF/spring.factories new file mode 100644 index 0000000000..a9401752a0 --- /dev/null +++ b/spring-boot-starters/wx-java-channel-spring-boot-starter/src/main/resources/META-INF/spring.factories @@ -0,0 +1,2 @@ +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ + com.binarywang.spring.starter.wxjava.channel.config.WxChannelAutoConfiguration diff --git a/spring-boot-starters/wx-java-channel-spring-boot-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/spring-boot-starters/wx-java-channel-spring-boot-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 0000000000..99ccbadbbc --- /dev/null +++ b/spring-boot-starters/wx-java-channel-spring-boot-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1 @@ +com.binarywang.spring.starter.wxjava.channel.config.WxChannelAutoConfiguration diff --git a/spring-boot-starters/wx-java-cp-multi-spring-boot-starter/README.md b/spring-boot-starters/wx-java-cp-multi-spring-boot-starter/README.md new file mode 100644 index 0000000000..e3ea7bf0f8 --- /dev/null +++ b/spring-boot-starters/wx-java-cp-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.WxCpMultiServices; +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 WxCpMultiServices wxCpMultiServices; + + 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-multi-spring-boot-starter/pom.xml b/spring-boot-starters/wx-java-cp-multi-spring-boot-starter/pom.xml new file mode 100644 index 0000000000..550a14d2ad --- /dev/null +++ b/spring-boot-starters/wx-java-cp-multi-spring-boot-starter/pom.xml @@ -0,0 +1,60 @@ + + + + wx-java-spring-boot-starters + com.github.binarywang + 4.8.0 + + 4.0.0 + + wx-java-cp-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-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/autoconfigure/WxCpMultiAutoConfiguration.java b/spring-boot-starters/wx-java-cp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/autoconfigure/WxCpMultiAutoConfiguration.java new file mode 100644 index 0000000000..40a6d9048d --- /dev/null +++ b/spring-boot-starters/wx-java-cp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/autoconfigure/WxCpMultiAutoConfiguration.java @@ -0,0 +1,16 @@ +package com.binarywang.spring.starter.wxjava.cp.autoconfigure; + +import com.binarywang.spring.starter.wxjava.cp.configuration.WxCpMultiServicesAutoConfiguration; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; + +/** + * 企业微信自动注册 + * + * @author yl + * created on 2023/10/16 + */ +@Configuration +@Import(WxCpMultiServicesAutoConfiguration.class) +public class WxCpMultiAutoConfiguration { +} diff --git a/spring-boot-starters/wx-java-cp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/configuration/WxCpMultiServicesAutoConfiguration.java b/spring-boot-starters/wx-java-cp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/configuration/WxCpMultiServicesAutoConfiguration.java new file mode 100644 index 0000000000..12a4947301 --- /dev/null +++ b/spring-boot-starters/wx-java-cp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/configuration/WxCpMultiServicesAutoConfiguration.java @@ -0,0 +1,27 @@ +package com.binarywang.spring.starter.wxjava.cp.configuration; + +import com.binarywang.spring.starter.wxjava.cp.configuration.services.WxCpInJedisConfiguration; +import com.binarywang.spring.starter.wxjava.cp.configuration.services.WxCpInMemoryConfiguration; +import com.binarywang.spring.starter.wxjava.cp.configuration.services.WxCpInRedisTemplateConfiguration; +import com.binarywang.spring.starter.wxjava.cp.configuration.services.WxCpInRedissonConfiguration; +import com.binarywang.spring.starter.wxjava.cp.properties.WxCpMultiProperties; +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(WxCpMultiProperties.class) +@Import({ + WxCpInJedisConfiguration.class, + WxCpInMemoryConfiguration.class, + WxCpInRedissonConfiguration.class, + WxCpInRedisTemplateConfiguration.class +}) +public class WxCpMultiServicesAutoConfiguration { +} 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 new file mode 100644 index 0000000000..9b959222e0 --- /dev/null +++ b/spring-boot-starters/wx-java-cp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/configuration/services/AbstractWxCpConfiguration.java @@ -0,0 +1,165 @@ +package com.binarywang.spring.starter.wxjava.cp.configuration.services; + +import com.binarywang.spring.starter.wxjava.cp.properties.WxCpMultiProperties; +import com.binarywang.spring.starter.wxjava.cp.properties.WxCpSingleProperties; +import com.binarywang.spring.starter.wxjava.cp.service.WxCpMultiServices; +import com.binarywang.spring.starter.wxjava.cp.service.WxCpMultiServicesImpl; +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.config.WxCpConfigStorage; +import me.chanjar.weixin.cp.config.impl.WxCpDefaultConfigImpl; +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 AbstractWxCpConfiguration { + + protected WxCpMultiServices wxCpMultiServices(WxCpMultiProperties wxCpMultiProperties) { + Map corps = wxCpMultiProperties.getCorps(); + if (corps == null || corps.isEmpty()) { + log.warn("企业微信应用参数未配置,通过 WxCpMultiServices#getWxCpService(\"tenantId\")获取实例将返回空"); + return new WxCpMultiServicesImpl(); + } + /** + * 校验同一个企业下,agentId 是否唯一,避免使用 redis 缓存 token、ticket 时错乱。 + * + * 查看 {@link me.chanjar.weixin.cp.config.impl.AbstractWxCpInRedisConfigImpl#setAgentId(Integer)} + */ + Collection corpList = corps.values(); + if (corpList.size() > 1) { + // 先按 corpId 分组统计 + Map> corpsMap = corpList.stream() + .collect(Collectors.groupingBy(WxCpSingleProperties::getCorpId)); + Set>> entries = corpsMap.entrySet(); + for (Map.Entry> entry : entries) { + String corpId = entry.getKey(); + // 校验每个企业下,agentId 是否唯一 + boolean multi = entry.getValue().stream() + // 通讯录没有 agentId,如果不判断是否为空,这里会报 NPE 异常 + .collect(Collectors.groupingBy(c -> c.getAgentId() == null ? 0 : c.getAgentId(), Collectors.counting())) + .entrySet().stream().anyMatch(e -> e.getValue() > 1); + if (multi) { + throw new RuntimeException("请确保企业微信配置唯一性[" + corpId + "]"); + } + } + } + WxCpMultiServicesImpl services = new WxCpMultiServicesImpl(); + + Set> entries = corps.entrySet(); + for (Map.Entry entry : entries) { + String tenantId = entry.getKey(); + WxCpSingleProperties wxCpSingleProperties = entry.getValue(); + WxCpDefaultConfigImpl storage = this.wxCpConfigStorage(wxCpMultiProperties); + this.configCorp(storage, wxCpSingleProperties); + this.configHttp(storage, wxCpMultiProperties.getConfigStorage()); + WxCpService wxCpService = this.wxCpService(storage, wxCpMultiProperties.getConfigStorage()); + services.addWxCpService(tenantId, wxCpService); + } + return services; + } + + /** + * 配置 WxCpDefaultConfigImpl + * + * @param wxCpMultiProperties 参数 + * @return WxCpDefaultConfigImpl + */ + protected abstract WxCpDefaultConfigImpl wxCpConfigStorage(WxCpMultiProperties wxCpMultiProperties); + + private WxCpService wxCpService(WxCpConfigStorage wxCpConfigStorage, WxCpMultiProperties.ConfigStorage storage) { + WxCpMultiProperties.HttpClientType httpClientType = storage.getHttpClientType(); + WxCpService wxCpService; + switch (httpClientType) { + case OK_HTTP: + wxCpService = new WxCpServiceOkHttpImpl(); + break; + case JODD_HTTP: + wxCpService = new WxCpServiceJoddHttpImpl(); + break; + case HTTP_CLIENT: + wxCpService = new WxCpServiceApacheHttpClientImpl(); + break; + default: + wxCpService = new WxCpServiceImpl(); + break; + } + wxCpService.setWxCpConfigStorage(wxCpConfigStorage); + int maxRetryTimes = storage.getMaxRetryTimes(); + if (maxRetryTimes < 0) { + maxRetryTimes = 0; + } + int retrySleepMillis = storage.getRetrySleepMillis(); + if (retrySleepMillis < 0) { + retrySleepMillis = 1000; + } + wxCpService.setRetrySleepMillis(retrySleepMillis); + wxCpService.setMaxRetryTimes(maxRetryTimes); + return wxCpService; + } + + private void configCorp(WxCpDefaultConfigImpl config, WxCpSingleProperties wxCpSingleProperties) { + String corpId = wxCpSingleProperties.getCorpId(); + String corpSecret = wxCpSingleProperties.getCorpSecret(); + Integer agentId = wxCpSingleProperties.getAgentId(); + String token = wxCpSingleProperties.getToken(); + String aesKey = wxCpSingleProperties.getAesKey(); + // 企业微信,私钥,会话存档路径 + String msgAuditPriKey = wxCpSingleProperties.getMsgAuditPriKey(); + String msgAuditLibPath = wxCpSingleProperties.getMsgAuditLibPath(); + + config.setCorpId(corpId); + config.setCorpSecret(corpSecret); + config.setAgentId(agentId); + if (StringUtils.isNotBlank(token)) { + config.setToken(token); + } + if (StringUtils.isNotBlank(aesKey)) { + config.setAesKey(aesKey); + } + if (StringUtils.isNotBlank(msgAuditPriKey)) { + config.setMsgAuditPriKey(msgAuditPriKey); + } + if (StringUtils.isNotBlank(msgAuditLibPath)) { + config.setMsgAuditLibPath(msgAuditLibPath); + } + if (StringUtils.isNotBlank(wxCpSingleProperties.getBaseApiUrl())) { + config.setBaseApiUrl(wxCpSingleProperties.getBaseApiUrl()); + } + } + + private void configHttp(WxCpDefaultConfigImpl config, WxCpMultiProperties.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-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/configuration/services/WxCpInJedisConfiguration.java b/spring-boot-starters/wx-java-cp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/configuration/services/WxCpInJedisConfiguration.java new file mode 100644 index 0000000000..e03647cb63 --- /dev/null +++ b/spring-boot-starters/wx-java-cp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/configuration/services/WxCpInJedisConfiguration.java @@ -0,0 +1,76 @@ +package com.binarywang.spring.starter.wxjava.cp.configuration.services; + +import com.binarywang.spring.starter.wxjava.cp.properties.WxCpMultiProperties; +import com.binarywang.spring.starter.wxjava.cp.properties.WxCpMultiRedisProperties; +import com.binarywang.spring.starter.wxjava.cp.service.WxCpMultiServices; +import lombok.RequiredArgsConstructor; +import me.chanjar.weixin.cp.config.impl.WxCpDefaultConfigImpl; +import me.chanjar.weixin.cp.config.impl.WxCpJedisConfigImpl; +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 = WxCpMultiProperties.PREFIX + ".config-storage", name = "type", havingValue = "jedis" +) +@RequiredArgsConstructor +public class WxCpInJedisConfiguration extends AbstractWxCpConfiguration { + private final WxCpMultiProperties wxCpMultiProperties; + private final ApplicationContext applicationContext; + + @Bean + public WxCpMultiServices wxCpMultiServices() { + return this.wxCpMultiServices(wxCpMultiProperties); + } + + @Override + protected WxCpDefaultConfigImpl wxCpConfigStorage(WxCpMultiProperties wxCpMultiProperties) { + return this.configRedis(wxCpMultiProperties); + } + + private WxCpDefaultConfigImpl configRedis(WxCpMultiProperties wxCpMultiProperties) { + WxCpMultiRedisProperties wxCpMultiRedisProperties = wxCpMultiProperties.getConfigStorage().getRedis(); + JedisPool jedisPool; + if (wxCpMultiRedisProperties != null && StringUtils.isNotEmpty(wxCpMultiRedisProperties.getHost())) { + jedisPool = getJedisPool(wxCpMultiProperties); + } else { + jedisPool = applicationContext.getBean(JedisPool.class); + } + return new WxCpJedisConfigImpl(jedisPool, wxCpMultiProperties.getConfigStorage().getKeyPrefix()); + } + + private JedisPool getJedisPool(WxCpMultiProperties wxCpMultiProperties) { + WxCpMultiProperties.ConfigStorage storage = wxCpMultiProperties.getConfigStorage(); + WxCpMultiRedisProperties 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-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/configuration/services/WxCpInMemoryConfiguration.java b/spring-boot-starters/wx-java-cp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/configuration/services/WxCpInMemoryConfiguration.java new file mode 100644 index 0000000000..29593667ed --- /dev/null +++ b/spring-boot-starters/wx-java-cp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/configuration/services/WxCpInMemoryConfiguration.java @@ -0,0 +1,38 @@ +package com.binarywang.spring.starter.wxjava.cp.configuration.services; + +import com.binarywang.spring.starter.wxjava.cp.properties.WxCpMultiProperties; +import com.binarywang.spring.starter.wxjava.cp.service.WxCpMultiServices; +import lombok.RequiredArgsConstructor; +import me.chanjar.weixin.cp.config.impl.WxCpDefaultConfigImpl; +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 = WxCpMultiProperties.PREFIX + ".config-storage", name = "type", havingValue = "memory", matchIfMissing = true +) +@RequiredArgsConstructor +public class WxCpInMemoryConfiguration extends AbstractWxCpConfiguration { + private final WxCpMultiProperties wxCpMultiProperties; + + @Bean + public WxCpMultiServices wxCpMultiServices() { + return this.wxCpMultiServices(wxCpMultiProperties); + } + + @Override + protected WxCpDefaultConfigImpl wxCpConfigStorage(WxCpMultiProperties wxCpMultiProperties) { + return this.configInMemory(); + } + + private WxCpDefaultConfigImpl configInMemory() { + return new WxCpDefaultConfigImpl(); + } +} diff --git a/spring-boot-starters/wx-java-cp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/configuration/services/WxCpInRedisTemplateConfiguration.java b/spring-boot-starters/wx-java-cp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/configuration/services/WxCpInRedisTemplateConfiguration.java new file mode 100644 index 0000000000..374c5cdfb0 --- /dev/null +++ b/spring-boot-starters/wx-java-cp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/configuration/services/WxCpInRedisTemplateConfiguration.java @@ -0,0 +1,43 @@ +package com.binarywang.spring.starter.wxjava.cp.configuration.services; + +import com.binarywang.spring.starter.wxjava.cp.properties.WxCpMultiProperties; +import com.binarywang.spring.starter.wxjava.cp.service.WxCpMultiServices; +import lombok.RequiredArgsConstructor; +import me.chanjar.weixin.cp.config.impl.WxCpDefaultConfigImpl; +import me.chanjar.weixin.cp.config.impl.WxCpRedisTemplateConfigImpl; +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 = WxCpMultiProperties.PREFIX + ".config-storage", name = "type", havingValue = "redistemplate" +) +@RequiredArgsConstructor +public class WxCpInRedisTemplateConfiguration extends AbstractWxCpConfiguration { + private final WxCpMultiProperties wxCpMultiProperties; + private final ApplicationContext applicationContext; + + @Bean + public WxCpMultiServices wxCpMultiServices() { + return this.wxCpMultiServices(wxCpMultiProperties); + } + + @Override + protected WxCpDefaultConfigImpl wxCpConfigStorage(WxCpMultiProperties wxCpMultiProperties) { + return this.configRedisTemplate(wxCpMultiProperties); + } + + private WxCpDefaultConfigImpl configRedisTemplate(WxCpMultiProperties wxCpMultiProperties) { + StringRedisTemplate redisTemplate = applicationContext.getBean(StringRedisTemplate.class); + return new WxCpRedisTemplateConfigImpl(redisTemplate, wxCpMultiProperties.getConfigStorage().getKeyPrefix()); + } +} diff --git a/spring-boot-starters/wx-java-cp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/configuration/services/WxCpInRedissonConfiguration.java b/spring-boot-starters/wx-java-cp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/configuration/services/WxCpInRedissonConfiguration.java new file mode 100644 index 0000000000..c0753a44aa --- /dev/null +++ b/spring-boot-starters/wx-java-cp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/configuration/services/WxCpInRedissonConfiguration.java @@ -0,0 +1,67 @@ +package com.binarywang.spring.starter.wxjava.cp.configuration.services; + +import com.binarywang.spring.starter.wxjava.cp.properties.WxCpMultiProperties; +import com.binarywang.spring.starter.wxjava.cp.properties.WxCpMultiRedisProperties; +import com.binarywang.spring.starter.wxjava.cp.service.WxCpMultiServices; +import lombok.RequiredArgsConstructor; +import me.chanjar.weixin.cp.config.impl.WxCpDefaultConfigImpl; +import me.chanjar.weixin.cp.config.impl.WxCpRedissonConfigImpl; +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 = WxCpMultiProperties.PREFIX + ".config-storage", name = "type", havingValue = "redisson" +) +@RequiredArgsConstructor +public class WxCpInRedissonConfiguration extends AbstractWxCpConfiguration { + private final WxCpMultiProperties wxCpMultiProperties; + private final ApplicationContext applicationContext; + + @Bean + public WxCpMultiServices wxCpMultiServices() { + return this.wxCpMultiServices(wxCpMultiProperties); + } + + @Override + protected WxCpDefaultConfigImpl wxCpConfigStorage(WxCpMultiProperties wxCpMultiProperties) { + return this.configRedisson(wxCpMultiProperties); + } + + private WxCpDefaultConfigImpl configRedisson(WxCpMultiProperties wxCpMultiProperties) { + WxCpMultiRedisProperties redisProperties = wxCpMultiProperties.getConfigStorage().getRedis(); + RedissonClient redissonClient; + if (redisProperties != null && StringUtils.isNotEmpty(redisProperties.getHost())) { + redissonClient = getRedissonClient(wxCpMultiProperties); + } else { + redissonClient = applicationContext.getBean(RedissonClient.class); + } + return new WxCpRedissonConfigImpl(redissonClient, wxCpMultiProperties.getConfigStorage().getKeyPrefix()); + } + + private RedissonClient getRedissonClient(WxCpMultiProperties wxCpMultiProperties) { + WxCpMultiProperties.ConfigStorage storage = wxCpMultiProperties.getConfigStorage(); + WxCpMultiRedisProperties 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-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/properties/WxCpMultiProperties.java b/spring-boot-starters/wx-java-cp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/properties/WxCpMultiProperties.java new file mode 100644 index 0000000000..ab694a30b2 --- /dev/null +++ b/spring-boot-starters/wx-java-cp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/properties/WxCpMultiProperties.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 = WxCpMultiProperties.PREFIX) +public class WxCpMultiProperties implements Serializable { + private static final long serialVersionUID = -1569510477055668503L; + public static final String PREFIX = "wx.cp"; + + 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"; + + /** + * redis连接配置 + */ + @NestedConfigurationProperty + private WxCpMultiRedisProperties redis = new WxCpMultiRedisProperties(); + + /** + * 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-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/properties/WxCpMultiRedisProperties.java b/spring-boot-starters/wx-java-cp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/properties/WxCpMultiRedisProperties.java new file mode 100644 index 0000000000..ea1f257c41 --- /dev/null +++ b/spring-boot-starters/wx-java-cp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/properties/WxCpMultiRedisProperties.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 WxCpMultiRedisProperties 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-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 new file mode 100644 index 0000000000..8ad7149fe6 --- /dev/null +++ b/spring-boot-starters/wx-java-cp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/properties/WxCpSingleProperties.java @@ -0,0 +1,52 @@ +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 WxCpSingleProperties implements Serializable { + private static final long serialVersionUID = -7502823825007859418L; + /** + * 微信企业号 corpId + */ + private String corpId; + /** + * 微信企业号 corpSecret + */ + private String corpSecret; + /** + * 微信企业号应用 token + */ + private String token; + /** + * 微信企业号应用 ID + */ + private Integer agentId; + /** + * 微信企业号应用 EncodingAESKey + */ + private String aesKey; + /** + * 微信企业号应用 会话存档私钥 + */ + private String msgAuditPriKey; + /** + * 微信企业号应用 会话存档类库路径 + */ + 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-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/service/WxCpMultiServices.java b/spring-boot-starters/wx-java-cp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/service/WxCpMultiServices.java new file mode 100644 index 0000000000..dfcb25631d --- /dev/null +++ b/spring-boot-starters/wx-java-cp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/service/WxCpMultiServices.java @@ -0,0 +1,26 @@ +package com.binarywang.spring.starter.wxjava.cp.service; + +import me.chanjar.weixin.cp.api.WxCpService; + +/** + * 企业微信 {@link WxCpService} 所有实例存放类. + * + * @author yl + * created on 2023/10/16 + */ +public interface WxCpMultiServices { + /** + * 通过租户 Id 获取 WxCpService + * + * @param tenantId 租户 Id + * @return WxCpService + */ + WxCpService getWxCpService(String tenantId); + + /** + * 根据租户 Id,从列表中移除一个 WxCpService 实例 + * + * @param tenantId 租户 Id + */ + void removeWxCpService(String tenantId); +} diff --git a/spring-boot-starters/wx-java-cp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/service/WxCpMultiServicesImpl.java b/spring-boot-starters/wx-java-cp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/service/WxCpMultiServicesImpl.java new file mode 100644 index 0000000000..19eae24159 --- /dev/null +++ b/spring-boot-starters/wx-java-cp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/service/WxCpMultiServicesImpl.java @@ -0,0 +1,42 @@ +package com.binarywang.spring.starter.wxjava.cp.service; + +import me.chanjar.weixin.cp.api.WxCpService; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * 企业微信 {@link WxCpMultiServices} 默认实现 + * + * @author yl + * created on 2023/10/16 + */ +public class WxCpMultiServicesImpl implements WxCpMultiServices { + private final Map services = new ConcurrentHashMap<>(); + + /** + * 通过租户 Id 获取 WxCpService + * + * @param tenantId 租户 Id + * @return WxCpService + */ + @Override + public WxCpService getWxCpService(String tenantId) { + return this.services.get(tenantId); + } + + /** + * 根据租户 Id,添加一个 WxCpService 到列表 + * + * @param tenantId 租户 Id + * @param wxCpService WxCpService 实例 + */ + public void addWxCpService(String tenantId, WxCpService wxCpService) { + this.services.put(tenantId, wxCpService); + } + + @Override + public void removeWxCpService(String tenantId) { + this.services.remove(tenantId); + } +} diff --git a/spring-boot-starters/wx-java-cp-multi-spring-boot-starter/src/main/resources/META-INF/spring.factories b/spring-boot-starters/wx-java-cp-multi-spring-boot-starter/src/main/resources/META-INF/spring.factories new file mode 100644 index 0000000000..6010561a96 --- /dev/null +++ b/spring-boot-starters/wx-java-cp-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.WxCpMultiAutoConfiguration diff --git a/spring-boot-starters/wx-java-cp-multi-spring-boot-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/spring-boot-starters/wx-java-cp-multi-spring-boot-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 0000000000..3c48ec34e1 --- /dev/null +++ b/spring-boot-starters/wx-java-cp-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.WxCpMultiAutoConfiguration diff --git a/spring-boot-starters/wx-java-cp-spring-boot-starter/README.md b/spring-boot-starters/wx-java-cp-spring-boot-starter/README.md new file mode 100644 index 0000000000..d6c1abc945 --- /dev/null +++ b/spring-boot-starters/wx-java-cp-spring-boot-starter/README.md @@ -0,0 +1,41 @@ +# wx-java-cp-spring-boot-starter + +## 快速开始 + +1. 引入依赖 + ```xml + + com.github.binarywang + wx-java-cp-spring-boot-starter + ${version} + + ``` +2. 添加配置(application.properties) + ```properties + # 企业微信号配置(必填) + wx.cp.corp-id = @corp-id + wx.cp.corp-secret = @corp-secret + # 选填 + wx.cp.agent-id = @agent-id + wx.cp.token = @token + wx.cp.aes-key = @aes-key + wx.cp.msg-audit-priKey = @msg-audit-priKey + wx.cp.msg-audit-lib-path = @msg-audit-lib-path + # ConfigStorage 配置(选填) + wx.cp.config-storage.type=memory # 配置类型: memory(默认), jedis, redisson, redistemplate + # http 客户端配置(选填) + 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. 支持自动注入的类型: `WxCpService`, `WxCpConfigStorage` + +4. 覆盖自动配置: 自定义注入的bean会覆盖自动注入的 + +- WxCpService +- WxCpConfigStorage 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 new file mode 100644 index 0000000000..81f68274c5 --- /dev/null +++ b/spring-boot-starters/wx-java-cp-spring-boot-starter/pom.xml @@ -0,0 +1,57 @@ + + + + wx-java-spring-boot-starters + com.github.binarywang + 4.8.0 + + 4.0.0 + + wx-java-cp-spring-boot-starter + WxJava - Spring Boot Starter for WxCp + 微信企业号开发的 Spring Boot Starter + + + + com.github.binarywang + weixin-java-cp + ${project.version} + + + redis.clients + jedis + + + org.redisson + redisson + + + org.springframework.data + spring-data-redis + + + + + + + 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-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/config/WxCpAutoConfiguration.java b/spring-boot-starters/wx-java-cp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/config/WxCpAutoConfiguration.java new file mode 100644 index 0000000000..f78c39dd45 --- /dev/null +++ b/spring-boot-starters/wx-java-cp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/config/WxCpAutoConfiguration.java @@ -0,0 +1,21 @@ +package com.binarywang.spring.starter.wxjava.cp.config; + +import com.binarywang.spring.starter.wxjava.cp.properties.WxCpProperties; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; + +/** + * 企业微信自动注册 + * + * @author yl + * created on 2021/12/6 + */ +@Configuration +@EnableConfigurationProperties(WxCpProperties.class) +@Import({ + WxCpStorageAutoConfiguration.class, + WxCpServiceAutoConfiguration.class +}) +public class WxCpAutoConfiguration { +} diff --git a/spring-boot-starters/wx-java-cp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/config/WxCpServiceAutoConfiguration.java b/spring-boot-starters/wx-java-cp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/config/WxCpServiceAutoConfiguration.java new file mode 100644 index 0000000000..70c4045259 --- /dev/null +++ b/spring-boot-starters/wx-java-cp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/config/WxCpServiceAutoConfiguration.java @@ -0,0 +1,44 @@ +package com.binarywang.spring.starter.wxjava.cp.config; + +import com.binarywang.spring.starter.wxjava.cp.properties.WxCpProperties; +import lombok.RequiredArgsConstructor; +import me.chanjar.weixin.cp.api.WxCpService; +import me.chanjar.weixin.cp.api.impl.WxCpServiceImpl; +import me.chanjar.weixin.cp.config.WxCpConfigStorage; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * 企业微信平台相关服务自动注册 + * + * @author yl + * created on 2021/12/6 + */ +@Configuration +@RequiredArgsConstructor +public class WxCpServiceAutoConfiguration { + private final WxCpProperties wxCpProperties; + + @Bean + @ConditionalOnMissingBean + @ConditionalOnBean(WxCpConfigStorage.class) + public WxCpService wxCpService(WxCpConfigStorage wxCpConfigStorage) { + WxCpService wxCpService = new WxCpServiceImpl(); + wxCpService.setWxCpConfigStorage(wxCpConfigStorage); + + WxCpProperties.ConfigStorage storage = wxCpProperties.getConfigStorage(); + int maxRetryTimes = storage.getMaxRetryTimes(); + if (maxRetryTimes < 0) { + maxRetryTimes = 0; + } + int retrySleepMillis = storage.getRetrySleepMillis(); + if (retrySleepMillis < 0) { + retrySleepMillis = 1000; + } + wxCpService.setRetrySleepMillis(retrySleepMillis); + wxCpService.setMaxRetryTimes(maxRetryTimes); + return wxCpService; + } +} diff --git a/spring-boot-starters/wx-java-cp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/config/WxCpStorageAutoConfiguration.java b/spring-boot-starters/wx-java-cp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/config/WxCpStorageAutoConfiguration.java new file mode 100644 index 0000000000..1c7d80b84e --- /dev/null +++ b/spring-boot-starters/wx-java-cp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/config/WxCpStorageAutoConfiguration.java @@ -0,0 +1,24 @@ +package com.binarywang.spring.starter.wxjava.cp.config; + +import com.binarywang.spring.starter.wxjava.cp.storage.WxCpInJedisConfigStorageConfiguration; +import com.binarywang.spring.starter.wxjava.cp.storage.WxCpInMemoryConfigStorageConfiguration; +import com.binarywang.spring.starter.wxjava.cp.storage.WxCpInRedisTemplateConfigStorageConfiguration; +import com.binarywang.spring.starter.wxjava.cp.storage.WxCpInRedissonConfigStorageConfiguration; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; + +/** + * 企业微信存储策略自动配置 + * + * @author yl + * created on 2021/12/6 + */ +@Configuration +@Import({ + WxCpInMemoryConfigStorageConfiguration.class, + WxCpInJedisConfigStorageConfiguration.class, + WxCpInRedissonConfigStorageConfiguration.class, + WxCpInRedisTemplateConfigStorageConfiguration.class +}) +public class WxCpStorageAutoConfiguration { +} 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 new file mode 100644 index 0000000000..c93a7e187f --- /dev/null +++ b/spring-boot-starters/wx-java-cp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/properties/WxCpProperties.java @@ -0,0 +1,139 @@ +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; + +/** + * 企业微信接入相关配置属性 + * + * @author yl + * created on 2021/12/6 + */ +@Data +@NoArgsConstructor +@ConfigurationProperties(prefix = WxCpProperties.PREFIX) +public class WxCpProperties { + public static final String PREFIX = "wx.cp"; + + /** + * 微信企业号 corpId + */ + private String corpId; + /** + * 微信企业号 corpSecret + */ + private String corpSecret; + /** + * 微信企业号应用 token + */ + private String token; + /** + * 微信企业号应用 ID + */ + private Integer agentId; + /** + * 微信企业号应用 EncodingAESKey + */ + private String aesKey; + /** + * 微信企业号应用 会话存档私钥 + */ + private String msgAuditPriKey; + /** + * 微信企业号应用 会话存档类库路径 + */ + private String msgAuditLibPath; + + /** + * 自定义企业微信服务器baseUrl,用于替换默认的 https://qyapi.weixin.qq.com + * 例如:http://proxy.company.com:8080 + */ + private String baseApiUrl; + + /** + * 配置存储策略,默认内存 + */ + 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"; + + /** + * redis连接配置 + */ + @NestedConfigurationProperty + private WxCpRedisProperties redis = new WxCpRedisProperties(); + + /** + * 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 + } +} diff --git a/spring-boot-starters/wx-java-cp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/properties/WxCpRedisProperties.java b/spring-boot-starters/wx-java-cp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/properties/WxCpRedisProperties.java new file mode 100644 index 0000000000..63a7fe01e0 --- /dev/null +++ b/spring-boot-starters/wx-java-cp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/properties/WxCpRedisProperties.java @@ -0,0 +1,46 @@ +package com.binarywang.spring.starter.wxjava.cp.properties; + +import lombok.Data; + +import java.io.Serializable; + +/** + * Redis配置. + * + * @author yl + * created on 2023/04/23 + */ +@Data +public class WxCpRedisProperties 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-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 new file mode 100644 index 0000000000..2b1d8c13c5 --- /dev/null +++ b/spring-boot-starters/wx-java-cp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/storage/AbstractWxCpConfigStorageConfiguration.java @@ -0,0 +1,64 @@ +package com.binarywang.spring.starter.wxjava.cp.storage; + +import com.binarywang.spring.starter.wxjava.cp.properties.WxCpProperties; +import me.chanjar.weixin.cp.config.impl.WxCpDefaultConfigImpl; +import org.apache.commons.lang3.StringUtils; + +/** + * WxCpConfigStorage 抽象配置类 + * + * @author yl & Wang_Wong + * created on 2021/12/6 + */ +public abstract class AbstractWxCpConfigStorageConfiguration { + + protected WxCpDefaultConfigImpl config(WxCpDefaultConfigImpl config, WxCpProperties properties) { + String corpId = properties.getCorpId(); + String corpSecret = properties.getCorpSecret(); + Integer agentId = properties.getAgentId(); + String token = properties.getToken(); + String aesKey = properties.getAesKey(); + // 企业微信,私钥,会话存档路径 + String msgAuditPriKey = properties.getMsgAuditPriKey(); + String msgAuditLibPath = properties.getMsgAuditLibPath(); + + config.setCorpId(corpId); + config.setCorpSecret(corpSecret); + config.setAgentId(agentId); + if (StringUtils.isNotBlank(token)) { + config.setToken(token); + } + if (StringUtils.isNotBlank(aesKey)) { + config.setAesKey(aesKey); + } + if (StringUtils.isNotBlank(msgAuditPriKey)) { + config.setMsgAuditPriKey(msgAuditPriKey); + } + 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(); + 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); + } + } + return config; + } + +} diff --git a/spring-boot-starters/wx-java-cp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/storage/WxCpInJedisConfigStorageConfiguration.java b/spring-boot-starters/wx-java-cp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/storage/WxCpInJedisConfigStorageConfiguration.java new file mode 100644 index 0000000000..246971baed --- /dev/null +++ b/spring-boot-starters/wx-java-cp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/storage/WxCpInJedisConfigStorageConfiguration.java @@ -0,0 +1,74 @@ +package com.binarywang.spring.starter.wxjava.cp.storage; + +import com.binarywang.spring.starter.wxjava.cp.properties.WxCpProperties; +import com.binarywang.spring.starter.wxjava.cp.properties.WxCpRedisProperties; +import lombok.RequiredArgsConstructor; +import me.chanjar.weixin.cp.config.WxCpConfigStorage; +import me.chanjar.weixin.cp.config.impl.WxCpDefaultConfigImpl; +import me.chanjar.weixin.cp.config.impl.WxCpJedisConfigImpl; +import org.apache.commons.lang3.StringUtils; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import redis.clients.jedis.JedisPool; +import redis.clients.jedis.JedisPoolConfig; + +/** + * 自动装配基于 jedis 策略配置 + * + * @author yl + * created on 2023/04/23 + */ +@Configuration +@ConditionalOnProperty( + prefix = WxCpProperties.PREFIX + ".config-storage", name = "type", havingValue = "jedis" +) +@RequiredArgsConstructor +public class WxCpInJedisConfigStorageConfiguration extends AbstractWxCpConfigStorageConfiguration { + private final WxCpProperties wxCpProperties; + private final ApplicationContext applicationContext; + + @Bean + @ConditionalOnMissingBean(WxCpConfigStorage.class) + public WxCpConfigStorage wxCpConfigStorage() { + WxCpDefaultConfigImpl config = getConfigStorage(); + return this.config(config, wxCpProperties); + } + + private WxCpJedisConfigImpl getConfigStorage() { + WxCpRedisProperties wxCpRedisProperties = wxCpProperties.getConfigStorage().getRedis(); + JedisPool jedisPool; + if (wxCpRedisProperties != null && StringUtils.isNotEmpty(wxCpRedisProperties.getHost())) { + jedisPool = getJedisPool(); + } else { + jedisPool = applicationContext.getBean(JedisPool.class); + } + return new WxCpJedisConfigImpl(jedisPool, wxCpProperties.getConfigStorage().getKeyPrefix()); + } + + private JedisPool getJedisPool() { + WxCpProperties.ConfigStorage storage = wxCpProperties.getConfigStorage(); + WxCpRedisProperties 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-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/storage/WxCpInMemoryConfigStorageConfiguration.java b/spring-boot-starters/wx-java-cp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/storage/WxCpInMemoryConfigStorageConfiguration.java new file mode 100644 index 0000000000..3722bd07d1 --- /dev/null +++ b/spring-boot-starters/wx-java-cp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/storage/WxCpInMemoryConfigStorageConfiguration.java @@ -0,0 +1,33 @@ +package com.binarywang.spring.starter.wxjava.cp.storage; + +import com.binarywang.spring.starter.wxjava.cp.properties.WxCpProperties; +import lombok.RequiredArgsConstructor; +import me.chanjar.weixin.cp.config.WxCpConfigStorage; +import me.chanjar.weixin.cp.config.impl.WxCpDefaultConfigImpl; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * 自动装配基于内存策略配置 + * + * @author yl + * created on 2021/12/6 + */ +@Configuration +@ConditionalOnProperty( + prefix = WxCpProperties.PREFIX + ".config-storage", name = "type", + matchIfMissing = true, havingValue = "memory" +) +@RequiredArgsConstructor +public class WxCpInMemoryConfigStorageConfiguration extends AbstractWxCpConfigStorageConfiguration { + private final WxCpProperties wxCpProperties; + + @Bean + @ConditionalOnMissingBean(WxCpConfigStorage.class) + public WxCpConfigStorage wxCpConfigStorage() { + WxCpDefaultConfigImpl config = new WxCpDefaultConfigImpl(); + return this.config(config, wxCpProperties); + } +} diff --git a/spring-boot-starters/wx-java-cp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/storage/WxCpInRedisTemplateConfigStorageConfiguration.java b/spring-boot-starters/wx-java-cp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/storage/WxCpInRedisTemplateConfigStorageConfiguration.java new file mode 100644 index 0000000000..879568b16a --- /dev/null +++ b/spring-boot-starters/wx-java-cp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/storage/WxCpInRedisTemplateConfigStorageConfiguration.java @@ -0,0 +1,41 @@ +package com.binarywang.spring.starter.wxjava.cp.storage; + +import com.binarywang.spring.starter.wxjava.cp.properties.WxCpProperties; +import lombok.RequiredArgsConstructor; +import me.chanjar.weixin.cp.config.WxCpConfigStorage; +import me.chanjar.weixin.cp.config.impl.WxCpDefaultConfigImpl; +import me.chanjar.weixin.cp.config.impl.WxCpRedisTemplateConfigImpl; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.core.StringRedisTemplate; + +/** + * 自动装配基于 redisTemplate 策略配置 + * + * @author yl + * created on 2023/04/23 + */ +@Configuration +@ConditionalOnProperty( + prefix = WxCpProperties.PREFIX + ".config-storage", name = "type", havingValue = "redistemplate" +) +@RequiredArgsConstructor +public class WxCpInRedisTemplateConfigStorageConfiguration extends AbstractWxCpConfigStorageConfiguration { + private final WxCpProperties wxCpProperties; + private final ApplicationContext applicationContext; + + @Bean + @ConditionalOnMissingBean(WxCpConfigStorage.class) + public WxCpConfigStorage wxCpConfigStorage() { + WxCpDefaultConfigImpl config = getConfigStorage(); + return this.config(config, wxCpProperties); + } + + private WxCpRedisTemplateConfigImpl getConfigStorage() { + StringRedisTemplate redisTemplate = applicationContext.getBean(StringRedisTemplate.class); + return new WxCpRedisTemplateConfigImpl(redisTemplate, wxCpProperties.getConfigStorage().getKeyPrefix()); + } +} diff --git a/spring-boot-starters/wx-java-cp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/storage/WxCpInRedissonConfigStorageConfiguration.java b/spring-boot-starters/wx-java-cp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/storage/WxCpInRedissonConfigStorageConfiguration.java new file mode 100644 index 0000000000..060b894fd1 --- /dev/null +++ b/spring-boot-starters/wx-java-cp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/storage/WxCpInRedissonConfigStorageConfiguration.java @@ -0,0 +1,65 @@ +package com.binarywang.spring.starter.wxjava.cp.storage; + +import com.binarywang.spring.starter.wxjava.cp.properties.WxCpProperties; +import com.binarywang.spring.starter.wxjava.cp.properties.WxCpRedisProperties; +import lombok.RequiredArgsConstructor; +import me.chanjar.weixin.cp.config.WxCpConfigStorage; +import me.chanjar.weixin.cp.config.impl.WxCpDefaultConfigImpl; +import me.chanjar.weixin.cp.config.impl.WxCpRedissonConfigImpl; +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.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * 自动装配基于 redisson 策略配置 + * + * @author yl + * created on 2023/04/23 + */ +@Configuration +@ConditionalOnProperty( + prefix = WxCpProperties.PREFIX + ".config-storage", name = "type", havingValue = "redisson" +) +@RequiredArgsConstructor +public class WxCpInRedissonConfigStorageConfiguration extends AbstractWxCpConfigStorageConfiguration { + private final WxCpProperties wxCpProperties; + private final ApplicationContext applicationContext; + + @Bean + @ConditionalOnMissingBean(WxCpConfigStorage.class) + public WxCpConfigStorage wxCpConfigStorage() { + WxCpDefaultConfigImpl config = getConfigStorage(); + return this.config(config, wxCpProperties); + } + + private WxCpRedissonConfigImpl getConfigStorage() { + WxCpRedisProperties redisProperties = wxCpProperties.getConfigStorage().getRedis(); + RedissonClient redissonClient; + if (redisProperties != null && StringUtils.isNotEmpty(redisProperties.getHost())) { + redissonClient = getRedissonClient(); + } else { + redissonClient = applicationContext.getBean(RedissonClient.class); + } + return new WxCpRedissonConfigImpl(redissonClient, wxCpProperties.getConfigStorage().getKeyPrefix()); + } + + private RedissonClient getRedissonClient() { + WxCpProperties.ConfigStorage storage = wxCpProperties.getConfigStorage(); + WxCpRedisProperties 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-spring-boot-starter/src/main/resources/META-INF/spring.factories b/spring-boot-starters/wx-java-cp-spring-boot-starter/src/main/resources/META-INF/spring.factories new file mode 100644 index 0000000000..c2ef7f6354 --- /dev/null +++ b/spring-boot-starters/wx-java-cp-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.config.WxCpAutoConfiguration diff --git a/spring-boot-starters/wx-java-cp-spring-boot-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/spring-boot-starters/wx-java-cp-spring-boot-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 0000000000..0beff3f862 --- /dev/null +++ b/spring-boot-starters/wx-java-cp-spring-boot-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1 @@ +com.binarywang.spring.starter.wxjava.cp.config.WxCpAutoConfiguration 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..f1cc1fba13 --- /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.8.0 + + 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/README.md b/spring-boot-starters/wx-java-miniapp-multi-spring-boot-starter/README.md new file mode 100644 index 0000000000..ccc0d5bf5f --- /dev/null +++ b/spring-boot-starters/wx-java-miniapp-multi-spring-boot-starter/README.md @@ -0,0 +1,96 @@ +# wx-java-miniapp-multi-spring-boot-starter + +## 快速开始 + +1. 引入依赖 + ```xml + + com.github.binarywang + wx-java-miniapp-multi-spring-boot-starter + ${version} + + ``` +2. 添加配置(application.properties) + ```properties + # 公众号配置 + ## 应用 1 配置(必填) + wx.ma.apps.tenantId1.app-id=appId + wx.ma.apps.tenantId1.app-secret=@secret + ## 选填 + wx.ma.apps.tenantId1.token=@token + wx.ma.apps.tenantId1.aes-key=@aesKey + wx.ma.apps.tenantId1.use-stable-access-token=@useStableAccessToken + ## 应用 2 配置(必填) + wx.ma.apps.tenantId2.app-id=@appId + wx.ma.apps.tenantId2.app-secret =@secret + ## 选填 + wx.ma.apps.tenantId2.token=@token + wx.ma.apps.tenantId2.aes-key=@aesKey + wx.ma.apps.tenantId2.use-stable-access-token=@useStableAccessToken + + # ConfigStorage 配置(选填) + ## 配置类型: memory(默认), jedis, redisson + wx.ma.config-storage.type=memory + ## 相关redis前缀配置: wx:ma:multi(默认) + wx.ma.config-storage.key-prefix=wx:ma:multi + wx.ma.config-storage.redis.host=127.0.0.1 + wx.ma.config-storage.redis.port=6379 + ## 单机和 sentinel 同时存在时,优先使用sentinel配置 + # wx.ma.config-storage.redis.sentinel-ips=127.0.0.1:16379,127.0.0.1:26379 + # wx.ma.config-storage.redis.sentinel-name=mymaster + + # http 客户端配置(选填) + ## # http客户端类型: http_client(默认), ok_http, jodd_http + wx.ma.config-storage.http-client-type=http_client + wx.ma.config-storage.http-proxy-host= + wx.ma.config-storage.http-proxy-port= + wx.ma.config-storage.http-proxy-username= + wx.ma.config-storage.http-proxy-password= + ## 最大重试次数,默认:5 次,如果小于 0,则为 0 + wx.ma.config-storage.max-retry-times=5 + ## 重试时间间隔步进,默认:1000 毫秒,如果小于 0,则为 1000 + wx.ma.config-storage.retry-sleep-millis=1000 + ``` +3. 自动注入的类型:`WxMaMultiServices` + +4. 使用样例 + +```java +import com.binarywang.spring.starter.wxjava.miniapp.service.WxMaMultiServices; +import com.binarywang.spring.starter.wxjava.miniapp.service.WxMaMultiServices; +import cn.binarywang.wx.miniapp.api.WxMaService; +import cn.binarywang.wx.miniapp.api.WxMaUserService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +@Service +public class DemoService { + @Autowired + private WxMaMultiServices wxMaMultiServices; + + public void test() { + // 应用 1 的 WxMaService + WxMaService wxMaService1 = wxMaMultiServices.getWxMaService("tenantId1"); + WxMaUserService userService1 = wxMaService1.getUserService(); + userService1.userInfo("xxx"); + // todo ... + + // 应用 2 的 WxMaService + WxMaService wxMaService2 = wxMaMultiServices.getWxMaService("tenantId2"); + WxMaUserService userService2 = wxMaService2.getUserService(); + userService2.userInfo("xxx"); + // todo ... + + // 应用 3 的 WxMaService + WxMaService wxMaService3 = wxMaMultiServices.getWxMaService("tenantId3"); + // 判断是否为空 + if (wxMaService3 == null) { + // todo wxMaService3 为空,请先配置 tenantId3 微信公众号应用参数 + return; + } + WxMaUserService userService3 = wxMaService3.getUserService(); + userService3.userInfo("xxx"); + // todo ... + } +} +``` 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 new file mode 100644 index 0000000000..8c8854067f --- /dev/null +++ b/spring-boot-starters/wx-java-miniapp-multi-spring-boot-starter/pom.xml @@ -0,0 +1,72 @@ + + + + wx-java-spring-boot-starters + com.github.binarywang + 4.8.0 + + 4.0.0 + + wx-java-miniapp-multi-spring-boot-starter + WxJava - Spring Boot Starter for MiniApp::支持多账号配置 + 微信公众号开发的 Spring Boot Starter::支持多账号配置 + + + + com.github.binarywang + weixin-java-miniapp + ${project.version} + + + redis.clients + jedis + provided + + + org.redisson + redisson + provided + + + org.springframework.data + spring-data-redis + provided + + + org.jodd + jodd-http + provided + + + com.squareup.okhttp3 + okhttp + 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-miniapp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/miniapp/autoconfigure/WxMaMultiAutoConfiguration.java b/spring-boot-starters/wx-java-miniapp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/miniapp/autoconfigure/WxMaMultiAutoConfiguration.java new file mode 100644 index 0000000000..bd03751c37 --- /dev/null +++ b/spring-boot-starters/wx-java-miniapp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/miniapp/autoconfigure/WxMaMultiAutoConfiguration.java @@ -0,0 +1,14 @@ +package com.binarywang.spring.starter.wxjava.miniapp.autoconfigure; + +import com.binarywang.spring.starter.wxjava.miniapp.configuration.WxMaMultiServiceConfiguration; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; + +/** + * @author monch + * created on 2024/9/6 + */ +@Configuration +@Import(WxMaMultiServiceConfiguration.class) +public class WxMaMultiAutoConfiguration { +} 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 new file mode 100644 index 0000000000..e1db56cfc7 --- /dev/null +++ b/spring-boot-starters/wx-java-miniapp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/miniapp/configuration/WxMaMultiServiceConfiguration.java @@ -0,0 +1,27 @@ +package com.binarywang.spring.starter.wxjava.miniapp.configuration; + +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; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; + +/** + * 微信小程序相关服务自动注册 + * + * @author monch + * created on 2024/9/6 + */ +@Configuration +@EnableConfigurationProperties(WxMaMultiProperties.class) +@Import({ + 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 new file mode 100644 index 0000000000..15e638f89e --- /dev/null +++ b/spring-boot-starters/wx-java-miniapp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/miniapp/configuration/services/AbstractWxMaConfiguration.java @@ -0,0 +1,150 @@ +package com.binarywang.spring.starter.wxjava.miniapp.configuration.services; + +import com.binarywang.spring.starter.wxjava.miniapp.properties.WxMaMultiProperties; +import com.binarywang.spring.starter.wxjava.miniapp.properties.WxMaSingleProperties; +import com.binarywang.spring.starter.wxjava.miniapp.service.WxMaMultiServices; +import com.binarywang.spring.starter.wxjava.miniapp.service.WxMaMultiServicesImpl; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import cn.binarywang.wx.miniapp.api.WxMaService; +import cn.binarywang.wx.miniapp.api.impl.WxMaServiceHttpClientImpl; +import cn.binarywang.wx.miniapp.api.impl.WxMaServiceImpl; +import cn.binarywang.wx.miniapp.api.impl.WxMaServiceJoddHttpImpl; +import cn.binarywang.wx.miniapp.api.impl.WxMaServiceOkHttpImpl; +import cn.binarywang.wx.miniapp.config.WxMaConfig; +import cn.binarywang.wx.miniapp.config.impl.WxMaDefaultConfigImpl; +import org.apache.commons.lang3.StringUtils; + +import java.util.Collection; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * WxMaConfigStorage 抽象配置类 + * + * @author monch + * created on 2024/9/6 + */ +@RequiredArgsConstructor +@Slf4j +public abstract class AbstractWxMaConfiguration { + + protected WxMaMultiServices wxMaMultiServices(WxMaMultiProperties wxMaMultiProperties) { + Map appsMap = wxMaMultiProperties.getApps(); + if (appsMap == null || appsMap.isEmpty()) { + log.warn("微信公众号应用参数未配置,通过 WxMaMultiServices#getWxMaService(\"tenantId\")获取实例将返回空"); + return new WxMaMultiServicesImpl(); + } + /** + * 校验 appId 是否唯一,避免使用 redis 缓存 token、ticket 时错乱。 + * + * 查看 {@link cn.binarywang.wx.miniapp.config.impl.WxMaRedisConfigImpl#setAppId(String)} + */ + Collection apps = appsMap.values(); + if (apps.size() > 1) { + // 校验 appId 是否唯一 + boolean multi = apps.stream() + // 没有 appId,如果不判断是否为空,这里会报 NPE 异常 + .collect(Collectors.groupingBy(c -> c.getAppId() == null ? 0 : c.getAppId(), Collectors.counting())) + .entrySet().stream().anyMatch(e -> e.getValue() > 1); + if (multi) { + throw new RuntimeException("请确保微信公众号配置 appId 的唯一性"); + } + } + WxMaMultiServicesImpl services = new WxMaMultiServicesImpl(); + + Set> entries = appsMap.entrySet(); + for (Map.Entry entry : entries) { + String tenantId = entry.getKey(); + WxMaSingleProperties wxMaSingleProperties = entry.getValue(); + WxMaDefaultConfigImpl storage = this.wxMaConfigStorage(wxMaMultiProperties); + this.configApp(storage, wxMaSingleProperties); + this.configHttp(storage, wxMaMultiProperties.getConfigStorage()); + WxMaService wxMaService = this.wxMaService(storage, wxMaMultiProperties); + services.addWxMaService(tenantId, wxMaService); + } + return services; + } + + /** + * 配置 WxMaDefaultConfigImpl + * + * @param wxMaMultiProperties 参数 + * @return WxMaDefaultConfigImpl + */ + protected abstract WxMaDefaultConfigImpl wxMaConfigStorage(WxMaMultiProperties wxMaMultiProperties); + + public WxMaService wxMaService(WxMaConfig wxMaConfig, WxMaMultiProperties wxMaMultiProperties) { + WxMaMultiProperties.ConfigStorage storage = wxMaMultiProperties.getConfigStorage(); + WxMaMultiProperties.HttpClientType httpClientType = storage.getHttpClientType(); + WxMaService wxMaService; + switch (httpClientType) { + case OK_HTTP: + wxMaService = new WxMaServiceOkHttpImpl(); + break; + case JODD_HTTP: + wxMaService = new WxMaServiceJoddHttpImpl(); + break; + case HTTP_CLIENT: + wxMaService = new WxMaServiceHttpClientImpl(); + break; + default: + wxMaService = new WxMaServiceImpl(); + break; + } + + wxMaService.setWxMaConfig(wxMaConfig); + int maxRetryTimes = storage.getMaxRetryTimes(); + if (maxRetryTimes < 0) { + maxRetryTimes = 0; + } + int retrySleepMillis = storage.getRetrySleepMillis(); + if (retrySleepMillis < 0) { + retrySleepMillis = 1000; + } + wxMaService.setRetrySleepMillis(retrySleepMillis); + wxMaService.setMaxRetryTimes(maxRetryTimes); + return wxMaService; + } + + 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); + if (StringUtils.isNotBlank(token)) { + config.setToken(token); + } + 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) { + 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-miniapp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/miniapp/configuration/services/WxMaInJedisConfiguration.java b/spring-boot-starters/wx-java-miniapp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/miniapp/configuration/services/WxMaInJedisConfiguration.java new file mode 100644 index 0000000000..52eeffe7e4 --- /dev/null +++ b/spring-boot-starters/wx-java-miniapp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/miniapp/configuration/services/WxMaInJedisConfiguration.java @@ -0,0 +1,76 @@ +package com.binarywang.spring.starter.wxjava.miniapp.configuration.services; + +import cn.binarywang.wx.miniapp.config.impl.WxMaRedisConfigImpl; +import com.binarywang.spring.starter.wxjava.miniapp.properties.WxMaMultiProperties; +import com.binarywang.spring.starter.wxjava.miniapp.properties.WxMaMultiRedisProperties; +import com.binarywang.spring.starter.wxjava.miniapp.service.WxMaMultiServices; +import lombok.RequiredArgsConstructor; +import cn.binarywang.wx.miniapp.config.impl.WxMaDefaultConfigImpl; +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 monch + * created on 2024/9/6 + */ +@Configuration +@ConditionalOnProperty( + prefix = WxMaMultiProperties.PREFIX + ".config-storage", name = "type", havingValue = "jedis" +) +@RequiredArgsConstructor +public class WxMaInJedisConfiguration 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.configRedis(wxMaMultiProperties); + } + + private WxMaDefaultConfigImpl configRedis(WxMaMultiProperties wxMaMultiProperties) { + WxMaMultiRedisProperties wxMaMultiRedisProperties = wxMaMultiProperties.getConfigStorage().getRedis(); + JedisPool jedisPool; + if (wxMaMultiRedisProperties != null && StringUtils.isNotEmpty(wxMaMultiRedisProperties.getHost())) { + jedisPool = getJedisPool(wxMaMultiProperties); + } else { + jedisPool = applicationContext.getBean(JedisPool.class); + } + return new WxMaRedisConfigImpl(jedisPool); + } + + private JedisPool getJedisPool(WxMaMultiProperties wxMaMultiProperties) { + WxMaMultiProperties.ConfigStorage storage = wxMaMultiProperties.getConfigStorage(); + WxMaMultiRedisProperties 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-miniapp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/miniapp/configuration/services/WxMaInMemoryConfiguration.java b/spring-boot-starters/wx-java-miniapp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/miniapp/configuration/services/WxMaInMemoryConfiguration.java new file mode 100644 index 0000000000..3c8202a6b3 --- /dev/null +++ b/spring-boot-starters/wx-java-miniapp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/miniapp/configuration/services/WxMaInMemoryConfiguration.java @@ -0,0 +1,39 @@ +package com.binarywang.spring.starter.wxjava.miniapp.configuration.services; + +import cn.binarywang.wx.miniapp.config.impl.WxMaDefaultConfigImpl; +import com.binarywang.spring.starter.wxjava.miniapp.properties.WxMaMultiProperties; +import com.binarywang.spring.starter.wxjava.miniapp.service.WxMaMultiServices; +import lombok.RequiredArgsConstructor; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * 自动装配基于内存策略配置 + * + * @author monch + * created on 2024/9/6 + */ +@Configuration +@ConditionalOnProperty( + prefix = WxMaMultiProperties.PREFIX + ".config-storage", name = "type", havingValue = "memory", matchIfMissing = true +) +@RequiredArgsConstructor +public class WxMaInMemoryConfiguration extends AbstractWxMaConfiguration { + private final WxMaMultiProperties wxMaMultiProperties; + + @Bean + public WxMaMultiServices wxMaMultiServices() { + return this.wxMaMultiServices(wxMaMultiProperties); + } + + @Override + protected WxMaDefaultConfigImpl wxMaConfigStorage(WxMaMultiProperties wxMaMultiProperties) { + return this.configInMemory(); + } + + private WxMaDefaultConfigImpl configInMemory() { + return new WxMaDefaultConfigImpl(); + // return new WxMaDefaultConfigImpl(); + } +} 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/configuration/services/WxMaInRedissonConfiguration.java b/spring-boot-starters/wx-java-miniapp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/miniapp/configuration/services/WxMaInRedissonConfiguration.java new file mode 100644 index 0000000000..c1915400d3 --- /dev/null +++ b/spring-boot-starters/wx-java-miniapp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/miniapp/configuration/services/WxMaInRedissonConfiguration.java @@ -0,0 +1,67 @@ +package com.binarywang.spring.starter.wxjava.miniapp.configuration.services; + +import cn.binarywang.wx.miniapp.config.impl.WxMaDefaultConfigImpl; +import cn.binarywang.wx.miniapp.config.impl.WxMaRedissonConfigImpl; +import com.binarywang.spring.starter.wxjava.miniapp.properties.WxMaMultiProperties; +import com.binarywang.spring.starter.wxjava.miniapp.properties.WxMaMultiRedisProperties; +import com.binarywang.spring.starter.wxjava.miniapp.service.WxMaMultiServices; +import lombok.RequiredArgsConstructor; +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 monch + * created on 2024/9/6 + */ +@Configuration +@ConditionalOnProperty( + prefix = WxMaMultiProperties.PREFIX + ".config-storage", name = "type", havingValue = "redisson" +) +@RequiredArgsConstructor +public class WxMaInRedissonConfiguration 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.configRedisson(wxMaMultiProperties); + } + + private WxMaDefaultConfigImpl configRedisson(WxMaMultiProperties wxMaMultiProperties) { + WxMaMultiRedisProperties redisProperties = wxMaMultiProperties.getConfigStorage().getRedis(); + RedissonClient redissonClient; + if (redisProperties != null && StringUtils.isNotEmpty(redisProperties.getHost())) { + redissonClient = getRedissonClient(wxMaMultiProperties); + } else { + redissonClient = applicationContext.getBean(RedissonClient.class); + } + return new WxMaRedissonConfigImpl(redissonClient, wxMaMultiProperties.getConfigStorage().getKeyPrefix()); + } + + private RedissonClient getRedissonClient(WxMaMultiProperties wxMaMultiProperties) { + WxMaMultiProperties.ConfigStorage storage = wxMaMultiProperties.getConfigStorage(); + WxMaMultiRedisProperties 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-miniapp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/miniapp/properties/WxMaMultiProperties.java b/spring-boot-starters/wx-java-miniapp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/miniapp/properties/WxMaMultiProperties.java new file mode 100644 index 0000000000..6dae33d584 --- /dev/null +++ b/spring-boot-starters/wx-java-miniapp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/miniapp/properties/WxMaMultiProperties.java @@ -0,0 +1,154 @@ +package com.binarywang.spring.starter.wxjava.miniapp.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 monch + * created on 2024/9/6 + */ +@Data +@NoArgsConstructor +@ConfigurationProperties(WxMaMultiProperties.PREFIX) +public class WxMaMultiProperties implements Serializable { + private static final long serialVersionUID = -5358245184407791011L; + public static final String PREFIX = "wx.ma"; + + private Map apps = new HashMap<>(); + + /** + * 自定义host配置 + */ + private HostConfig hosts; + + /** + * 存储策略 + */ + private final ConfigStorage configStorage = new ConfigStorage(); + + @Data + @NoArgsConstructor + public static class HostConfig implements Serializable { + private static final long serialVersionUID = -4172767630740346001L; + + /** + * 对应于:https://api.weixin.qq.com + */ + private String apiHost; + + /** + * 对应于:https://open.weixin.qq.com + */ + private String openHost; + + /** + * 对应于:https://mp.weixin.qq.com + */ + private String mpHost; + } + + @Data + @NoArgsConstructor + public static class ConfigStorage implements Serializable { + private static final long serialVersionUID = 4815731027000065434L; + + /** + * 存储类型. + */ + private StorageType type = StorageType.MEMORY; + + /** + * 指定key前缀. + */ + private String keyPrefix = "wx:ma:multi"; + + /** + * redis连接配置. + */ + @NestedConfigurationProperty + private final WxMaMultiRedisProperties redis = new WxMaMultiRedisProperties(); + + /** + * 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 cn.binarywang.wx.miniapp.api.WxMaService#setMaxRetryTimes(int)}
+     *   {@link cn.binarywang.wx.miniapp.api.impl.BaseWxMaServiceImpl#setMaxRetryTimes(int)}
+     * 
+ */ + private int maxRetryTimes = 5; + + /** + * http 请求重试间隔 + *
+     *   {@link cn.binarywang.wx.miniapp.api.WxMaService#setRetrySleepMillis(int)}
+     *   {@link cn.binarywang.wx.miniapp.api.impl.BaseWxMaServiceImpl#setRetrySleepMillis(int)}
+     * 
+ */ + private int retrySleepMillis = 1000; + } + + public enum StorageType { + /** + * 内存 + */ + MEMORY, + /** + * jedis + */ + JEDIS, + /** + * redisson + */ + REDISSON, + /** + * redisTemplate + */ + REDIS_TEMPLATE + } + + public enum HttpClientType { + /** + * HttpClient + */ + HTTP_CLIENT, + /** + * OkHttp + */ + OK_HTTP, + /** + * JoddHttp + */ + JODD_HTTP + } +} diff --git a/spring-boot-starters/wx-java-miniapp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/miniapp/properties/WxMaMultiRedisProperties.java b/spring-boot-starters/wx-java-miniapp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/miniapp/properties/WxMaMultiRedisProperties.java new file mode 100644 index 0000000000..67562c69a4 --- /dev/null +++ b/spring-boot-starters/wx-java-miniapp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/miniapp/properties/WxMaMultiRedisProperties.java @@ -0,0 +1,56 @@ +package com.binarywang.spring.starter.wxjava.miniapp.properties; + +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * @author monch + * created on 2024/9/6 + */ +@Data +@NoArgsConstructor +public class WxMaMultiRedisProperties implements Serializable { + private static final long serialVersionUID = -5924815351660074401L; + + /** + * 主机地址. + */ + private String host = "127.0.0.1"; + + /** + * 端口号. + */ + private int port = 6379; + + /** + * 密码. + */ + private String password; + + /** + * 超时. + */ + private int timeout = 2000; + + /** + * 数据库. + */ + private int database = 0; + + /** + * sentinel ips + */ + private String sentinelIps; + + /** + * sentinel name + */ + private String sentinelName; + + private Integer maxActive; + private Integer maxIdle; + private Integer maxWaitMillis; + private Integer minIdle; +} diff --git a/spring-boot-starters/wx-java-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 new file mode 100644 index 0000000000..5defae5514 --- /dev/null +++ b/spring-boot-starters/wx-java-miniapp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/miniapp/properties/WxMaSingleProperties.java @@ -0,0 +1,57 @@ +package com.binarywang.spring.starter.wxjava.miniapp.properties; + +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * @author monch + * created on 2024/9/6 + */ +@Data +@NoArgsConstructor +public class WxMaSingleProperties implements Serializable { + private static final long serialVersionUID = 1980986361098922525L; + /** + * 设置微信公众号的 appid. + */ + private String appId; + + /** + * 设置微信公众号的 app secret. + */ + private String appSecret; + + /** + * 设置微信公众号的 token. + */ + private String token; + + /** + * 设置微信公众号的 EncodingAESKey. + */ + 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-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/miniapp/service/WxMaMultiServices.java b/spring-boot-starters/wx-java-miniapp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/miniapp/service/WxMaMultiServices.java new file mode 100644 index 0000000000..90fce690c7 --- /dev/null +++ b/spring-boot-starters/wx-java-miniapp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/miniapp/service/WxMaMultiServices.java @@ -0,0 +1,27 @@ +package com.binarywang.spring.starter.wxjava.miniapp.service; + + +import cn.binarywang.wx.miniapp.api.WxMaService; + +/** + * 微信小程序 {@link WxMaService} 所有实例存放类. + * + * @author monch + * created on 2024/9/6 + */ +public interface WxMaMultiServices { + /** + * 通过租户 Id 获取 WxMaService + * + * @param tenantId 租户 Id + * @return WxMaService + */ + WxMaService getWxMaService(String tenantId); + + /** + * 根据租户 Id,从列表中移除一个 WxMaService 实例 + * + * @param tenantId 租户 Id + */ + void removeWxMaService(String tenantId); +} diff --git a/spring-boot-starters/wx-java-miniapp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/miniapp/service/WxMaMultiServicesImpl.java b/spring-boot-starters/wx-java-miniapp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/miniapp/service/WxMaMultiServicesImpl.java new file mode 100644 index 0000000000..913a371f52 --- /dev/null +++ b/spring-boot-starters/wx-java-miniapp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/miniapp/service/WxMaMultiServicesImpl.java @@ -0,0 +1,36 @@ +package com.binarywang.spring.starter.wxjava.miniapp.service; + +import cn.binarywang.wx.miniapp.api.WxMaService; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * 微信小程序 {@link com.binarywang.spring.starter.wxjava.miniapp.service.WxMaMultiServices} 默认实现 + * + * @author monch + * created on 2024/9/6 + */ +public class WxMaMultiServicesImpl implements com.binarywang.spring.starter.wxjava.miniapp.service.WxMaMultiServices { + private final Map services = new ConcurrentHashMap<>(); + + @Override + public WxMaService getWxMaService(String tenantId) { + return this.services.get(tenantId); + } + + /** + * 根据租户 Id,添加一个 WxMaService 到列表 + * + * @param tenantId 租户 Id + * @param wxMaService WxMaService 实例 + */ + public void addWxMaService(String tenantId, WxMaService wxMaService) { + this.services.put(tenantId, wxMaService); + } + + @Override + public void removeWxMaService(String tenantId) { + this.services.remove(tenantId); + } +} diff --git a/spring-boot-starters/wx-java-miniapp-multi-spring-boot-starter/src/main/resources/META-INF/spring.factories b/spring-boot-starters/wx-java-miniapp-multi-spring-boot-starter/src/main/resources/META-INF/spring.factories new file mode 100644 index 0000000000..bc9bec9bfb --- /dev/null +++ b/spring-boot-starters/wx-java-miniapp-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.miniapp.autoconfigure.WxMaMultiAutoConfiguration diff --git a/spring-boot-starters/wx-java-miniapp-multi-spring-boot-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/spring-boot-starters/wx-java-miniapp-multi-spring-boot-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 0000000000..3023f06bdd --- /dev/null +++ b/spring-boot-starters/wx-java-miniapp-multi-spring-boot-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1 @@ +com.binarywang.spring.starter.wxjava.miniapp.autoconfigure.WxMaMultiAutoConfiguration diff --git a/spring-boot-starters/wx-java-miniapp-spring-boot-starter/README.md b/spring-boot-starters/wx-java-miniapp-spring-boot-starter/README.md index fe75ef4e3e..cbf0b53925 100644 --- a/spring-boot-starters/wx-java-miniapp-spring-boot-starter/README.md +++ b/spring-boot-starters/wx-java-miniapp-spring-boot-starter/README.md @@ -1,26 +1,36 @@ -# 使用说明 -1. 在自己的Spring Boot项目里,引入maven依赖 -```xml +# wx-java-miniapp-spring-boot-starter +## 快速开始 +1. 引入依赖 + ```xml com.github.binarywang wx-java-miniapp-spring-boot-starter ${version} - ``` -2. 添加配置(application.yml) -```yml -wx: - miniapp: - appid: 111 - secret: 111 - token: 111 - aesKey: 111 - msgDataFormat: JSON -``` - - - - - - + ``` +2. 添加配置(application.properties) + ```properties + # 小程序配置(必填) + wx.miniapp.appid = appId + wx.miniapp.secret = @secret + wx.miniapp.token = @token + wx.miniapp.aesKey = @aesKey + wx.miniapp.msgDataFormat = @msgDataFormat # 消息格式,XML或者JSON. + wx.miniapp.use-stable-access-token=@useStableAccessToken + # 存储配置redis(可选) + # 注意: 指定redis.host值后不会使用容器注入的redis连接(JedisPool) + wx.miniapp.config-storage.type = Jedis # 配置类型: Memory(默认), Jedis, RedisTemplate + wx.miniapp.config-storage.key-prefix = wa # 相关redis前缀配置: wa(默认) + wx.miniapp.config-storage.redis.host = 127.0.0.1 + wx.miniapp.config-storage.redis.port = 6379 + # http客户端配置 + wx.miniapp.config-storage.http-client-type=HttpClient # http客户端类型: HttpClient(默认), OkHttp, JoddHttp + wx.miniapp.config-storage.http-proxy-host= + wx.miniapp.config-storage.http-proxy-port= + wx.miniapp.config-storage.http-proxy-username= + wx.miniapp.config-storage.http-proxy-password= + ``` +3. 自动注入的类型 +- `WxMaService` +- `WxMaConfig` 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 c538357675..bcc61b0309 100644 --- a/spring-boot-starters/wx-java-miniapp-spring-boot-starter/pom.xml +++ b/spring-boot-starters/wx-java-miniapp-spring-boot-starter/pom.xml @@ -1,11 +1,10 @@ - wx-java-spring-boot-starters com.github.binarywang - 3.6.0 + 4.8.0 4.0.0 @@ -19,6 +18,31 @@ weixin-java-miniapp ${project.version}
+ + redis.clients + jedis + provided + + + org.redisson + redisson + provided + + + org.springframework.data + spring-data-redis + provided + + + org.jodd + jodd-http + provided + + + com.squareup.okhttp3 + okhttp + provided + diff --git a/spring-boot-starters/wx-java-miniapp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/miniapp/config/WxMaAutoConfiguration.java b/spring-boot-starters/wx-java-miniapp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/miniapp/config/WxMaAutoConfiguration.java index 109b398d13..67a7efaecf 100644 --- a/spring-boot-starters/wx-java-miniapp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/miniapp/config/WxMaAutoConfiguration.java +++ b/spring-boot-starters/wx-java-miniapp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/miniapp/config/WxMaAutoConfiguration.java @@ -1,50 +1,21 @@ package com.binarywang.spring.starter.wxjava.miniapp.config; -import cn.binarywang.wx.miniapp.api.WxMaService; -import cn.binarywang.wx.miniapp.api.impl.WxMaServiceImpl; -import cn.binarywang.wx.miniapp.config.WxMaConfig; -import cn.binarywang.wx.miniapp.config.impl.WxMaDefaultConfigImpl; import com.binarywang.spring.starter.wxjava.miniapp.properties.WxMaProperties; -import lombok.AllArgsConstructor; -import org.apache.commons.lang3.StringUtils; -import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; /** * 自动配置. * * @author Binary Wang - * @date 2019-08-10 + * created on 2019-08-10 */ -@AllArgsConstructor @Configuration -@ConditionalOnClass(WxMaService.class) @EnableConfigurationProperties(WxMaProperties.class) -@ConditionalOnProperty(prefix = "wx.miniapp", value = "enabled", matchIfMissing = true) +@Import({ + WxMaStorageAutoConfiguration.class, + WxMaServiceAutoConfiguration.class +}) public class WxMaAutoConfiguration { - private WxMaProperties properties; - - /** - * 小程序service. - * - * @return 小程序service - */ - @Bean - @ConditionalOnMissingBean(WxMaService.class) - public WxMaService service() { - WxMaDefaultConfigImpl config = new WxMaDefaultConfigImpl(); - config.setAppid(StringUtils.trimToNull(this.properties.getAppid())); - config.setSecret(StringUtils.trimToNull(this.properties.getSecret())); - config.setToken(StringUtils.trimToNull(this.properties.getToken())); - config.setAesKey(StringUtils.trimToNull(this.properties.getAesKey())); - config.setMsgDataFormat(StringUtils.trimToNull(this.properties.getMsgDataFormat())); - - final WxMaServiceImpl service = new WxMaServiceImpl(); - service.setWxMaConfig(config); - return service; - } } diff --git a/spring-boot-starters/wx-java-miniapp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/miniapp/config/WxMaServiceAutoConfiguration.java b/spring-boot-starters/wx-java-miniapp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/miniapp/config/WxMaServiceAutoConfiguration.java new file mode 100644 index 0000000000..f03d3f1493 --- /dev/null +++ b/spring-boot-starters/wx-java-miniapp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/miniapp/config/WxMaServiceAutoConfiguration.java @@ -0,0 +1,60 @@ +package com.binarywang.spring.starter.wxjava.miniapp.config; + +import cn.binarywang.wx.miniapp.api.WxMaService; +import cn.binarywang.wx.miniapp.api.impl.WxMaServiceHttpClientImpl; +import cn.binarywang.wx.miniapp.api.impl.WxMaServiceHttpComponentsImpl; +import cn.binarywang.wx.miniapp.api.impl.WxMaServiceImpl; +import cn.binarywang.wx.miniapp.api.impl.WxMaServiceJoddHttpImpl; +import cn.binarywang.wx.miniapp.api.impl.WxMaServiceOkHttpImpl; +import cn.binarywang.wx.miniapp.config.WxMaConfig; +import com.binarywang.spring.starter.wxjava.miniapp.enums.HttpClientType; +import com.binarywang.spring.starter.wxjava.miniapp.properties.WxMaProperties; +import lombok.AllArgsConstructor; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * 微信小程序平台相关服务自动注册. + * + * @author someone TaoYu + */ +@Configuration +@AllArgsConstructor +public class WxMaServiceAutoConfiguration { + + private final WxMaProperties wxMaProperties; + + /** + * 小程序service. + * + * @return 小程序service + */ + @Bean + @ConditionalOnMissingBean(WxMaService.class) + @ConditionalOnBean(WxMaConfig.class) + public WxMaService wxMaService(WxMaConfig wxMaConfig) { + HttpClientType httpClientType = wxMaProperties.getConfigStorage().getHttpClientType(); + WxMaService wxMaService; + switch (httpClientType) { + case OkHttp: + wxMaService = new WxMaServiceOkHttpImpl(); + break; + case JoddHttp: + wxMaService = new WxMaServiceJoddHttpImpl(); + break; + case HttpClient: + wxMaService = new WxMaServiceHttpClientImpl(); + break; + case HttpComponents: + wxMaService = new WxMaServiceHttpComponentsImpl(); + break; + default: + wxMaService = new WxMaServiceImpl(); + break; + } + wxMaService.setWxMaConfig(wxMaConfig); + return wxMaService; + } +} diff --git a/spring-boot-starters/wx-java-miniapp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/miniapp/config/WxMaStorageAutoConfiguration.java b/spring-boot-starters/wx-java-miniapp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/miniapp/config/WxMaStorageAutoConfiguration.java new file mode 100644 index 0000000000..0f0477a8b0 --- /dev/null +++ b/spring-boot-starters/wx-java-miniapp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/miniapp/config/WxMaStorageAutoConfiguration.java @@ -0,0 +1,23 @@ +package com.binarywang.spring.starter.wxjava.miniapp.config; + +import com.binarywang.spring.starter.wxjava.miniapp.config.storage.WxMaInJedisConfigStorageConfiguration; +import com.binarywang.spring.starter.wxjava.miniapp.config.storage.WxMaInMemoryConfigStorageConfiguration; +import com.binarywang.spring.starter.wxjava.miniapp.config.storage.WxMaInRedisTemplateConfigStorageConfiguration; +import com.binarywang.spring.starter.wxjava.miniapp.config.storage.WxMaInRedissonConfigStorageConfiguration; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; + +/** + * 微信小程序存储策略自动配置. + * + * @author someone TaoYu + */ +@Configuration +@Import({ + WxMaInMemoryConfigStorageConfiguration.class, + WxMaInJedisConfigStorageConfiguration.class, + WxMaInRedisTemplateConfigStorageConfiguration.class, + WxMaInRedissonConfigStorageConfiguration.class +}) +public class WxMaStorageAutoConfiguration { +} 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 new file mode 100644 index 0000000000..abcd83e848 --- /dev/null +++ b/spring-boot-starters/wx-java-miniapp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/miniapp/config/storage/AbstractWxMaConfigStorageConfiguration.java @@ -0,0 +1,58 @@ +package com.binarywang.spring.starter.wxjava.miniapp.config.storage; + +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; + +/** + * @author yl TaoYu + */ +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()); + config.setHttpProxyUsername(configStorageProperties.getHttpProxyUsername()); + config.setHttpProxyPassword(configStorageProperties.getHttpProxyPassword()); + if (configStorageProperties.getHttpProxyPort() != null) { + config.setHttpProxyPort(configStorageProperties.getHttpProxyPort()); + } + + // 设置自定义的HttpClient超时配置 + ApacheHttpClientBuilder clientBuilder = config.getApacheHttpClientBuilder(); + if (clientBuilder == null) { + clientBuilder = DefaultApacheHttpClientBuilder.get(); + } + if (clientBuilder instanceof DefaultApacheHttpClientBuilder) { + DefaultApacheHttpClientBuilder defaultBuilder = (DefaultApacheHttpClientBuilder) clientBuilder; + defaultBuilder.setConnectionTimeout(storage.getConnectionTimeout()); + defaultBuilder.setSoTimeout(storage.getSoTimeout()); + defaultBuilder.setConnectionRequestTimeout(storage.getConnectionRequestTimeout()); + config.setApacheHttpClientBuilder(defaultBuilder); + } + + int maxRetryTimes = configStorageProperties.getMaxRetryTimes(); + if (configStorageProperties.getMaxRetryTimes() < 0) { + maxRetryTimes = 0; + } + int retrySleepMillis = configStorageProperties.getRetrySleepMillis(); + if (retrySleepMillis < 0) { + retrySleepMillis = 1000; + } + config.setRetrySleepMillis(retrySleepMillis); + config.setMaxRetryTimes(maxRetryTimes); + return config; + } +} diff --git a/spring-boot-starters/wx-java-miniapp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/miniapp/config/storage/WxMaInJedisConfigStorageConfiguration.java b/spring-boot-starters/wx-java-miniapp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/miniapp/config/storage/WxMaInJedisConfigStorageConfiguration.java new file mode 100644 index 0000000000..93b901ebf8 --- /dev/null +++ b/spring-boot-starters/wx-java-miniapp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/miniapp/config/storage/WxMaInJedisConfigStorageConfiguration.java @@ -0,0 +1,72 @@ +package com.binarywang.spring.starter.wxjava.miniapp.config.storage; + +import cn.binarywang.wx.miniapp.config.WxMaConfig; +import cn.binarywang.wx.miniapp.config.impl.WxMaRedisBetterConfigImpl; +import com.binarywang.spring.starter.wxjava.miniapp.properties.RedisProperties; +import com.binarywang.spring.starter.wxjava.miniapp.properties.WxMaProperties; +import lombok.RequiredArgsConstructor; +import me.chanjar.weixin.common.redis.JedisWxRedisOps; +import me.chanjar.weixin.common.redis.WxRedisOps; +import org.apache.commons.lang3.StringUtils; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import redis.clients.jedis.JedisPool; +import redis.clients.jedis.JedisPoolConfig; + +/** + * @author yl TaoYu + */ +@Configuration +@ConditionalOnProperty(prefix = WxMaProperties.PREFIX + ".config-storage", name = "type", havingValue = "jedis") +@ConditionalOnClass({JedisPool.class, JedisPoolConfig.class}) +@RequiredArgsConstructor +public class WxMaInJedisConfigStorageConfiguration extends AbstractWxMaConfigStorageConfiguration { + private final WxMaProperties properties; + private final ApplicationContext applicationContext; + + @Bean + @ConditionalOnMissingBean(WxMaConfig.class) + public WxMaConfig wxMaConfig() { + WxMaRedisBetterConfigImpl config = getWxMaRedisBetterConfigImpl(); + return this.config(config, properties); + } + + private WxMaRedisBetterConfigImpl getWxMaRedisBetterConfigImpl() { + RedisProperties redisProperties = properties.getConfigStorage().getRedis(); + JedisPool jedisPool; + if (redisProperties != null && StringUtils.isNotEmpty(redisProperties.getHost())) { + jedisPool = getJedisPool(); + } else { + jedisPool = applicationContext.getBean(JedisPool.class); + } + WxRedisOps redisOps = new JedisWxRedisOps(jedisPool); + return new WxMaRedisBetterConfigImpl(redisOps, properties.getConfigStorage().getKeyPrefix()); + } + + private JedisPool getJedisPool() { + WxMaProperties.ConfigStorage storage = properties.getConfigStorage(); + RedisProperties redis = storage.getRedis(); + + JedisPoolConfig config = new JedisPoolConfig(); + if (redis.getMaxActive() != null) { + config.setMaxTotal(redis.getMaxActive()); + } + if (redis.getMaxIdle() != null) { + config.setMaxIdle(redis.getMaxIdle()); + } + if (redis.getMaxWaitMillis() != null) { + config.setMaxWaitMillis(redis.getMaxWaitMillis()); + } + if (redis.getMinIdle() != null) { + config.setMinIdle(redis.getMinIdle()); + } + config.setTestOnBorrow(true); + config.setTestWhileIdle(true); + + return new JedisPool(config, redis.getHost(), redis.getPort(), redis.getTimeout(), redis.getPassword(), redis.getDatabase()); + } +} diff --git a/spring-boot-starters/wx-java-miniapp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/miniapp/config/storage/WxMaInMemoryConfigStorageConfiguration.java b/spring-boot-starters/wx-java-miniapp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/miniapp/config/storage/WxMaInMemoryConfigStorageConfiguration.java new file mode 100644 index 0000000000..44e727af83 --- /dev/null +++ b/spring-boot-starters/wx-java-miniapp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/miniapp/config/storage/WxMaInMemoryConfigStorageConfiguration.java @@ -0,0 +1,28 @@ +package com.binarywang.spring.starter.wxjava.miniapp.config.storage; + +import cn.binarywang.wx.miniapp.config.WxMaConfig; +import cn.binarywang.wx.miniapp.config.impl.WxMaDefaultConfigImpl; +import com.binarywang.spring.starter.wxjava.miniapp.properties.WxMaProperties; +import lombok.RequiredArgsConstructor; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * @author yl TaoYu + */ +@Configuration +@ConditionalOnProperty(prefix = WxMaProperties.PREFIX + ".config-storage", name = "type", + matchIfMissing = true, havingValue = "memory") +@RequiredArgsConstructor +public class WxMaInMemoryConfigStorageConfiguration extends AbstractWxMaConfigStorageConfiguration { + private final WxMaProperties properties; + + @Bean + @ConditionalOnMissingBean(WxMaConfig.class) + public WxMaConfig wxMaConfig() { + WxMaDefaultConfigImpl config = new WxMaDefaultConfigImpl(); + return this.config(config, properties); + } +} diff --git a/spring-boot-starters/wx-java-miniapp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/miniapp/config/storage/WxMaInRedisTemplateConfigStorageConfiguration.java b/spring-boot-starters/wx-java-miniapp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/miniapp/config/storage/WxMaInRedisTemplateConfigStorageConfiguration.java new file mode 100644 index 0000000000..81cf8c6559 --- /dev/null +++ b/spring-boot-starters/wx-java-miniapp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/miniapp/config/storage/WxMaInRedisTemplateConfigStorageConfiguration.java @@ -0,0 +1,40 @@ +package com.binarywang.spring.starter.wxjava.miniapp.config.storage; + +import cn.binarywang.wx.miniapp.config.WxMaConfig; +import cn.binarywang.wx.miniapp.config.impl.WxMaRedisBetterConfigImpl; +import com.binarywang.spring.starter.wxjava.miniapp.properties.WxMaProperties; +import lombok.RequiredArgsConstructor; +import me.chanjar.weixin.common.redis.RedisTemplateWxRedisOps; +import me.chanjar.weixin.common.redis.WxRedisOps; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.core.StringRedisTemplate; + +/** + * @author yl TaoYu + */ +@Configuration +@ConditionalOnProperty(prefix = WxMaProperties.PREFIX + ".config-storage", name = "type", havingValue = "redistemplate") +@ConditionalOnClass(StringRedisTemplate.class) +@RequiredArgsConstructor +public class WxMaInRedisTemplateConfigStorageConfiguration extends AbstractWxMaConfigStorageConfiguration { + private final WxMaProperties properties; + private final ApplicationContext applicationContext; + + @Bean + @ConditionalOnMissingBean(WxMaConfig.class) + public WxMaConfig wxMaConfig() { + WxMaRedisBetterConfigImpl config = getWxMaInRedisTemplateConfigStorage(); + return this.config(config, properties); + } + + private WxMaRedisBetterConfigImpl getWxMaInRedisTemplateConfigStorage() { + StringRedisTemplate redisTemplate = applicationContext.getBean(StringRedisTemplate.class); + WxRedisOps redisOps = new RedisTemplateWxRedisOps(redisTemplate); + return new WxMaRedisBetterConfigImpl(redisOps, properties.getConfigStorage().getKeyPrefix()); + } +} diff --git a/spring-boot-starters/wx-java-miniapp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/miniapp/config/storage/WxMaInRedissonConfigStorageConfiguration.java b/spring-boot-starters/wx-java-miniapp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/miniapp/config/storage/WxMaInRedissonConfigStorageConfiguration.java new file mode 100644 index 0000000000..2a030b5f9e --- /dev/null +++ b/spring-boot-starters/wx-java-miniapp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/miniapp/config/storage/WxMaInRedissonConfigStorageConfiguration.java @@ -0,0 +1,61 @@ +package com.binarywang.spring.starter.wxjava.miniapp.config.storage; + +import cn.binarywang.wx.miniapp.config.WxMaConfig; +import cn.binarywang.wx.miniapp.config.impl.WxMaRedissonConfigImpl; +import com.binarywang.spring.starter.wxjava.miniapp.properties.RedisProperties; +import com.binarywang.spring.starter.wxjava.miniapp.properties.WxMaProperties; +import lombok.RequiredArgsConstructor; +import org.apache.commons.lang3.StringUtils; +import org.redisson.Redisson; +import org.redisson.api.RedissonClient; +import org.redisson.config.Config; +import org.redisson.config.TransportMode; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * @author yl TaoYu + */ +@Configuration +@ConditionalOnProperty(prefix = WxMaProperties.PREFIX + ".config-storage", name = "type", havingValue = "redisson") +@ConditionalOnClass({Redisson.class, RedissonClient.class}) +@RequiredArgsConstructor +public class WxMaInRedissonConfigStorageConfiguration extends AbstractWxMaConfigStorageConfiguration { + private final WxMaProperties properties; + private final ApplicationContext applicationContext; + + @Bean + @ConditionalOnMissingBean(WxMaConfig.class) + public WxMaConfig wxMaConfig() { + WxMaRedissonConfigImpl config = getWxMaInRedissonConfigStorage(); + return this.config(config, properties); + } + + private WxMaRedissonConfigImpl getWxMaInRedissonConfigStorage() { + RedisProperties redisProperties = properties.getConfigStorage().getRedis(); + RedissonClient redissonClient; + if (redisProperties != null && StringUtils.isNotEmpty(redisProperties.getHost())) { + redissonClient = getRedissonClient(); + } else { + redissonClient = applicationContext.getBean(RedissonClient.class); + } + return new WxMaRedissonConfigImpl(redissonClient, properties.getConfigStorage().getKeyPrefix()); + } + + private RedissonClient getRedissonClient() { + WxMaProperties.ConfigStorage storage = properties.getConfigStorage(); + RedisProperties 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-miniapp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/miniapp/enums/HttpClientType.java b/spring-boot-starters/wx-java-miniapp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/miniapp/enums/HttpClientType.java new file mode 100644 index 0000000000..48549e4399 --- /dev/null +++ b/spring-boot-starters/wx-java-miniapp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/miniapp/enums/HttpClientType.java @@ -0,0 +1,26 @@ +package com.binarywang.spring.starter.wxjava.miniapp.enums; + +/** + * httpclient类型. + * + * @author Binary Wang + * created on 2020-05-25 + */ +public enum HttpClientType { + /** + * HttpClient (Apache HttpClient 4.x). + */ + HttpClient, + /** + * OkHttp. + */ + OkHttp, + /** + * JoddHttp. + */ + JoddHttp, + /** + * HttpComponents (Apache HttpClient 5.x). + */ + HttpComponents, +} diff --git a/spring-boot-starters/wx-java-miniapp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/miniapp/enums/StorageType.java b/spring-boot-starters/wx-java-miniapp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/miniapp/enums/StorageType.java new file mode 100644 index 0000000000..31c6e4b602 --- /dev/null +++ b/spring-boot-starters/wx-java-miniapp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/miniapp/enums/StorageType.java @@ -0,0 +1,26 @@ +package com.binarywang.spring.starter.wxjava.miniapp.enums; + +/** + * storage类型. + * + * @author Binary Wang + * created on 2020-05-25 + */ +public enum StorageType { + /** + * 内存. + */ + Memory, + /** + * redis(JedisClient). + */ + Jedis, + /** + * redis(Redisson). + */ + Redisson, + /** + * redis(RedisTemplate). + */ + RedisTemplate +} diff --git a/spring-boot-starters/wx-java-miniapp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/miniapp/properties/RedisProperties.java b/spring-boot-starters/wx-java-miniapp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/miniapp/properties/RedisProperties.java new file mode 100644 index 0000000000..75e3740a19 --- /dev/null +++ b/spring-boot-starters/wx-java-miniapp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/miniapp/properties/RedisProperties.java @@ -0,0 +1,43 @@ +package com.binarywang.spring.starter.wxjava.miniapp.properties; + +import lombok.Data; + +/** + * redis 配置. + * + * @author Binary Wang + * created on 2020-08-30 + */ +@Data +public class RedisProperties { + + /** + * 主机地址.不填则从spring容器内获取JedisPool + */ + 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-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 6477e61fa2..82f1500941 100644 --- a/spring-boot-starters/wx-java-miniapp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/miniapp/properties/WxMaProperties.java +++ b/spring-boot-starters/wx-java-miniapp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/miniapp/properties/WxMaProperties.java @@ -1,17 +1,24 @@ package com.binarywang.spring.starter.wxjava.miniapp.properties; +import com.binarywang.spring.starter.wxjava.miniapp.enums.HttpClientType; +import com.binarywang.spring.starter.wxjava.miniapp.enums.StorageType; import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.NestedConfigurationProperty; + +import static com.binarywang.spring.starter.wxjava.miniapp.properties.WxMaProperties.PREFIX; /** * 属性配置类. * * @author Binary Wang - * @date 2019-08-10 + * created on 2019-08-10 */ @Data -@ConfigurationProperties(prefix = "wx.miniapp") +@ConfigurationProperties(prefix = PREFIX) public class WxMaProperties { + public static final String PREFIX = "wx.miniapp"; + /** * 设置微信小程序的appid. */ @@ -36,4 +43,102 @@ public class WxMaProperties { * 消息格式,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; + + /** + * 存储策略 + */ + private final ConfigStorage configStorage = new ConfigStorage(); + + @Data + public static class ConfigStorage { + + /** + * 存储类型. + */ + private StorageType type = StorageType.Memory; + + /** + * 指定key前缀. + */ + private String keyPrefix = "wa"; + + /** + * redis连接配置. + */ + @NestedConfigurationProperty + private final RedisProperties redis = new RedisProperties(); + + /** + * http客户端类型. + */ + private HttpClientType httpClientType = HttpClientType.HttpComponents; + + /** + * http代理主机. + */ + private String httpProxyHost; + + /** + * http代理端口. + */ + private Integer httpProxyPort; + + /** + * http代理用户名. + */ + private String httpProxyUsername; + + /** + * http代理密码. + */ + private String httpProxyPassword; + + /** + * http 请求重试间隔 + *
+     *   {@link cn.binarywang.wx.miniapp.api.impl.BaseWxMaServiceImpl#setRetrySleepMillis(int)}
+     * 
+ */ + private int retrySleepMillis = 1000; + /** + * http 请求最大重试次数 + *
+     *   {@link cn.binarywang.wx.miniapp.api.impl.BaseWxMaServiceImpl#setMaxRetryTimes(int)}
+     * 
+ */ + 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-miniapp-spring-boot-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/spring-boot-starters/wx-java-miniapp-spring-boot-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 0000000000..6644fa9701 --- /dev/null +++ b/spring-boot-starters/wx-java-miniapp-spring-boot-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1 @@ +com.binarywang.spring.starter.wxjava.miniapp.config.WxMaAutoConfiguration diff --git a/spring-boot-starters/wx-java-mp-multi-spring-boot-starter/README.md b/spring-boot-starters/wx-java-mp-multi-spring-boot-starter/README.md new file mode 100644 index 0000000000..26b593addd --- /dev/null +++ b/spring-boot-starters/wx-java-mp-multi-spring-boot-starter/README.md @@ -0,0 +1,100 @@ +# wx-java-mp-multi-spring-boot-starter + +## 快速开始 + +1. 引入依赖 + ```xml + + com.github.binarywang + wx-java-mp-multi-spring-boot-starter + ${version} + + ``` +2. 添加配置(application.properties) + ```properties + # 公众号配置 + ## 应用 1 配置(必填) + wx.mp.apps.tenantId1.app-id=appId + wx.mp.apps.tenantId1.app-secret=@secret + ## 选填 + wx.mp.apps.tenantId1.token=@token + wx.mp.apps.tenantId1.aes-key=@aesKey + wx.mp.apps.tenantId1.use-stable-access-token=@useStableAccessToken + ## 应用 2 配置(必填) + wx.mp.apps.tenantId2.app-id=@appId + wx.mp.apps.tenantId2.app-secret =@secret + ## 选填 + wx.mp.apps.tenantId2.token=@token + wx.mp.apps.tenantId2.aes-key=@aesKey + wx.mp.apps.tenantId2.use-stable-access-token=@useStableAccessToken + + # ConfigStorage 配置(选填) + ## 配置类型: memory(默认), jedis, redisson, redis_template + wx.mp.config-storage.type=memory + ## 相关redis前缀配置: wx:mp:multi(默认) + wx.mp.config-storage.key-prefix=wx:mp:multi + wx.mp.config-storage.redis.host=127.0.0.1 + wx.mp.config-storage.redis.port=6379 + ## 单机和 sentinel 同时存在时,优先使用sentinel配置 + # wx.mp.config-storage.redis.sentinel-ips=127.0.0.1:16379,127.0.0.1:26379 + # wx.mp.config-storage.redis.sentinel-name=mymaster + + # http 客户端配置(选填) + ## # http客户端类型: http_client(默认), ok_http, jodd_http + wx.mp.config-storage.http-client-type=http_client + wx.mp.config-storage.http-proxy-host= + wx.mp.config-storage.http-proxy-port= + wx.mp.config-storage.http-proxy-username= + wx.mp.config-storage.http-proxy-password= + ## 最大重试次数,默认:5 次,如果小于 0,则为 0 + wx.mp.config-storage.max-retry-times=5 + ## 重试时间间隔步进,默认:1000 毫秒,如果小于 0,则为 1000 + wx.mp.config-storage.retry-sleep-millis=1000 + + # 公众号地址 host 配置 + # wx.mp.hosts.api-host=http://proxy.com/ + # wx.mp.hosts.open-host=http://proxy.com/ + # wx.mp.hosts.mp-host=http://proxy.com/ + ``` +3. 自动注入的类型:`WxMpMultiServices` + +4. 使用样例 + +```java +import com.binarywang.spring.starter.wxjava.mp.service.WxMaMultiServices; +import me.chanjar.weixin.mp.api.WxMpService; +import me.chanjar.weixin.mp.api.WxMpUserService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +@Service +public class DemoService { + @Autowired + private WxMpMultiServices wxMpMultiServices; + + public void test() { + // 应用 1 的 WxMpService + WxMpService wxMpService1 = wxMpMultiServices.getWxMpService("tenantId1"); + WxMpUserService userService1 = wxMpService1.getUserService(); + userService1.userInfo("xxx"); + // todo ... + + // 应用 2 的 WxMpService + WxMpService wxMpService2 = wxMpMultiServices.getWxMpService("tenantId2"); + WxMpUserService userService2 = wxMpService2.getUserService(); + userService2.userInfo("xxx"); + // todo ... + + // 应用 3 的 WxMpService + WxMpService wxMpService3 = wxMpMultiServices.getWxMpService("tenantId3"); + // 判断是否为空 + if (wxMpService3 == null) { + // todo wxMpService3 为空,请先配置 tenantId3 微信公众号应用参数 + return; + } + WxMpUserService userService3 = wxMpService3.getUserService(); + userService3.userInfo("xxx"); + // todo ... + } +} +``` 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 new file mode 100644 index 0000000000..6323ae4b6a --- /dev/null +++ b/spring-boot-starters/wx-java-mp-multi-spring-boot-starter/pom.xml @@ -0,0 +1,77 @@ + + + + wx-java-spring-boot-starters + com.github.binarywang + 4.8.0 + + 4.0.0 + + wx-java-mp-multi-spring-boot-starter + WxJava - Spring Boot Starter for MP::支持多账号配置 + 微信公众号开发的 Spring Boot Starter::支持多账号配置 + + + + com.github.binarywang + weixin-java-mp + ${project.version} + + + redis.clients + jedis + provided + + + org.redisson + redisson + provided + + + org.springframework.data + spring-data-redis + provided + + + org.jodd + jodd-http + provided + + + com.squareup.okhttp3 + okhttp + provided + + + org.apache.httpcomponents.client5 + httpclient5 + 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-mp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/autoconfigure/WxMpMultiAutoConfiguration.java b/spring-boot-starters/wx-java-mp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/autoconfigure/WxMpMultiAutoConfiguration.java new file mode 100644 index 0000000000..21ec0925d3 --- /dev/null +++ b/spring-boot-starters/wx-java-mp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/autoconfigure/WxMpMultiAutoConfiguration.java @@ -0,0 +1,14 @@ +package com.binarywang.spring.starter.wxjava.mp.autoconfigure; + +import com.binarywang.spring.starter.wxjava.mp.configuration.WxMpMultiServiceConfiguration; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; + +/** + * @author yl + * created on 2024/1/23 + */ +@Configuration +@Import(WxMpMultiServiceConfiguration.class) +public class WxMpMultiAutoConfiguration { +} diff --git a/spring-boot-starters/wx-java-mp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/configuration/WxMpMultiServiceConfiguration.java b/spring-boot-starters/wx-java-mp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/configuration/WxMpMultiServiceConfiguration.java new file mode 100644 index 0000000000..35a53d0ccd --- /dev/null +++ b/spring-boot-starters/wx-java-mp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/configuration/WxMpMultiServiceConfiguration.java @@ -0,0 +1,27 @@ +package com.binarywang.spring.starter.wxjava.mp.configuration; + +import com.binarywang.spring.starter.wxjava.mp.configuration.services.WxMpInJedisConfiguration; +import com.binarywang.spring.starter.wxjava.mp.configuration.services.WxMpInMemoryConfiguration; +import com.binarywang.spring.starter.wxjava.mp.configuration.services.WxMpInRedisTemplateConfiguration; +import com.binarywang.spring.starter.wxjava.mp.configuration.services.WxMpInRedissonConfiguration; +import com.binarywang.spring.starter.wxjava.mp.properties.WxMpMultiProperties; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; + +/** + * 微信公众号相关服务自动注册 + * + * @author yl + * created on 2024/1/23 + */ +@Configuration +@EnableConfigurationProperties(WxMpMultiProperties.class) +@Import({ + WxMpInJedisConfiguration.class, + WxMpInMemoryConfiguration.class, + WxMpInRedissonConfiguration.class, + WxMpInRedisTemplateConfiguration.class +}) +public class WxMpMultiServiceConfiguration { +} 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 new file mode 100644 index 0000000000..1f431b645d --- /dev/null +++ b/spring-boot-starters/wx-java-mp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/configuration/services/AbstractWxMpConfiguration.java @@ -0,0 +1,165 @@ +package com.binarywang.spring.starter.wxjava.mp.configuration.services; + +import com.binarywang.spring.starter.wxjava.mp.properties.WxMpMultiProperties; +import com.binarywang.spring.starter.wxjava.mp.properties.WxMpSingleProperties; +import com.binarywang.spring.starter.wxjava.mp.service.WxMpMultiServices; +import com.binarywang.spring.starter.wxjava.mp.service.WxMpMultiServicesImpl; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import me.chanjar.weixin.mp.api.WxMpService; +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; +import org.apache.commons.lang3.StringUtils; + +import java.util.Collection; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * WxMpConfigStorage 抽象配置类 + * + * @author yl + * created on 2024/1/23 + */ +@RequiredArgsConstructor +@Slf4j +public abstract class AbstractWxMpConfiguration { + + protected WxMpMultiServices wxMpMultiServices(WxMpMultiProperties wxMpMultiProperties) { + Map appsMap = wxMpMultiProperties.getApps(); + if (appsMap == null || appsMap.isEmpty()) { + log.warn("微信公众号应用参数未配置,通过 WxMpMultiServices#getWxMpService(\"tenantId\")获取实例将返回空"); + return new WxMpMultiServicesImpl(); + } + /** + * 校验 appId 是否唯一,避免使用 redis 缓存 token、ticket 时错乱。 + * + * 查看 {@link me.chanjar.weixin.mp.config.impl.WxMpRedisConfigImpl#setAppId(String)} + */ + Collection apps = appsMap.values(); + if (apps.size() > 1) { + // 校验 appId 是否唯一 + boolean multi = apps.stream() + // 没有 appId,如果不判断是否为空,这里会报 NPE 异常 + .collect(Collectors.groupingBy(c -> c.getAppId() == null ? 0 : c.getAppId(), Collectors.counting())) + .entrySet().stream().anyMatch(e -> e.getValue() > 1); + if (multi) { + throw new RuntimeException("请确保微信公众号配置 appId 的唯一性"); + } + } + WxMpMultiServicesImpl services = new WxMpMultiServicesImpl(); + + Set> entries = appsMap.entrySet(); + for (Map.Entry entry : entries) { + String tenantId = entry.getKey(); + WxMpSingleProperties wxMpSingleProperties = entry.getValue(); + WxMpDefaultConfigImpl storage = this.wxMpConfigStorage(wxMpMultiProperties); + this.configApp(storage, wxMpSingleProperties); + this.configHttp(storage, wxMpMultiProperties.getConfigStorage()); + this.configHost(storage, wxMpMultiProperties.getHosts()); + WxMpService wxMpService = this.wxMpService(storage, wxMpMultiProperties); + services.addWxMpService(tenantId, wxMpService); + } + return services; + } + + /** + * 配置 WxMpDefaultConfigImpl + * + * @param wxMpMultiProperties 参数 + * @return WxMpDefaultConfigImpl + */ + protected abstract WxMpDefaultConfigImpl wxMpConfigStorage(WxMpMultiProperties wxMpMultiProperties); + + public WxMpService wxMpService(WxMpConfigStorage configStorage, WxMpMultiProperties wxMpMultiProperties) { + WxMpMultiProperties.ConfigStorage storage = wxMpMultiProperties.getConfigStorage(); + WxMpMultiProperties.HttpClientType httpClientType = storage.getHttpClientType(); + WxMpService wxMpService; + switch (httpClientType) { + case OK_HTTP: + wxMpService = new WxMpServiceOkHttpImpl(); + break; + case JODD_HTTP: + wxMpService = new WxMpServiceJoddHttpImpl(); + break; + case HTTP_CLIENT: + wxMpService = new WxMpServiceHttpClientImpl(); + break; + case HTTP_COMPONENTS: + wxMpService = new WxMpServiceHttpComponentsImpl(); + break; + default: + wxMpService = new WxMpServiceImpl(); + break; + } + + wxMpService.setWxMpConfigStorage(configStorage); + int maxRetryTimes = storage.getMaxRetryTimes(); + if (maxRetryTimes < 0) { + maxRetryTimes = 0; + } + int retrySleepMillis = storage.getRetrySleepMillis(); + if (retrySleepMillis < 0) { + retrySleepMillis = 1000; + } + wxMpService.setRetrySleepMillis(retrySleepMillis); + wxMpService.setMaxRetryTimes(maxRetryTimes); + return wxMpService; + } + + private void configApp(WxMpDefaultConfigImpl config, WxMpSingleProperties corpProperties) { + String appId = corpProperties.getAppId(); + String appSecret = corpProperties.getAppSecret(); + String token = corpProperties.getToken(); + String aesKey = corpProperties.getAesKey(); + boolean useStableAccessToken = corpProperties.isUseStableAccessToken(); + + config.setAppId(appId); + config.setSecret(appSecret); + if (StringUtils.isNotBlank(token)) { + config.setToken(token); + } + if (StringUtils.isNotBlank(aesKey)) { + config.setAesKey(aesKey); + } + config.setUseStableAccessToken(useStableAccessToken); + } + + private void configHttp(WxMpDefaultConfigImpl config, WxMpMultiProperties.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); + } + } + } + + /** + * wx host config + */ + private void configHost(WxMpDefaultConfigImpl config, WxMpMultiProperties.HostConfig hostConfig) { + if (hostConfig != null) { + String apiHost = hostConfig.getApiHost(); + String mpHost = hostConfig.getMpHost(); + String openHost = hostConfig.getOpenHost(); + WxMpHostConfig wxMpHostConfig = new WxMpHostConfig(); + wxMpHostConfig.setApiHost(StringUtils.isNotBlank(apiHost) ? apiHost : null); + wxMpHostConfig.setMpHost(StringUtils.isNotBlank(mpHost) ? mpHost : null); + wxMpHostConfig.setOpenHost(StringUtils.isNotBlank(openHost) ? openHost : null); + config.setHostConfig(wxMpHostConfig); + } + } +} diff --git a/spring-boot-starters/wx-java-mp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/configuration/services/WxMpInJedisConfiguration.java b/spring-boot-starters/wx-java-mp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/configuration/services/WxMpInJedisConfiguration.java new file mode 100644 index 0000000000..c137d0c087 --- /dev/null +++ b/spring-boot-starters/wx-java-mp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/configuration/services/WxMpInJedisConfiguration.java @@ -0,0 +1,77 @@ +package com.binarywang.spring.starter.wxjava.mp.configuration.services; + +import com.binarywang.spring.starter.wxjava.mp.properties.WxMpMultiProperties; +import com.binarywang.spring.starter.wxjava.mp.properties.WxMpMultiRedisProperties; +import com.binarywang.spring.starter.wxjava.mp.service.WxMpMultiServices; +import lombok.RequiredArgsConstructor; +import me.chanjar.weixin.common.redis.JedisWxRedisOps; +import me.chanjar.weixin.mp.config.impl.WxMpDefaultConfigImpl; +import me.chanjar.weixin.mp.config.impl.WxMpRedisConfigImpl; +import org.apache.commons.lang3.StringUtils; +import org.springframework.boot.autoconfigure.condition.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 2024/1/23 + */ +@Configuration +@ConditionalOnProperty( + prefix = WxMpMultiProperties.PREFIX + ".config-storage", name = "type", havingValue = "jedis" +) +@RequiredArgsConstructor +public class WxMpInJedisConfiguration extends AbstractWxMpConfiguration { + private final WxMpMultiProperties wxMpMultiProperties; + private final ApplicationContext applicationContext; + + @Bean + public WxMpMultiServices wxMpMultiServices() { + return this.wxMpMultiServices(wxMpMultiProperties); + } + + @Override + protected WxMpDefaultConfigImpl wxMpConfigStorage(WxMpMultiProperties wxMpMultiProperties) { + return this.configRedis(wxMpMultiProperties); + } + + private WxMpDefaultConfigImpl configRedis(WxMpMultiProperties wxMpMultiProperties) { + WxMpMultiRedisProperties wxMpMultiRedisProperties = wxMpMultiProperties.getConfigStorage().getRedis(); + JedisPool jedisPool; + if (wxMpMultiRedisProperties != null && StringUtils.isNotEmpty(wxMpMultiRedisProperties.getHost())) { + jedisPool = getJedisPool(wxMpMultiProperties); + } else { + jedisPool = applicationContext.getBean(JedisPool.class); + } + return new WxMpRedisConfigImpl(new JedisWxRedisOps(jedisPool), wxMpMultiProperties.getConfigStorage().getKeyPrefix()); + } + + private JedisPool getJedisPool(WxMpMultiProperties wxMpMultiProperties) { + WxMpMultiProperties.ConfigStorage storage = wxMpMultiProperties.getConfigStorage(); + WxMpMultiRedisProperties redis = storage.getRedis(); + + JedisPoolConfig config = new JedisPoolConfig(); + if (redis.getMaxActive() != null) { + config.setMaxTotal(redis.getMaxActive()); + } + if (redis.getMaxIdle() != null) { + config.setMaxIdle(redis.getMaxIdle()); + } + if (redis.getMaxWaitMillis() != null) { + config.setMaxWaitMillis(redis.getMaxWaitMillis()); + } + if (redis.getMinIdle() != null) { + config.setMinIdle(redis.getMinIdle()); + } + config.setTestOnBorrow(true); + config.setTestWhileIdle(true); + + return new JedisPool(config, redis.getHost(), redis.getPort(), + redis.getTimeout(), redis.getPassword(), redis.getDatabase()); + } +} diff --git a/spring-boot-starters/wx-java-mp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/configuration/services/WxMpInMemoryConfiguration.java b/spring-boot-starters/wx-java-mp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/configuration/services/WxMpInMemoryConfiguration.java new file mode 100644 index 0000000000..cd90eba114 --- /dev/null +++ b/spring-boot-starters/wx-java-mp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/configuration/services/WxMpInMemoryConfiguration.java @@ -0,0 +1,40 @@ +package com.binarywang.spring.starter.wxjava.mp.configuration.services; + +import com.binarywang.spring.starter.wxjava.mp.properties.WxMpMultiProperties; +import com.binarywang.spring.starter.wxjava.mp.service.WxMpMultiServices; +import lombok.RequiredArgsConstructor; +import me.chanjar.weixin.mp.config.impl.WxMpDefaultConfigImpl; +import me.chanjar.weixin.mp.config.impl.WxMpMapConfigImpl; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * 自动装配基于内存策略配置 + * + * @author yl + * created on 2024/1/23 + */ +@Configuration +@ConditionalOnProperty( + prefix = WxMpMultiProperties.PREFIX + ".config-storage", name = "type", havingValue = "memory", matchIfMissing = true +) +@RequiredArgsConstructor +public class WxMpInMemoryConfiguration extends AbstractWxMpConfiguration { + private final WxMpMultiProperties wxMpMultiProperties; + + @Bean + public WxMpMultiServices wxMpMultiServices() { + return this.wxMpMultiServices(wxMpMultiProperties); + } + + @Override + protected WxMpDefaultConfigImpl wxMpConfigStorage(WxMpMultiProperties wxMpMultiProperties) { + return this.configInMemory(); + } + + private WxMpDefaultConfigImpl configInMemory() { + return new WxMpMapConfigImpl(); + // return new WxMpDefaultConfigImpl(); + } +} diff --git a/spring-boot-starters/wx-java-mp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/configuration/services/WxMpInRedisTemplateConfiguration.java b/spring-boot-starters/wx-java-mp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/configuration/services/WxMpInRedisTemplateConfiguration.java new file mode 100644 index 0000000000..fd96176a8a --- /dev/null +++ b/spring-boot-starters/wx-java-mp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/configuration/services/WxMpInRedisTemplateConfiguration.java @@ -0,0 +1,45 @@ +package com.binarywang.spring.starter.wxjava.mp.configuration.services; + +import com.binarywang.spring.starter.wxjava.mp.properties.WxMpMultiProperties; +import com.binarywang.spring.starter.wxjava.mp.service.WxMpMultiServices; +import lombok.RequiredArgsConstructor; +import me.chanjar.weixin.common.redis.RedisTemplateWxRedisOps; +import me.chanjar.weixin.mp.config.impl.WxMpDefaultConfigImpl; +import me.chanjar.weixin.mp.config.impl.WxMpRedisConfigImpl; +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 2024/1/23 + */ +@Configuration +@ConditionalOnProperty( + prefix = WxMpMultiProperties.PREFIX + ".config-storage", name = "type", havingValue = "redis_template" +) +@RequiredArgsConstructor +public class WxMpInRedisTemplateConfiguration extends AbstractWxMpConfiguration { + private final WxMpMultiProperties WxMpMultiProperties; + private final ApplicationContext applicationContext; + + @Bean + public WxMpMultiServices wxMpMultiServices() { + return this.wxMpMultiServices(WxMpMultiProperties); + } + + @Override + protected WxMpDefaultConfigImpl wxMpConfigStorage(WxMpMultiProperties wxMpMultiProperties) { + return this.configRedisTemplate(WxMpMultiProperties); + } + + private WxMpDefaultConfigImpl configRedisTemplate(WxMpMultiProperties wxMpMultiProperties) { + StringRedisTemplate redisTemplate = applicationContext.getBean(StringRedisTemplate.class); + return new WxMpRedisConfigImpl(new RedisTemplateWxRedisOps(redisTemplate), + wxMpMultiProperties.getConfigStorage().getKeyPrefix()); + } +} diff --git a/spring-boot-starters/wx-java-mp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/configuration/services/WxMpInRedissonConfiguration.java b/spring-boot-starters/wx-java-mp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/configuration/services/WxMpInRedissonConfiguration.java new file mode 100644 index 0000000000..a2b606c4a6 --- /dev/null +++ b/spring-boot-starters/wx-java-mp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/configuration/services/WxMpInRedissonConfiguration.java @@ -0,0 +1,67 @@ +package com.binarywang.spring.starter.wxjava.mp.configuration.services; + +import com.binarywang.spring.starter.wxjava.mp.properties.WxMpMultiProperties; +import com.binarywang.spring.starter.wxjava.mp.properties.WxMpMultiRedisProperties; +import com.binarywang.spring.starter.wxjava.mp.service.WxMpMultiServices; +import lombok.RequiredArgsConstructor; +import me.chanjar.weixin.mp.config.impl.WxMpDefaultConfigImpl; +import me.chanjar.weixin.mp.config.impl.WxMpRedissonConfigImpl; +import org.apache.commons.lang3.StringUtils; +import org.redisson.Redisson; +import org.redisson.api.RedissonClient; +import org.redisson.config.Config; +import org.redisson.config.TransportMode; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * 自动装配基于 redisson 策略配置 + * + * @author yl + * created on 2024/1/23 + */ +@Configuration +@ConditionalOnProperty( + prefix = WxMpMultiProperties.PREFIX + ".config-storage", name = "type", havingValue = "redisson" +) +@RequiredArgsConstructor +public class WxMpInRedissonConfiguration extends AbstractWxMpConfiguration { + private final WxMpMultiProperties wxMpMultiProperties; + private final ApplicationContext applicationContext; + + @Bean + public WxMpMultiServices wxMpMultiServices() { + return this.wxMpMultiServices(wxMpMultiProperties); + } + + @Override + protected WxMpDefaultConfigImpl wxMpConfigStorage(WxMpMultiProperties wxMpMultiProperties) { + return this.configRedisson(wxMpMultiProperties); + } + + private WxMpDefaultConfigImpl configRedisson(WxMpMultiProperties wxMpMultiProperties) { + WxMpMultiRedisProperties redisProperties = wxMpMultiProperties.getConfigStorage().getRedis(); + RedissonClient redissonClient; + if (redisProperties != null && StringUtils.isNotEmpty(redisProperties.getHost())) { + redissonClient = getRedissonClient(wxMpMultiProperties); + } else { + redissonClient = applicationContext.getBean(RedissonClient.class); + } + return new WxMpRedissonConfigImpl(redissonClient, wxMpMultiProperties.getConfigStorage().getKeyPrefix()); + } + + private RedissonClient getRedissonClient(WxMpMultiProperties wxMpMultiProperties) { + WxMpMultiProperties.ConfigStorage storage = wxMpMultiProperties.getConfigStorage(); + WxMpMultiRedisProperties 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-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 new file mode 100644 index 0000000000..8b2fa58aa3 --- /dev/null +++ b/spring-boot-starters/wx-java-mp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/properties/WxMpMultiProperties.java @@ -0,0 +1,158 @@ +package com.binarywang.spring.starter.wxjava.mp.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 2024/1/23 + */ +@Data +@NoArgsConstructor +@ConfigurationProperties(WxMpMultiProperties.PREFIX) +public class WxMpMultiProperties implements Serializable { + private static final long serialVersionUID = -5358245184407791011L; + public static final String PREFIX = "wx.mp"; + + private Map apps = new HashMap<>(); + + /** + * 自定义host配置 + */ + private HostConfig hosts; + + /** + * 存储策略 + */ + private final ConfigStorage configStorage = new ConfigStorage(); + + @Data + @NoArgsConstructor + public static class HostConfig implements Serializable { + private static final long serialVersionUID = -4172767630740346001L; + + /** + * 对应于:https://api.weixin.qq.com + */ + private String apiHost; + + /** + * 对应于:https://open.weixin.qq.com + */ + private String openHost; + + /** + * 对应于:https://mp.weixin.qq.com + */ + private String mpHost; + } + + @Data + @NoArgsConstructor + public static class ConfigStorage implements Serializable { + private static final long serialVersionUID = 4815731027000065434L; + + /** + * 存储类型. + */ + private StorageType type = StorageType.MEMORY; + + /** + * 指定key前缀. + */ + private String keyPrefix = "wx:mp:multi"; + + /** + * redis连接配置. + */ + @NestedConfigurationProperty + private final WxMpMultiRedisProperties redis = new WxMpMultiRedisProperties(); + + /** + * 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.mp.api.WxMpService#setMaxRetryTimes(int)}
+     *   {@link me.chanjar.weixin.mp.api.impl.BaseWxMpServiceImpl#setMaxRetryTimes(int)}
+     * 
+ */ + private int maxRetryTimes = 5; + + /** + * http 请求重试间隔 + *
+     *   {@link me.chanjar.weixin.mp.api.WxMpService#setRetrySleepMillis(int)}
+     *   {@link me.chanjar.weixin.mp.api.impl.BaseWxMpServiceImpl#setRetrySleepMillis(int)}
+     * 
+ */ + private int retrySleepMillis = 1000; + } + + public enum StorageType { + /** + * 内存 + */ + MEMORY, + /** + * jedis + */ + JEDIS, + /** + * redisson + */ + REDISSON, + /** + * redisTemplate + */ + REDIS_TEMPLATE + } + + public enum HttpClientType { + /** + * HttpClient + */ + HTTP_CLIENT, + /** + * HttpComponents + */ + HTTP_COMPONENTS, + /** + * OkHttp + */ + OK_HTTP, + /** + * JoddHttp + */ + JODD_HTTP + } +} diff --git a/spring-boot-starters/wx-java-mp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/properties/WxMpMultiRedisProperties.java b/spring-boot-starters/wx-java-mp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/properties/WxMpMultiRedisProperties.java new file mode 100644 index 0000000000..38cae8bdac --- /dev/null +++ b/spring-boot-starters/wx-java-mp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/properties/WxMpMultiRedisProperties.java @@ -0,0 +1,56 @@ +package com.binarywang.spring.starter.wxjava.mp.properties; + +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * @author yl + * created on 2024/1/23 + */ +@Data +@NoArgsConstructor +public class WxMpMultiRedisProperties implements Serializable { + private static final long serialVersionUID = -5924815351660074401L; + + /** + * 主机地址. + */ + private String host = "127.0.0.1"; + + /** + * 端口号. + */ + private int port = 6379; + + /** + * 密码. + */ + private String password; + + /** + * 超时. + */ + private int timeout = 2000; + + /** + * 数据库. + */ + private int database = 0; + + /** + * sentinel ips + */ + private String sentinelIps; + + /** + * sentinel name + */ + private String sentinelName; + + private Integer maxActive; + private Integer maxIdle; + private Integer maxWaitMillis; + private Integer minIdle; +} diff --git a/spring-boot-starters/wx-java-mp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/properties/WxMpSingleProperties.java b/spring-boot-starters/wx-java-mp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/properties/WxMpSingleProperties.java new file mode 100644 index 0000000000..6302784bf0 --- /dev/null +++ b/spring-boot-starters/wx-java-mp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/properties/WxMpSingleProperties.java @@ -0,0 +1,40 @@ +package com.binarywang.spring.starter.wxjava.mp.properties; + +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * @author yl + * created on 2024/1/23 + */ +@Data +@NoArgsConstructor +public class WxMpSingleProperties implements Serializable { + private static final long serialVersionUID = 1980986361098922525L; + /** + * 设置微信公众号的 appid. + */ + private String appId; + + /** + * 设置微信公众号的 app secret. + */ + private String appSecret; + + /** + * 设置微信公众号的 token. + */ + private String token; + + /** + * 设置微信公众号的 EncodingAESKey. + */ + private String aesKey; + + /** + * 是否使用稳定版 Access Token + */ + private boolean useStableAccessToken = false; +} diff --git a/spring-boot-starters/wx-java-mp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/service/WxMpMultiServices.java b/spring-boot-starters/wx-java-mp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/service/WxMpMultiServices.java new file mode 100644 index 0000000000..69122e5277 --- /dev/null +++ b/spring-boot-starters/wx-java-mp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/service/WxMpMultiServices.java @@ -0,0 +1,27 @@ +package com.binarywang.spring.starter.wxjava.mp.service; + + +import me.chanjar.weixin.mp.api.WxMpService; + +/** + * 企业微信 {@link WxMpService} 所有实例存放类. + * + * @author yl + * created on 2024/1/23 + */ +public interface WxMpMultiServices { + /** + * 通过租户 Id 获取 WxMpService + * + * @param tenantId 租户 Id + * @return WxMpService + */ + WxMpService getWxMpService(String tenantId); + + /** + * 根据租户 Id,从列表中移除一个 WxMpService 实例 + * + * @param tenantId 租户 Id + */ + void removeWxMpService(String tenantId); +} diff --git a/spring-boot-starters/wx-java-mp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/service/WxMpMultiServicesImpl.java b/spring-boot-starters/wx-java-mp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/service/WxMpMultiServicesImpl.java new file mode 100644 index 0000000000..e5f358abe2 --- /dev/null +++ b/spring-boot-starters/wx-java-mp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/service/WxMpMultiServicesImpl.java @@ -0,0 +1,36 @@ +package com.binarywang.spring.starter.wxjava.mp.service; + +import me.chanjar.weixin.mp.api.WxMpService; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * 企业微信 {@link WxMpMultiServices} 默认实现 + * + * @author yl + * created on 2024/1/23 + */ +public class WxMpMultiServicesImpl implements WxMpMultiServices { + private final Map services = new ConcurrentHashMap<>(); + + @Override + public WxMpService getWxMpService(String tenantId) { + return this.services.get(tenantId); + } + + /** + * 根据租户 Id,添加一个 WxMpService 到列表 + * + * @param tenantId 租户 Id + * @param wxMpService WxMpService 实例 + */ + public void addWxMpService(String tenantId, WxMpService wxMpService) { + this.services.put(tenantId, wxMpService); + } + + @Override + public void removeWxMpService(String tenantId) { + this.services.remove(tenantId); + } +} diff --git a/spring-boot-starters/wx-java-mp-multi-spring-boot-starter/src/main/resources/META-INF/spring.factories b/spring-boot-starters/wx-java-mp-multi-spring-boot-starter/src/main/resources/META-INF/spring.factories new file mode 100644 index 0000000000..d20dc22dc3 --- /dev/null +++ b/spring-boot-starters/wx-java-mp-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.mp.autoconfigure.WxMpMultiAutoConfiguration diff --git a/spring-boot-starters/wx-java-mp-multi-spring-boot-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/spring-boot-starters/wx-java-mp-multi-spring-boot-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 0000000000..324e3555ba --- /dev/null +++ b/spring-boot-starters/wx-java-mp-multi-spring-boot-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1 @@ +com.binarywang.spring.starter.wxjava.mp.autoconfigure.WxMpMultiAutoConfiguration diff --git a/spring-boot-starters/wx-java-mp-spring-boot-starter/README.md b/spring-boot-starters/wx-java-mp-spring-boot-starter/README.md index ec7f343c62..091912cfad 100644 --- a/spring-boot-starters/wx-java-mp-spring-boot-starter/README.md +++ b/spring-boot-starters/wx-java-mp-spring-boot-starter/README.md @@ -1,5 +1,7 @@ -# wx-java-mp-starter +# wx-java-mp-spring-boot-starter + ## 快速开始 + 1. 引入依赖 ```xml @@ -11,22 +13,34 @@ 2. 添加配置(application.properties) ```properties # 公众号配置(必填) - wx.mp.appId = @appId - wx.mp.secret = @secret - wx.mp.token = @token - wx.mp.aesKey = @aesKey - # 存储配置redis(可选) - wx.mp.config-storage.type = redis - wx.mp.config-storage.redis.host = 127.0.0.1 - wx.mp.config-storage.redis.port = 6379 + wx.mp.app-id=appId + wx.mp.secret=@secret + wx.mp.token=@token + wx.mp.aes-key=@aesKey + wx.mp.use-stable-access-token=@useStableAccessToken + # 存储配置redis(可选) + wx.mp.config-storage.type= edis # 配置类型: Memory(默认), Jedis, RedisTemplate + wx.mp.config-storage.key-prefix=wx # 相关redis前缀配置: wx(默认) + wx.mp.config-storage.redis.host=127.0.0.1 + wx.mp.config-storage.redis.port=6379 + #单机和sentinel同时存在时,优先使用sentinel配置 + #wx.mp.config-storage.redis.sentinel-ips=127.0.0.1:16379,127.0.0.1:26379 + #wx.mp.config-storage.redis.sentinel-name=mymaster + # http客户端配置 + wx.mp.config-storage.http-client-type=HttpComponents # http客户端类型: HttpComponents(Apache HttpClient 5.x,推荐), HttpClient(Apache HttpClient 4.x), OkHttp, JoddHttp + wx.mp.config-storage.http-proxy-host= + wx.mp.config-storage.http-proxy-port= + wx.mp.config-storage.http-proxy-username= + wx.mp.config-storage.http-proxy-password= + # 公众号地址host配置 + #wx.mp.hosts.api-host=http://proxy.com/ + #wx.mp.hosts.open-host=http://proxy.com/ + #wx.mp.hosts.mp-host=http://proxy.com/ ``` -3. 支持自动注入的类型 - -`WxMpService`以及相关的服务类, 比如: `wxMpService.getXxxService`。 - - - - - +3. 自动注入的类型 +- `WxMpService` +- `WxMpConfigStorage` +4、参考demo: +https://github.com/binarywang/wx-java-mp-demo 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 d45c1bd729..38e484b450 100644 --- a/spring-boot-starters/wx-java-mp-spring-boot-starter/pom.xml +++ b/spring-boot-starters/wx-java-mp-spring-boot-starter/pom.xml @@ -5,7 +5,7 @@ wx-java-spring-boot-starters com.github.binarywang - 3.6.0 + 4.8.0 4.0.0 @@ -22,7 +22,32 @@ redis.clients jedis - compile + provided + + + org.springframework.data + spring-data-redis + provided + + + org.jodd + jodd-http + provided + + + com.squareup.okhttp3 + okhttp + provided + + + org.apache.httpcomponents.client5 + httpclient5 + provided + + + org.redisson + redisson + provided diff --git a/spring-boot-starters/wx-java-mp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/config/WxMpAutoConfiguration.java b/spring-boot-starters/wx-java-mp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/config/WxMpAutoConfiguration.java index e2b0a60d2b..b2e3848ab8 100644 --- a/spring-boot-starters/wx-java-mp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/config/WxMpAutoConfiguration.java +++ b/spring-boot-starters/wx-java-mp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/config/WxMpAutoConfiguration.java @@ -12,6 +12,6 @@ */ @Configuration @EnableConfigurationProperties(WxMpProperties.class) -@Import({WxMpStorageAutoConfiguration.class, WxMpServiceAutoConfiguration.class}) +@Import({ WxMpStorageAutoConfiguration.class, WxMpServiceAutoConfiguration.class }) public class WxMpAutoConfiguration { } 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 7d96226733..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 @@ -1,7 +1,13 @@ package com.binarywang.spring.starter.wxjava.mp.config; -import me.chanjar.weixin.mp.api.*; +import com.binarywang.spring.starter.wxjava.mp.enums.HttpClientType; +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; import me.chanjar.weixin.mp.config.WxMpConfigStorage; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.context.annotation.Bean; @@ -17,115 +23,49 @@ public class WxMpServiceAutoConfiguration { @Bean @ConditionalOnMissingBean - public WxMpService wxMpService(WxMpConfigStorage configStorage) { - WxMpService wxMpService = new WxMpServiceImpl(); + public WxMpService wxMpService(WxMpConfigStorage configStorage, WxMpProperties wxMpProperties) { + HttpClientType httpClientType = wxMpProperties.getConfigStorage().getHttpClientType(); + WxMpService wxMpService; + switch (httpClientType) { + case OkHttp: + wxMpService = newWxMpServiceOkHttpImpl(); + break; + case JoddHttp: + wxMpService = newWxMpServiceJoddHttpImpl(); + break; + case HttpClient: + wxMpService = newWxMpServiceHttpClientImpl(); + break; + case HttpComponents: + wxMpService = newWxMpServiceHttpComponentsImpl(); + break; + default: + wxMpService = newWxMpServiceImpl(); + break; + } + wxMpService.setWxMpConfigStorage(configStorage); return wxMpService; } - @Bean - public WxMpKefuService wxMpKefuService(WxMpService wxMpService) { - return wxMpService.getKefuService(); - } - - @Bean - public WxMpMaterialService wxMpMaterialService(WxMpService wxMpService) { - return wxMpService.getMaterialService(); - } - - @Bean - public WxMpMenuService wxMpMenuService(WxMpService wxMpService) { - return wxMpService.getMenuService(); - } - - @Bean - public WxMpUserService wxMpUserService(WxMpService wxMpService) { - return wxMpService.getUserService(); - } - - @Bean - public WxMpUserTagService wxMpUserTagService(WxMpService wxMpService) { - return wxMpService.getUserTagService(); - } - - @Bean - public WxMpQrcodeService wxMpQrcodeService(WxMpService wxMpService) { - return wxMpService.getQrcodeService(); - } - - @Bean - public WxMpCardService wxMpCardService(WxMpService wxMpService) { - return wxMpService.getCardService(); - } - - @Bean - public WxMpDataCubeService wxMpDataCubeService(WxMpService wxMpService) { - return wxMpService.getDataCubeService(); - } - - @Bean - public WxMpUserBlacklistService wxMpUserBlacklistService(WxMpService wxMpService) { - return wxMpService.getBlackListService(); - } - - @Bean - public WxMpStoreService wxMpStoreService(WxMpService wxMpService) { - return wxMpService.getStoreService(); - } - - @Bean - public WxMpTemplateMsgService wxMpTemplateMsgService(WxMpService wxMpService) { - return wxMpService.getTemplateMsgService(); - } - - @Bean - public WxMpSubscribeMsgService wxMpSubscribeMsgService(WxMpService wxMpService) { - return wxMpService.getSubscribeMsgService(); + private WxMpService newWxMpServiceImpl() { + return new WxMpServiceImpl(); } - @Bean - public WxMpDeviceService wxMpDeviceService(WxMpService wxMpService) { - return wxMpService.getDeviceService(); + private WxMpService newWxMpServiceHttpClientImpl() { + return new WxMpServiceHttpClientImpl(); } - @Bean - public WxMpShakeService wxMpShakeService(WxMpService wxMpService) { - return wxMpService.getShakeService(); + private WxMpService newWxMpServiceOkHttpImpl() { + return new WxMpServiceOkHttpImpl(); } - @Bean - public WxMpMemberCardService wxMpMemberCardService(WxMpService wxMpService) { - return wxMpService.getMemberCardService(); - } - - @Bean - public WxMpMassMessageService wxMpMassMessageService(WxMpService wxMpService) { - return wxMpService.getMassMessageService(); + private WxMpService newWxMpServiceJoddHttpImpl() { + return new WxMpServiceJoddHttpImpl(); } - @Bean - public WxMpAiOpenService wxMpAiOpenService(WxMpService wxMpService) { - return wxMpService.getAiOpenService(); - } - - @Bean - public WxMpWifiService wxMpWifiService(WxMpService wxMpService) { - return wxMpService.getWifiService(); - } - - @Bean - public WxMpMarketingService wxMpMarketingService(WxMpService wxMpService) { - return wxMpService.getMarketingService(); - } - - @Bean - public WxMpCommentService wxMpCommentService(WxMpService wxMpService) { - return wxMpService.getCommentService(); - } - - @Bean - public WxMpOcrService wxMpOcrService(WxMpService wxMpService) { - return wxMpService.getOcrService(); + 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 18024707a4..cab3cb17b2 100644 --- a/spring-boot-starters/wx-java-mp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/config/WxMpStorageAutoConfiguration.java +++ b/spring-boot-starters/wx-java-mp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/config/WxMpStorageAutoConfiguration.java @@ -1,88 +1,26 @@ package com.binarywang.spring.starter.wxjava.mp.config; -import com.binarywang.spring.starter.wxjava.mp.properties.RedisProperties; -import com.binarywang.spring.starter.wxjava.mp.properties.WxMpProperties; +import com.binarywang.spring.starter.wxjava.mp.config.storage.WxMpInJedisConfigStorageConfiguration; +import com.binarywang.spring.starter.wxjava.mp.config.storage.WxMpInMemoryConfigStorageConfiguration; +import com.binarywang.spring.starter.wxjava.mp.config.storage.WxMpInRedisTemplateConfigStorageConfiguration; +import com.binarywang.spring.starter.wxjava.mp.config.storage.WxMpInRedissonConfigStorageConfiguration; import lombok.RequiredArgsConstructor; -import me.chanjar.weixin.mp.config.WxMpConfigStorage; -import me.chanjar.weixin.mp.config.impl.WxMpDefaultConfigImpl; -import me.chanjar.weixin.mp.config.impl.WxMpRedisConfigImpl; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import redis.clients.jedis.JedisPool; -import redis.clients.jedis.JedisPoolConfig; +import org.springframework.context.annotation.Import; /** * 微信公众号存储策略自动配置. * - * @author someone + * @author Luo */ @Configuration +@Import({ + WxMpInMemoryConfigStorageConfiguration.class, + WxMpInJedisConfigStorageConfiguration.class, + WxMpInRedisTemplateConfigStorageConfiguration.class, + WxMpInRedissonConfigStorageConfiguration.class +}) @RequiredArgsConstructor public class WxMpStorageAutoConfiguration { - private final WxMpProperties properties; - @Autowired(required = false) - private JedisPool jedisPool; - - @Bean - @ConditionalOnMissingBean(WxMpConfigStorage.class) - public WxMpConfigStorage wxMpInMemoryConfigStorage() { - WxMpProperties.ConfigStorage storage = properties.getConfigStorage(); - WxMpProperties.StorageType type = storage.getType(); - - if (type == WxMpProperties.StorageType.redis) { - return getWxMpInRedisConfigStorage(); - } - return getWxMpInMemoryConfigStorage(); - } - - private WxMpDefaultConfigImpl getWxMpInMemoryConfigStorage() { - WxMpDefaultConfigImpl config = new WxMpDefaultConfigImpl(); - setWxMpInfo(config); - return config; - } - - private WxMpRedisConfigImpl getWxMpInRedisConfigStorage() { - JedisPool poolToUse = jedisPool; - if (poolToUse == null) { - poolToUse = getJedisPool(); - } - WxMpRedisConfigImpl config = new WxMpRedisConfigImpl(poolToUse); - setWxMpInfo(config); - return config; - } - - private void setWxMpInfo(WxMpDefaultConfigImpl config) { - config.setAppId(properties.getAppId()); - config.setSecret(properties.getSecret()); - config.setToken(properties.getToken()); - config.setAesKey(properties.getAesKey()); - } - - private JedisPool getJedisPool() { - WxMpProperties.ConfigStorage storage = properties.getConfigStorage(); - RedisProperties redis = storage.getRedis(); - - JedisPoolConfig config = new JedisPoolConfig(); - if (redis.getMaxActive() != null) { - config.setMaxTotal(redis.getMaxActive()); - } - if (redis.getMaxIdle() != null) { - config.setMaxIdle(redis.getMaxIdle()); - } - if (redis.getMaxWaitMillis() != null) { - config.setMaxWaitMillis(redis.getMaxWaitMillis()); - } - if (redis.getMinIdle() != null) { - config.setMinIdle(redis.getMinIdle()); - } - config.setTestOnBorrow(true); - config.setTestWhileIdle(true); - - JedisPool pool = new JedisPool(config, redis.getHost(), redis.getPort(), - redis.getTimeout(), redis.getPassword(), redis.getDatabase()); - return pool; - } } diff --git a/spring-boot-starters/wx-java-mp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/config/storage/AbstractWxMpConfigStorageConfiguration.java b/spring-boot-starters/wx-java-mp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/config/storage/AbstractWxMpConfigStorageConfiguration.java new file mode 100644 index 0000000000..e39a8bf4d9 --- /dev/null +++ b/spring-boot-starters/wx-java-mp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/config/storage/AbstractWxMpConfigStorageConfiguration.java @@ -0,0 +1,54 @@ +package com.binarywang.spring.starter.wxjava.mp.config.storage; + +import com.binarywang.spring.starter.wxjava.mp.properties.WxMpProperties; +import me.chanjar.weixin.common.util.http.apache.ApacheHttpClientBuilder; +import me.chanjar.weixin.common.util.http.apache.DefaultApacheHttpClientBuilder; +import me.chanjar.weixin.mp.config.WxMpHostConfig; +import me.chanjar.weixin.mp.config.impl.WxMpDefaultConfigImpl; +import org.apache.commons.lang3.StringUtils; + +/** + * @author zhangyl + */ +public abstract class AbstractWxMpConfigStorageConfiguration { + + protected WxMpDefaultConfigImpl config(WxMpDefaultConfigImpl config, WxMpProperties properties) { + config.setAppId(properties.getAppId()); + config.setSecret(properties.getSecret()); + config.setToken(properties.getToken()); + config.setAesKey(properties.getAesKey()); + config.setUseStableAccessToken(properties.isUseStableAccessToken()); + + WxMpProperties.ConfigStorage configStorageProperties = properties.getConfigStorage(); + config.setHttpProxyHost(configStorageProperties.getHttpProxyHost()); + config.setHttpProxyUsername(configStorageProperties.getHttpProxyUsername()); + config.setHttpProxyPassword(configStorageProperties.getHttpProxyPassword()); + if (configStorageProperties.getHttpProxyPort() != null) { + config.setHttpProxyPort(configStorageProperties.getHttpProxyPort()); + } + + // 设置自定义的 HttpClient 超时配置 + ApacheHttpClientBuilder clientBuilder = config.getApacheHttpClientBuilder(); + if (clientBuilder == null) { + clientBuilder = DefaultApacheHttpClientBuilder.get(); + } + if (clientBuilder instanceof DefaultApacheHttpClientBuilder) { + DefaultApacheHttpClientBuilder defaultBuilder = (DefaultApacheHttpClientBuilder) clientBuilder; + defaultBuilder.setConnectionTimeout(configStorageProperties.getConnectionTimeout()); + defaultBuilder.setSoTimeout(configStorageProperties.getSoTimeout()); + defaultBuilder.setConnectionRequestTimeout(configStorageProperties.getConnectionRequestTimeout()); + config.setApacheHttpClientBuilder(defaultBuilder); + } + + // wx host config + if (null != properties.getHosts() && StringUtils.isNotEmpty(properties.getHosts().getApiHost())) { + WxMpHostConfig hostConfig = new WxMpHostConfig(); + hostConfig.setApiHost(properties.getHosts().getApiHost()); + hostConfig.setOpenHost(properties.getHosts().getOpenHost()); + hostConfig.setMpHost(properties.getHosts().getMpHost()); + config.setHostConfig(hostConfig); + } + + return config; + } +} diff --git a/spring-boot-starters/wx-java-mp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/config/storage/WxMpInJedisConfigStorageConfiguration.java b/spring-boot-starters/wx-java-mp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/config/storage/WxMpInJedisConfigStorageConfiguration.java new file mode 100644 index 0000000000..c21418a6f6 --- /dev/null +++ b/spring-boot-starters/wx-java-mp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/config/storage/WxMpInJedisConfigStorageConfiguration.java @@ -0,0 +1,80 @@ +package com.binarywang.spring.starter.wxjava.mp.config.storage; + +import com.binarywang.spring.starter.wxjava.mp.properties.RedisProperties; +import com.binarywang.spring.starter.wxjava.mp.properties.WxMpProperties; +import lombok.RequiredArgsConstructor; +import me.chanjar.weixin.common.redis.JedisWxRedisOps; +import me.chanjar.weixin.common.redis.WxRedisOps; +import me.chanjar.weixin.mp.config.WxMpConfigStorage; +import me.chanjar.weixin.mp.config.impl.WxMpRedisConfigImpl; +import org.apache.commons.lang3.StringUtils; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import redis.clients.jedis.Jedis; +import redis.clients.jedis.JedisPool; +import redis.clients.jedis.JedisPoolConfig; + +/** + * @author zhangyl + */ +@Configuration +@ConditionalOnProperty( + prefix = WxMpProperties.PREFIX + ".config-storage", + name = "type", + havingValue = "jedis" +) +@ConditionalOnClass(Jedis.class) +@RequiredArgsConstructor +public class WxMpInJedisConfigStorageConfiguration extends AbstractWxMpConfigStorageConfiguration { + private final WxMpProperties properties; + private final ApplicationContext applicationContext; + + @Bean + @ConditionalOnMissingBean(WxMpConfigStorage.class) + public WxMpConfigStorage wxMpConfigStorage() { + WxMpRedisConfigImpl config = getWxMpRedisConfigImpl(); + return this.config(config, properties); + } + + private WxMpRedisConfigImpl getWxMpRedisConfigImpl() { + RedisProperties redisProperties = properties.getConfigStorage().getRedis(); + JedisPool jedisPool; + if (redisProperties != null && StringUtils.isNotEmpty(redisProperties.getHost())) { + jedisPool = applicationContext.getBean("wxMpJedisPool", JedisPool.class); + } else { + jedisPool = applicationContext.getBean(JedisPool.class); + } + WxRedisOps redisOps = new JedisWxRedisOps(jedisPool); + return new WxMpRedisConfigImpl(redisOps, properties.getConfigStorage().getKeyPrefix()); + } + + @Bean + @ConditionalOnProperty(prefix = WxMpProperties.PREFIX + ".config-storage.redis", name = "host") + public JedisPool wxMpJedisPool() { + WxMpProperties.ConfigStorage storage = properties.getConfigStorage(); + RedisProperties redis = storage.getRedis(); + + JedisPoolConfig config = new JedisPoolConfig(); + if (redis.getMaxActive() != null) { + config.setMaxTotal(redis.getMaxActive()); + } + if (redis.getMaxIdle() != null) { + config.setMaxIdle(redis.getMaxIdle()); + } + if (redis.getMaxWaitMillis() != null) { + config.setMaxWaitMillis(redis.getMaxWaitMillis()); + } + if (redis.getMinIdle() != null) { + config.setMinIdle(redis.getMinIdle()); + } + config.setTestOnBorrow(true); + config.setTestWhileIdle(true); + + return new JedisPool(config, redis.getHost(), redis.getPort(), redis.getTimeout(), redis.getPassword(), + redis.getDatabase()); + } +} diff --git a/spring-boot-starters/wx-java-mp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/config/storage/WxMpInMemoryConfigStorageConfiguration.java b/spring-boot-starters/wx-java-mp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/config/storage/WxMpInMemoryConfigStorageConfiguration.java new file mode 100644 index 0000000000..16eada73ae --- /dev/null +++ b/spring-boot-starters/wx-java-mp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/config/storage/WxMpInMemoryConfigStorageConfiguration.java @@ -0,0 +1,33 @@ +package com.binarywang.spring.starter.wxjava.mp.config.storage; + +import com.binarywang.spring.starter.wxjava.mp.properties.WxMpProperties; +import lombok.RequiredArgsConstructor; +import me.chanjar.weixin.mp.config.WxMpConfigStorage; +import me.chanjar.weixin.mp.config.impl.WxMpDefaultConfigImpl; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * @author zhangyl + */ +@Configuration +@ConditionalOnProperty( + prefix = WxMpProperties.PREFIX + ".config-storage", + name = "type", + havingValue = "memory", + matchIfMissing = true +) +@RequiredArgsConstructor +public class WxMpInMemoryConfigStorageConfiguration extends AbstractWxMpConfigStorageConfiguration { + private final WxMpProperties properties; + + @Bean + @ConditionalOnMissingBean(WxMpConfigStorage.class) + public WxMpConfigStorage wxMpConfigStorage() { + WxMpDefaultConfigImpl config = new WxMpDefaultConfigImpl(); + config(config, properties); + return config; + } +} diff --git a/spring-boot-starters/wx-java-mp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/config/storage/WxMpInRedisTemplateConfigStorageConfiguration.java b/spring-boot-starters/wx-java-mp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/config/storage/WxMpInRedisTemplateConfigStorageConfiguration.java new file mode 100644 index 0000000000..0305ca4f8e --- /dev/null +++ b/spring-boot-starters/wx-java-mp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/config/storage/WxMpInRedisTemplateConfigStorageConfiguration.java @@ -0,0 +1,46 @@ +package com.binarywang.spring.starter.wxjava.mp.config.storage; + +import com.binarywang.spring.starter.wxjava.mp.properties.WxMpProperties; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import me.chanjar.weixin.common.redis.RedisTemplateWxRedisOps; +import me.chanjar.weixin.common.redis.WxRedisOps; +import me.chanjar.weixin.mp.config.WxMpConfigStorage; +import me.chanjar.weixin.mp.config.impl.WxMpRedisConfigImpl; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.core.StringRedisTemplate; + +/** + * @author zhangyl + */ +@Slf4j +@Configuration +@ConditionalOnProperty( + prefix = WxMpProperties.PREFIX + ".config-storage", + name = "type", + havingValue = "redistemplate" +) +@ConditionalOnClass(StringRedisTemplate.class) +@RequiredArgsConstructor +public class WxMpInRedisTemplateConfigStorageConfiguration extends AbstractWxMpConfigStorageConfiguration { + private final WxMpProperties properties; + private final ApplicationContext applicationContext; + + @Bean + @ConditionalOnMissingBean(WxMpConfigStorage.class) + public WxMpConfigStorage wxMpConfigStorage() { + WxMpRedisConfigImpl config = getWxMpInRedisTemplateConfigStorage(); + return this.config(config, properties); + } + + private WxMpRedisConfigImpl getWxMpInRedisTemplateConfigStorage() { + StringRedisTemplate redisTemplate = applicationContext.getBean(StringRedisTemplate.class); + WxRedisOps redisOps = new RedisTemplateWxRedisOps(redisTemplate); + return new WxMpRedisConfigImpl(redisOps, properties.getConfigStorage().getKeyPrefix()); + } +} diff --git a/spring-boot-starters/wx-java-mp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/config/storage/WxMpInRedissonConfigStorageConfiguration.java b/spring-boot-starters/wx-java-mp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/config/storage/WxMpInRedissonConfigStorageConfiguration.java new file mode 100644 index 0000000000..75b736f53f --- /dev/null +++ b/spring-boot-starters/wx-java-mp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/config/storage/WxMpInRedissonConfigStorageConfiguration.java @@ -0,0 +1,69 @@ +package com.binarywang.spring.starter.wxjava.mp.config.storage; + +import com.binarywang.spring.starter.wxjava.mp.properties.RedisProperties; +import com.binarywang.spring.starter.wxjava.mp.properties.WxMpProperties; +import lombok.RequiredArgsConstructor; +import me.chanjar.weixin.mp.config.WxMpConfigStorage; +import me.chanjar.weixin.mp.config.impl.WxMpRedissonConfigImpl; +import org.apache.commons.lang3.StringUtils; +import org.redisson.Redisson; +import org.redisson.api.RedissonClient; +import org.redisson.config.Config; +import org.redisson.config.TransportMode; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * @author zhangyl + */ +@Configuration +@ConditionalOnProperty( + prefix = WxMpProperties.PREFIX + ".config-storage", + name = "type", + havingValue = "redisson" +) +@ConditionalOnClass({Redisson.class, RedissonClient.class}) +@RequiredArgsConstructor +public class WxMpInRedissonConfigStorageConfiguration extends AbstractWxMpConfigStorageConfiguration { + private final WxMpProperties properties; + private final ApplicationContext applicationContext; + + @Bean + @ConditionalOnMissingBean(WxMpConfigStorage.class) + public WxMpConfigStorage wxMpConfigStorage() { + WxMpRedissonConfigImpl config = getWxMpInRedissonConfigStorage(); + return this.config(config, properties); + } + + private WxMpRedissonConfigImpl getWxMpInRedissonConfigStorage() { + RedisProperties redisProperties = properties.getConfigStorage().getRedis(); + RedissonClient redissonClient; + if (redisProperties != null && StringUtils.isNotEmpty(redisProperties.getHost())) { + redissonClient = applicationContext.getBean("wxMpRedissonClient", RedissonClient.class); + } else { + redissonClient = applicationContext.getBean(RedissonClient.class); + } + return new WxMpRedissonConfigImpl(redissonClient, properties.getConfigStorage().getKeyPrefix()); + } + + @Bean + @ConditionalOnProperty(prefix = WxMpProperties.PREFIX + ".config-storage.redis", name = "host") + public RedissonClient wxMpRedissonClient() { + WxMpProperties.ConfigStorage storage = properties.getConfigStorage(); + RedisProperties redis = storage.getRedis(); + + Config config = new Config(); + config.useSingleServer() + .setAddress("redis://" + redis.getHost() + ":" + redis.getPort()) + .setDatabase(redis.getDatabase()); + if (StringUtils.isNotBlank(redis.getPassword())) { + config.useSingleServer().setPassword(redis.getPassword()); + } + config.setTransportMode(TransportMode.NIO); + return Redisson.create(config); + } +} diff --git a/spring-boot-starters/wx-java-mp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/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 new file mode 100644 index 0000000000..0bf034417f --- /dev/null +++ b/spring-boot-starters/wx-java-mp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/enums/HttpClientType.java @@ -0,0 +1,26 @@ +package com.binarywang.spring.starter.wxjava.mp.enums; + +/** + * httpclient类型. + * + * @author Binary Wang + * created on 2020-08-30 + */ +public enum HttpClientType { + /** + * HttpClient. + */ + HttpClient, + /** + * OkHttp. + */ + OkHttp, + /** + * 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/enums/StorageType.java b/spring-boot-starters/wx-java-mp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/enums/StorageType.java new file mode 100644 index 0000000000..05ed6ce393 --- /dev/null +++ b/spring-boot-starters/wx-java-mp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/enums/StorageType.java @@ -0,0 +1,26 @@ +package com.binarywang.spring.starter.wxjava.mp.enums; + +/** + * storage类型. + * + * @author Binary Wang + * created on 2020-08-30 + */ +public enum StorageType { + /** + * 内存. + */ + Memory, + /** + * redis(JedisClient). + */ + Jedis, + /** + * redis(Redisson). + */ + Redisson, + /** + * redis(RedisTemplate). + */ + RedisTemplate +} diff --git a/spring-boot-starters/wx-java-mp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/properties/HostConfig.java b/spring-boot-starters/wx-java-mp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/properties/HostConfig.java new file mode 100644 index 0000000000..5b29400738 --- /dev/null +++ b/spring-boot-starters/wx-java-mp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/properties/HostConfig.java @@ -0,0 +1,27 @@ +package com.binarywang.spring.starter.wxjava.mp.properties; + +import lombok.Data; + +import java.io.Serializable; + +@Data +public class HostConfig implements Serializable { + + private static final long serialVersionUID = -4172767630740346001L; + + /** + * 对应于:https://api.weixin.qq.com + */ + private String apiHost; + + /** + * 对应于:https://open.weixin.qq.com + */ + private String openHost; + + /** + * 对应于:https://mp.weixin.qq.com + */ + private String mpHost; + +} diff --git a/spring-boot-starters/wx-java-mp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/properties/RedisProperties.java b/spring-boot-starters/wx-java-mp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/properties/RedisProperties.java index d95e8df984..573c87630f 100644 --- a/spring-boot-starters/wx-java-mp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/properties/RedisProperties.java +++ b/spring-boot-starters/wx-java-mp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/properties/RedisProperties.java @@ -5,9 +5,10 @@ import java.io.Serializable; /** - * Redis配置. + * redis 配置属性. * - * @author someone + * @author Binary Wang + * created on 2020-08-30 */ @Data public class RedisProperties implements Serializable { @@ -38,6 +39,16 @@ public class RedisProperties implements Serializable { */ private int database = 0; + /** + * sentinel ips + */ + private String sentinelIps; + + /** + * sentinel name + */ + private String sentinelName; + private Integer maxActive; private Integer maxIdle; private Integer maxWaitMillis; 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 0dbca9e778..a6c6e3b6bd 100644 --- a/spring-boot-starters/wx-java-mp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/properties/WxMpProperties.java +++ b/spring-boot-starters/wx-java-mp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/properties/WxMpProperties.java @@ -1,13 +1,15 @@ package com.binarywang.spring.starter.wxjava.mp.properties; +import com.binarywang.spring.starter.wxjava.mp.enums.HttpClientType; +import com.binarywang.spring.starter.wxjava.mp.enums.StorageType; import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.NestedConfigurationProperty; import java.io.Serializable; +import static com.binarywang.spring.starter.wxjava.mp.enums.StorageType.Memory; import static com.binarywang.spring.starter.wxjava.mp.properties.WxMpProperties.PREFIX; -import static com.binarywang.spring.starter.wxjava.mp.properties.WxMpProperties.StorageType.memory; - /** * 微信接入相关配置属性. @@ -40,29 +42,81 @@ public class WxMpProperties { private String aesKey; /** - * 存储策略, memory, redis. + * 是否使用稳定版 Access Token */ - private ConfigStorage configStorage = new ConfigStorage(); + private boolean useStableAccessToken = false; + /** + * 自定义host配置 + */ + @NestedConfigurationProperty + private HostConfig hosts; + + /** + * 存储策略 + */ + private final ConfigStorage configStorage = new ConfigStorage(); @Data public static class ConfigStorage implements Serializable { private static final long serialVersionUID = 4815731027000065434L; - private StorageType type = memory; + /** + * 存储类型. + */ + private StorageType type = Memory; - private RedisProperties redis = new RedisProperties(); + /** + * 指定key前缀. + */ + private String keyPrefix = "wx"; - } + /** + * redis连接配置. + */ + @NestedConfigurationProperty + private final RedisProperties redis = new RedisProperties(); + + /** + * http客户端类型. + */ + private HttpClientType httpClientType = HttpClientType.HttpComponents; + + /** + * http代理主机. + */ + private String httpProxyHost; + + /** + * http代理端口. + */ + private Integer httpProxyPort; - public enum StorageType { /** - * 内存. + * http代理用户名. */ - memory, + private String httpProxyUsername; + + /** + * http代理密码. + */ + private String httpProxyPassword; + /** - * redis. + * 连接超时时间,单位毫秒 */ - redis + private int connectionTimeout = 5000; + + /** + * 读数据超时时间,即socketTimeout,单位毫秒 + */ + private int soTimeout = 5000; + + /** + * 从连接池获取链接的超时时间,单位毫秒 + */ + private int connectionRequestTimeout = 5000; + } + } diff --git a/spring-boot-starters/wx-java-mp-spring-boot-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/spring-boot-starters/wx-java-mp-spring-boot-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 0000000000..cdffb05c9e --- /dev/null +++ b/spring-boot-starters/wx-java-mp-spring-boot-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1 @@ +com.binarywang.spring.starter.wxjava.mp.config.WxMpAutoConfiguration diff --git a/spring-boot-starters/wx-java-open-multi-spring-boot-starter/README.md b/spring-boot-starters/wx-java-open-multi-spring-boot-starter/README.md new file mode 100644 index 0000000000..ab5afa5449 --- /dev/null +++ b/spring-boot-starters/wx-java-open-multi-spring-boot-starter/README.md @@ -0,0 +1,98 @@ +# wx-java-open-multi-spring-boot-starter + +## 快速开始 + +1. 引入依赖 + ```xml + + com.github.binarywang + wx-java-open-multi-spring-boot-starter + ${version} + + ``` +2. 添加配置(application.properties) + ```properties + # 开放平台配置 + ## 应用 1 配置(必填) + wx.open.apps.tenantId1.app-id=appId + wx.open.apps.tenantId1.secret=@secret + ## 选填 + wx.open.apps.tenantId1.token=@token + wx.open.apps.tenantId1.aes-key=@aesKey + wx.open.apps.tenantId1.api-host-url=@apiHostUrl + wx.open.apps.tenantId1.access-token-url=@accessTokenUrl + ## 应用 2 配置(必填) + wx.open.apps.tenantId2.app-id=@appId + wx.open.apps.tenantId2.secret=@secret + ## 选填 + wx.open.apps.tenantId2.token=@token + wx.open.apps.tenantId2.aes-key=@aesKey + wx.open.apps.tenantId2.api-host-url=@apiHostUrl + wx.open.apps.tenantId2.access-token-url=@accessTokenUrl + + # ConfigStorage 配置(选填) + ## 配置类型: memory(默认), jedis, redisson, redistemplate + wx.open.config-storage.type=memory + ## 相关redis前缀配置: wx:open:multi(默认) + wx.open.config-storage.key-prefix=wx:open:multi + wx.open.config-storage.redis.host=127.0.0.1 + wx.open.config-storage.redis.port=6379 + ## 注意:当前版本暂不支持 sentinel 配置,以下配置仅作为预留 + # wx.open.config-storage.redis.sentinel-ips=127.0.0.1:16379,127.0.0.1:26379 + # wx.open.config-storage.redis.sentinel-name=mymaster + + # http 客户端配置(选填) + wx.open.config-storage.http-proxy-host= + wx.open.config-storage.http-proxy-port= + wx.open.config-storage.http-proxy-username= + wx.open.config-storage.http-proxy-password= + ## 最大重试次数,默认:5 次,如果小于 0,则为 0 + wx.open.config-storage.max-retry-times=5 + ## 重试时间间隔步进,默认:1000 毫秒,如果小于 0,则为 1000 + wx.open.config-storage.retry-sleep-millis=1000 + ## 连接超时时间,单位毫秒,默认:5000 + wx.open.config-storage.connection-timeout=5000 + ## 读数据超时时间,即socketTimeout,单位毫秒,默认:5000 + wx.open.config-storage.so-timeout=5000 + ## 从连接池获取链接的超时时间,单位毫秒,默认:5000 + wx.open.config-storage.connection-request-timeout=5000 + ``` +3. 自动注入的类型:`WxOpenMultiServices` + +4. 使用样例 + +```java +import com.binarywang.spring.starter.wxjava.open.service.WxOpenMultiServices; +import me.chanjar.weixin.open.api.WxOpenService; +import me.chanjar.weixin.open.api.WxOpenComponentService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +@Service +public class DemoService { + @Autowired + private WxOpenMultiServices wxOpenMultiServices; + + public void test() { + // 应用 1 的 WxOpenService + WxOpenService wxOpenService1 = wxOpenMultiServices.getWxOpenService("tenantId1"); + WxOpenComponentService componentService1 = wxOpenService1.getWxOpenComponentService(); + // todo ... + + // 应用 2 的 WxOpenService + WxOpenService wxOpenService2 = wxOpenMultiServices.getWxOpenService("tenantId2"); + WxOpenComponentService componentService2 = wxOpenService2.getWxOpenComponentService(); + // todo ... + + // 应用 3 的 WxOpenService + WxOpenService wxOpenService3 = wxOpenMultiServices.getWxOpenService("tenantId3"); + // 判断是否为空 + if (wxOpenService3 == null) { + // todo wxOpenService3 为空,请先配置 tenantId3 微信开放平台应用参数 + return; + } + WxOpenComponentService componentService3 = wxOpenService3.getWxOpenComponentService(); + // todo ... + } +} +``` diff --git a/spring-boot-starters/wx-java-open-multi-spring-boot-starter/pom.xml b/spring-boot-starters/wx-java-open-multi-spring-boot-starter/pom.xml new file mode 100644 index 0000000000..1ad7a5e8e1 --- /dev/null +++ b/spring-boot-starters/wx-java-open-multi-spring-boot-starter/pom.xml @@ -0,0 +1,62 @@ + + + + wx-java-spring-boot-starters + com.github.binarywang + 4.8.0 + + 4.0.0 + + wx-java-open-multi-spring-boot-starter + WxJava - Spring Boot Starter for WxOpen::支持多账号配置 + 微信开放平台开发的 Spring Boot Starter::支持多账号配置 + + + + com.github.binarywang + weixin-java-open + ${project.version} + + + redis.clients + jedis + provided + + + org.redisson + redisson + provided + + + org.springframework.data + spring-data-redis + provided + + + + + + + org.springframework.boot + spring-boot-maven-plugin + ${spring.boot.version} + + + org.apache.maven.plugins + maven-source-plugin + 2.2.1 + + + attach-sources + + jar-no-fork + + + + + + + + diff --git a/spring-boot-starters/wx-java-open-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/open/autoconfigure/WxOpenMultiAutoConfiguration.java b/spring-boot-starters/wx-java-open-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/open/autoconfigure/WxOpenMultiAutoConfiguration.java new file mode 100644 index 0000000000..749130f517 --- /dev/null +++ b/spring-boot-starters/wx-java-open-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/open/autoconfigure/WxOpenMultiAutoConfiguration.java @@ -0,0 +1,15 @@ +package com.binarywang.spring.starter.wxjava.open.autoconfigure; + +import com.binarywang.spring.starter.wxjava.open.configuration.WxOpenMultiServiceConfiguration; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; + +/** + * 微信开放平台多账号自动配置 + * + * @author Binary Wang + */ +@Configuration +@Import(WxOpenMultiServiceConfiguration.class) +public class WxOpenMultiAutoConfiguration { +} diff --git a/spring-boot-starters/wx-java-open-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/open/configuration/WxOpenMultiServiceConfiguration.java b/spring-boot-starters/wx-java-open-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/open/configuration/WxOpenMultiServiceConfiguration.java new file mode 100644 index 0000000000..e858185e30 --- /dev/null +++ b/spring-boot-starters/wx-java-open-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/open/configuration/WxOpenMultiServiceConfiguration.java @@ -0,0 +1,26 @@ +package com.binarywang.spring.starter.wxjava.open.configuration; + +import com.binarywang.spring.starter.wxjava.open.configuration.services.WxOpenInJedisConfiguration; +import com.binarywang.spring.starter.wxjava.open.configuration.services.WxOpenInMemoryConfiguration; +import com.binarywang.spring.starter.wxjava.open.configuration.services.WxOpenInRedisTemplateConfiguration; +import com.binarywang.spring.starter.wxjava.open.configuration.services.WxOpenInRedissonConfiguration; +import com.binarywang.spring.starter.wxjava.open.properties.WxOpenMultiProperties; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; + +/** + * 微信开放平台相关服务自动注册 + * + * @author Binary Wang + */ +@Configuration +@EnableConfigurationProperties(WxOpenMultiProperties.class) +@Import({ + WxOpenInJedisConfiguration.class, + WxOpenInMemoryConfiguration.class, + WxOpenInRedissonConfiguration.class, + WxOpenInRedisTemplateConfiguration.class +}) +public class WxOpenMultiServiceConfiguration { +} diff --git a/spring-boot-starters/wx-java-open-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/open/configuration/services/AbstractWxOpenConfiguration.java b/spring-boot-starters/wx-java-open-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/open/configuration/services/AbstractWxOpenConfiguration.java new file mode 100644 index 0000000000..0c63878783 --- /dev/null +++ b/spring-boot-starters/wx-java-open-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/open/configuration/services/AbstractWxOpenConfiguration.java @@ -0,0 +1,153 @@ +package com.binarywang.spring.starter.wxjava.open.configuration.services; + +import com.binarywang.spring.starter.wxjava.open.properties.WxOpenMultiProperties; +import com.binarywang.spring.starter.wxjava.open.properties.WxOpenSingleProperties; +import com.binarywang.spring.starter.wxjava.open.service.WxOpenMultiServices; +import com.binarywang.spring.starter.wxjava.open.service.WxOpenMultiServicesImpl; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import me.chanjar.weixin.common.util.http.apache.ApacheHttpClientBuilder; +import me.chanjar.weixin.common.util.http.apache.DefaultApacheHttpClientBuilder; +import me.chanjar.weixin.open.api.WxOpenConfigStorage; +import me.chanjar.weixin.open.api.WxOpenService; +import me.chanjar.weixin.open.api.impl.WxOpenInMemoryConfigStorage; +import me.chanjar.weixin.open.api.impl.WxOpenServiceImpl; +import org.apache.commons.lang3.StringUtils; + +import java.util.Collection; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * WxOpenConfigStorage 抽象配置类 + * + * @author Binary Wang + */ +@RequiredArgsConstructor +@Slf4j +public abstract class AbstractWxOpenConfiguration { + + protected WxOpenMultiServices wxOpenMultiServices(WxOpenMultiProperties wxOpenMultiProperties) { + Map appsMap = wxOpenMultiProperties.getApps(); + if (appsMap == null || appsMap.isEmpty()) { + log.warn("微信开放平台应用参数未配置,通过 WxOpenMultiServices#getWxOpenService(\"tenantId\")获取实例将返回空"); + return new WxOpenMultiServicesImpl(); + } + /** + * 校验 appId 是否唯一,避免使用 redis 缓存 token、ticket 时错乱。 + */ + Collection apps = appsMap.values(); + if (apps.size() > 1) { + // 校验 appId 是否唯一 + String nullAppIdPlaceholder = "__NULL_APP_ID__"; + boolean multi = apps.stream() + // 没有 appId,如果不判断是否为空,这里会报 NPE 异常 + .collect(Collectors.groupingBy(c -> c.getAppId() == null ? nullAppIdPlaceholder : c.getAppId(), Collectors.counting())) + .entrySet().stream().anyMatch(e -> e.getValue() > 1); + if (multi) { + throw new RuntimeException("请确保微信开放平台配置 appId 的唯一性"); + } + } + WxOpenMultiServicesImpl services = new WxOpenMultiServicesImpl(); + + Set> entries = appsMap.entrySet(); + for (Map.Entry entry : entries) { + String tenantId = entry.getKey(); + WxOpenSingleProperties wxOpenSingleProperties = entry.getValue(); + WxOpenInMemoryConfigStorage storage = this.wxOpenConfigStorage(wxOpenMultiProperties); + this.configApp(storage, wxOpenSingleProperties); + this.configHttp(storage, wxOpenMultiProperties.getConfigStorage()); + WxOpenService wxOpenService = this.wxOpenService(storage, wxOpenMultiProperties); + services.addWxOpenService(tenantId, wxOpenService); + } + return services; + } + + /** + * 配置 WxOpenInMemoryConfigStorage + * + * @param wxOpenMultiProperties 参数 + * @return WxOpenInMemoryConfigStorage + */ + protected abstract WxOpenInMemoryConfigStorage wxOpenConfigStorage(WxOpenMultiProperties wxOpenMultiProperties); + + public WxOpenService wxOpenService(WxOpenConfigStorage configStorage, WxOpenMultiProperties wxOpenMultiProperties) { + WxOpenService wxOpenService = new WxOpenServiceImpl(); + wxOpenService.setWxOpenConfigStorage(configStorage); + return wxOpenService; + } + + private void configApp(WxOpenInMemoryConfigStorage config, WxOpenSingleProperties appProperties) { + String appId = appProperties.getAppId(); + String secret = appProperties.getSecret(); + String token = appProperties.getToken(); + String aesKey = appProperties.getAesKey(); + String apiHostUrl = appProperties.getApiHostUrl(); + String accessTokenUrl = appProperties.getAccessTokenUrl(); + + // appId 和 secret 是必需的 + if (StringUtils.isBlank(appId)) { + throw new IllegalArgumentException("微信开放平台 appId 不能为空"); + } + if (StringUtils.isBlank(secret)) { + throw new IllegalArgumentException("微信开放平台 secret 不能为空"); + } + + config.setComponentAppId(appId); + config.setComponentAppSecret(secret); + if (StringUtils.isNotBlank(token)) { + config.setComponentToken(token); + } + if (StringUtils.isNotBlank(aesKey)) { + config.setComponentAesKey(aesKey); + } + // 设置URL配置 + config.setApiHostUrl(StringUtils.trimToNull(apiHostUrl)); + config.setAccessTokenUrl(StringUtils.trimToNull(accessTokenUrl)); + } + + private void configHttp(WxOpenInMemoryConfigStorage config, WxOpenMultiProperties.ConfigStorage storage) { + String httpProxyHost = storage.getHttpProxyHost(); + Integer httpProxyPort = storage.getHttpProxyPort(); + String httpProxyUsername = storage.getHttpProxyUsername(); + String httpProxyPassword = storage.getHttpProxyPassword(); + if (StringUtils.isNotBlank(httpProxyHost)) { + config.setHttpProxyHost(httpProxyHost); + if (httpProxyPort != null) { + config.setHttpProxyPort(httpProxyPort); + } + if (StringUtils.isNotBlank(httpProxyUsername)) { + config.setHttpProxyUsername(httpProxyUsername); + } + if (StringUtils.isNotBlank(httpProxyPassword)) { + config.setHttpProxyPassword(httpProxyPassword); + } + } + + // 设置重试配置 + int maxRetryTimes = storage.getMaxRetryTimes(); + if (maxRetryTimes < 0) { + maxRetryTimes = 0; + } + int retrySleepMillis = storage.getRetrySleepMillis(); + if (retrySleepMillis < 0) { + retrySleepMillis = 1000; + } + config.setRetrySleepMillis(retrySleepMillis); + config.setMaxRetryTimes(maxRetryTimes); + + // 设置自定义的HttpClient超时配置 + ApacheHttpClientBuilder clientBuilder = config.getApacheHttpClientBuilder(); + if (clientBuilder == null) { + clientBuilder = DefaultApacheHttpClientBuilder.get(); + } + if (clientBuilder instanceof DefaultApacheHttpClientBuilder) { + DefaultApacheHttpClientBuilder defaultBuilder = (DefaultApacheHttpClientBuilder) clientBuilder; + defaultBuilder.setConnectionTimeout(storage.getConnectionTimeout()); + defaultBuilder.setSoTimeout(storage.getSoTimeout()); + defaultBuilder.setConnectionRequestTimeout(storage.getConnectionRequestTimeout()); + config.setApacheHttpClientBuilder(defaultBuilder); + } + } +} diff --git a/spring-boot-starters/wx-java-open-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/open/configuration/services/WxOpenInJedisConfiguration.java b/spring-boot-starters/wx-java-open-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/open/configuration/services/WxOpenInJedisConfiguration.java new file mode 100644 index 0000000000..bb9577b99b --- /dev/null +++ b/spring-boot-starters/wx-java-open-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/open/configuration/services/WxOpenInJedisConfiguration.java @@ -0,0 +1,78 @@ +package com.binarywang.spring.starter.wxjava.open.configuration.services; + +import com.binarywang.spring.starter.wxjava.open.properties.WxOpenMultiProperties; +import com.binarywang.spring.starter.wxjava.open.properties.WxOpenMultiRedisProperties; +import com.binarywang.spring.starter.wxjava.open.service.WxOpenMultiServices; +import lombok.RequiredArgsConstructor; +import me.chanjar.weixin.open.api.impl.WxOpenInMemoryConfigStorage; +import me.chanjar.weixin.open.api.impl.WxOpenInRedisConfigStorage; +import org.apache.commons.lang3.StringUtils; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import redis.clients.jedis.JedisPool; +import redis.clients.jedis.JedisPoolConfig; + + +/** + * 自动装配基于 jedis 策略配置 + * + * @author Binary Wang + */ +@Configuration +@ConditionalOnProperty( + prefix = WxOpenMultiProperties.PREFIX + ".config-storage", name = "type", havingValue = "JEDIS" +) +@ConditionalOnClass({JedisPool.class, JedisPoolConfig.class}) +@RequiredArgsConstructor +public class WxOpenInJedisConfiguration extends AbstractWxOpenConfiguration { + private final WxOpenMultiProperties wxOpenMultiProperties; + private final ApplicationContext applicationContext; + + @Bean + public WxOpenMultiServices wxOpenMultiServices() { + return this.wxOpenMultiServices(wxOpenMultiProperties); + } + + @Override + protected WxOpenInMemoryConfigStorage wxOpenConfigStorage(WxOpenMultiProperties wxOpenMultiProperties) { + return this.configJedis(wxOpenMultiProperties); + } + + private WxOpenInRedisConfigStorage configJedis(WxOpenMultiProperties wxOpenMultiProperties) { + WxOpenMultiRedisProperties redisProperties = wxOpenMultiProperties.getConfigStorage().getRedis(); + JedisPool jedisPool; + if (redisProperties != null && StringUtils.isNotEmpty(redisProperties.getHost())) { + jedisPool = getJedisPool(wxOpenMultiProperties); + } else { + jedisPool = applicationContext.getBean(JedisPool.class); + } + return new WxOpenInRedisConfigStorage(jedisPool, wxOpenMultiProperties.getConfigStorage().getKeyPrefix()); + } + + private JedisPool getJedisPool(WxOpenMultiProperties wxOpenMultiProperties) { + WxOpenMultiProperties.ConfigStorage storage = wxOpenMultiProperties.getConfigStorage(); + WxOpenMultiRedisProperties redis = storage.getRedis(); + + JedisPoolConfig config = new JedisPoolConfig(); + if (redis.getMaxActive() != null) { + config.setMaxTotal(redis.getMaxActive()); + } + if (redis.getMaxIdle() != null) { + config.setMaxIdle(redis.getMaxIdle()); + } + if (redis.getMaxWaitMillis() != null) { + config.setMaxWaitMillis(redis.getMaxWaitMillis()); + } + if (redis.getMinIdle() != null) { + config.setMinIdle(redis.getMinIdle()); + } + config.setTestOnBorrow(true); + config.setTestWhileIdle(true); + + return new JedisPool(config, redis.getHost(), redis.getPort(), + redis.getTimeout(), redis.getPassword(), redis.getDatabase()); + } +} diff --git a/spring-boot-starters/wx-java-open-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/open/configuration/services/WxOpenInMemoryConfiguration.java b/spring-boot-starters/wx-java-open-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/open/configuration/services/WxOpenInMemoryConfiguration.java new file mode 100644 index 0000000000..f7448a0875 --- /dev/null +++ b/spring-boot-starters/wx-java-open-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/open/configuration/services/WxOpenInMemoryConfiguration.java @@ -0,0 +1,33 @@ +package com.binarywang.spring.starter.wxjava.open.configuration.services; + +import com.binarywang.spring.starter.wxjava.open.properties.WxOpenMultiProperties; +import com.binarywang.spring.starter.wxjava.open.service.WxOpenMultiServices; +import lombok.RequiredArgsConstructor; +import me.chanjar.weixin.open.api.impl.WxOpenInMemoryConfigStorage; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * 自动装配基于内存策略配置 + * + * @author someone + */ +@Configuration +@ConditionalOnProperty( + prefix = WxOpenMultiProperties.PREFIX + ".config-storage", name = "type", havingValue = "MEMORY", matchIfMissing = true +) +@RequiredArgsConstructor +public class WxOpenInMemoryConfiguration extends AbstractWxOpenConfiguration { + private final WxOpenMultiProperties wxOpenMultiProperties; + + @Bean + public WxOpenMultiServices wxOpenMultiServices() { + return this.wxOpenMultiServices(wxOpenMultiProperties); + } + + @Override + protected WxOpenInMemoryConfigStorage wxOpenConfigStorage(WxOpenMultiProperties wxOpenMultiProperties) { + return new WxOpenInMemoryConfigStorage(); + } +} diff --git a/spring-boot-starters/wx-java-open-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/open/configuration/services/WxOpenInRedisTemplateConfiguration.java b/spring-boot-starters/wx-java-open-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/open/configuration/services/WxOpenInRedisTemplateConfiguration.java new file mode 100644 index 0000000000..6208c90fe5 --- /dev/null +++ b/spring-boot-starters/wx-java-open-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/open/configuration/services/WxOpenInRedisTemplateConfiguration.java @@ -0,0 +1,44 @@ +package com.binarywang.spring.starter.wxjava.open.configuration.services; + +import com.binarywang.spring.starter.wxjava.open.properties.WxOpenMultiProperties; +import com.binarywang.spring.starter.wxjava.open.service.WxOpenMultiServices; +import lombok.RequiredArgsConstructor; +import me.chanjar.weixin.open.api.impl.WxOpenInMemoryConfigStorage; +import me.chanjar.weixin.open.api.impl.WxOpenInRedisTemplateConfigStorage; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.core.StringRedisTemplate; + +/** + * 自动装配基于 redis template 策略配置 + * + * @author Binary Wang + */ +@Configuration +@ConditionalOnProperty( + prefix = WxOpenMultiProperties.PREFIX + ".config-storage", name = "type", havingValue = "redistemplate" +) +@ConditionalOnClass(StringRedisTemplate.class) +@RequiredArgsConstructor +public class WxOpenInRedisTemplateConfiguration extends AbstractWxOpenConfiguration { + private final WxOpenMultiProperties wxOpenMultiProperties; + private final ApplicationContext applicationContext; + + @Bean + public WxOpenMultiServices wxOpenMultiServices() { + return this.wxOpenMultiServices(wxOpenMultiProperties); + } + + @Override + protected WxOpenInMemoryConfigStorage wxOpenConfigStorage(WxOpenMultiProperties wxOpenMultiProperties) { + return this.configRedisTemplate(); + } + + private WxOpenInRedisTemplateConfigStorage configRedisTemplate() { + StringRedisTemplate redisTemplate = applicationContext.getBean(StringRedisTemplate.class); + return new WxOpenInRedisTemplateConfigStorage(redisTemplate, wxOpenMultiProperties.getConfigStorage().getKeyPrefix()); + } +} diff --git a/spring-boot-starters/wx-java-open-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/open/configuration/services/WxOpenInRedissonConfiguration.java b/spring-boot-starters/wx-java-open-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/open/configuration/services/WxOpenInRedissonConfiguration.java new file mode 100644 index 0000000000..97569f3baf --- /dev/null +++ b/spring-boot-starters/wx-java-open-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/open/configuration/services/WxOpenInRedissonConfiguration.java @@ -0,0 +1,68 @@ +package com.binarywang.spring.starter.wxjava.open.configuration.services; + +import com.binarywang.spring.starter.wxjava.open.properties.WxOpenMultiProperties; +import com.binarywang.spring.starter.wxjava.open.properties.WxOpenMultiRedisProperties; +import com.binarywang.spring.starter.wxjava.open.service.WxOpenMultiServices; +import lombok.RequiredArgsConstructor; +import me.chanjar.weixin.open.api.impl.WxOpenInMemoryConfigStorage; +import me.chanjar.weixin.open.api.impl.WxOpenInRedissonConfigStorage; +import org.apache.commons.lang3.StringUtils; +import org.redisson.Redisson; +import org.redisson.api.RedissonClient; +import org.redisson.config.Config; +import org.redisson.config.TransportMode; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * 自动装配基于 redisson 策略配置 + * + * @author Binary Wang + */ +@Configuration +@ConditionalOnProperty( + prefix = WxOpenMultiProperties.PREFIX + ".config-storage", name = "type", havingValue = "redisson" +) +@ConditionalOnClass({Redisson.class, RedissonClient.class}) +@RequiredArgsConstructor +public class WxOpenInRedissonConfiguration extends AbstractWxOpenConfiguration { + private final WxOpenMultiProperties wxOpenMultiProperties; + private final ApplicationContext applicationContext; + + @Bean + public WxOpenMultiServices wxOpenMultiServices() { + return this.wxOpenMultiServices(wxOpenMultiProperties); + } + + @Override + protected WxOpenInMemoryConfigStorage wxOpenConfigStorage(WxOpenMultiProperties wxOpenMultiProperties) { + return this.configRedisson(wxOpenMultiProperties); + } + + private WxOpenInRedissonConfigStorage configRedisson(WxOpenMultiProperties wxOpenMultiProperties) { + WxOpenMultiRedisProperties redisProperties = wxOpenMultiProperties.getConfigStorage().getRedis(); + RedissonClient redissonClient; + if (redisProperties != null && StringUtils.isNotEmpty(redisProperties.getHost())) { + redissonClient = getRedissonClient(wxOpenMultiProperties); + } else { + redissonClient = applicationContext.getBean(RedissonClient.class); + } + return new WxOpenInRedissonConfigStorage(redissonClient, wxOpenMultiProperties.getConfigStorage().getKeyPrefix()); + } + + private RedissonClient getRedissonClient(WxOpenMultiProperties wxOpenMultiProperties) { + WxOpenMultiProperties.ConfigStorage storage = wxOpenMultiProperties.getConfigStorage(); + WxOpenMultiRedisProperties redis = storage.getRedis(); + + Config config = new Config(); + config.useSingleServer() + .setAddress("redis://" + redis.getHost() + ":" + redis.getPort()) + .setDatabase(redis.getDatabase()) + .setPassword(redis.getPassword()); + config.setTransportMode(TransportMode.NIO); + return Redisson.create(config); + } +} diff --git a/spring-boot-starters/wx-java-open-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/open/properties/WxOpenMultiProperties.java b/spring-boot-starters/wx-java-open-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/open/properties/WxOpenMultiProperties.java new file mode 100644 index 0000000000..95e5b66712 --- /dev/null +++ b/spring-boot-starters/wx-java-open-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/open/properties/WxOpenMultiProperties.java @@ -0,0 +1,125 @@ +package com.binarywang.spring.starter.wxjava.open.properties; + +import lombok.Data; +import lombok.NoArgsConstructor; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.NestedConfigurationProperty; + +import java.io.Serializable; +import java.util.HashMap; +import java.util.Map; + +/** + * 微信开放平台多账号配置属性. + * + * @author Binary Wang + */ +@Data +@NoArgsConstructor +@ConfigurationProperties(WxOpenMultiProperties.PREFIX) +public class WxOpenMultiProperties implements Serializable { + private static final long serialVersionUID = -5358245184407791011L; + public static final String PREFIX = "wx.open"; + + private Map apps = new HashMap<>(); + + /** + * 存储策略 + */ + private final ConfigStorage configStorage = new ConfigStorage(); + + @Data + @NoArgsConstructor + public static class ConfigStorage implements Serializable { + private static final long serialVersionUID = 4815731027000065434L; + + /** + * 存储类型. + */ + private StorageType type = StorageType.memory; + + /** + * 指定key前缀. + */ + private String keyPrefix = "wx:open:multi"; + + /** + * redis连接配置. + */ + @NestedConfigurationProperty + private final WxOpenMultiRedisProperties redis = new WxOpenMultiRedisProperties(); + + /** + * http代理主机. + */ + private String httpProxyHost; + + /** + * http代理端口. + */ + private Integer httpProxyPort; + + /** + * http代理用户名. + */ + private String httpProxyUsername; + + /** + * http代理密码. + */ + private String httpProxyPassword; + + /** + * http 请求最大重试次数 + *
+     *   {@link me.chanjar.weixin.mp.api.impl.BaseWxMpServiceImpl#setMaxRetryTimes(int)}
+     *   {@link cn.binarywang.wx.miniapp.api.impl.BaseWxMaServiceImpl#setMaxRetryTimes(int)}
+     * 
+ */ + private int maxRetryTimes = 5; + + /** + * http 请求重试间隔 + *
+     *   {@link me.chanjar.weixin.mp.api.impl.BaseWxMpServiceImpl#setRetrySleepMillis(int)}
+     *   {@link cn.binarywang.wx.miniapp.api.impl.BaseWxMaServiceImpl#setRetrySleepMillis(int)}
+     * 
+ */ + private int retrySleepMillis = 1000; + + /** + * 连接超时时间,单位毫秒 + */ + private int connectionTimeout = 5000; + + /** + * 读数据超时时间,即socketTimeout,单位毫秒 + */ + private int soTimeout = 5000; + + /** + * 从连接池获取链接的超时时间,单位毫秒 + */ + private int connectionRequestTimeout = 5000; + } + + public enum StorageType { + /** + * 内存 + */ + memory, + /** + * jedis + */ + jedis, + /** + * redisson + */ + redisson, + /** + * redisTemplate + */ + redistemplate + } + +} diff --git a/spring-boot-starters/wx-java-open-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/open/properties/WxOpenMultiRedisProperties.java b/spring-boot-starters/wx-java-open-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/open/properties/WxOpenMultiRedisProperties.java new file mode 100644 index 0000000000..ae6d5368d7 --- /dev/null +++ b/spring-boot-starters/wx-java-open-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/open/properties/WxOpenMultiRedisProperties.java @@ -0,0 +1,57 @@ +package com.binarywang.spring.starter.wxjava.open.properties; + +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * 微信开放平台多账号Redis配置. + * + * @author Binary Wang + */ +@Data +@NoArgsConstructor +public class WxOpenMultiRedisProperties implements Serializable { + private static final long serialVersionUID = -5924815351660074401L; + + /** + * 主机地址. + */ + private String host = "127.0.0.1"; + + /** + * 端口号. + */ + private int port = 6379; + + /** + * 密码. + */ + private String password; + + /** + * 超时. + */ + private int timeout = 2000; + + /** + * 数据库. + */ + private int database = 0; + + /** + * sentinel ips + */ + private String sentinelIps; + + /** + * sentinel name + */ + private String sentinelName; + + private Integer maxActive; + private Integer maxIdle; + private Integer maxWaitMillis; + private Integer minIdle; +} diff --git a/spring-boot-starters/wx-java-open-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/open/properties/WxOpenSingleProperties.java b/spring-boot-starters/wx-java-open-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/open/properties/WxOpenSingleProperties.java new file mode 100644 index 0000000000..116da323dc --- /dev/null +++ b/spring-boot-starters/wx-java-open-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/open/properties/WxOpenSingleProperties.java @@ -0,0 +1,49 @@ +package com.binarywang.spring.starter.wxjava.open.properties; + +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * 微信开放平台单个应用配置. + * + * @author Binary Wang + */ +@Data +@NoArgsConstructor +public class WxOpenSingleProperties implements Serializable { + private static final long serialVersionUID = 1980986361098922525L; + + /** + * 设置微信开放平台的appid. + */ + private String appId; + + /** + * 设置微信开放平台的app secret. + */ + private String secret; + + /** + * 设置微信开放平台的token. + */ + private String token; + + /** + * 设置微信开放平台的EncodingAESKey. + */ + private String aesKey; + + /** + * 自定义API主机地址,用于替换默认的 https://api.weixin.qq.com + * 例如:http://proxy.company.com:8080 + */ + private String apiHostUrl; + + /** + * 自定义获取AccessToken地址,用于向自定义统一服务获取AccessToken + * 例如:http://proxy.company.com:8080/oauth/token + */ + private String accessTokenUrl; +} diff --git a/spring-boot-starters/wx-java-open-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/open/service/WxOpenMultiServices.java b/spring-boot-starters/wx-java-open-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/open/service/WxOpenMultiServices.java new file mode 100644 index 0000000000..9228071a10 --- /dev/null +++ b/spring-boot-starters/wx-java-open-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/open/service/WxOpenMultiServices.java @@ -0,0 +1,26 @@ +package com.binarywang.spring.starter.wxjava.open.service; + + +import me.chanjar.weixin.open.api.WxOpenService; + +/** + * 微信开放平台 {@link WxOpenService} 所有实例存放类. + * + * @author binarywang + */ +public interface WxOpenMultiServices { + /** + * 通过租户 Id 获取 WxOpenService + * + * @param tenantId 租户 Id + * @return WxOpenService + */ + WxOpenService getWxOpenService(String tenantId); + + /** + * 根据租户 Id,从列表中移除一个 WxOpenService 实例 + * + * @param tenantId 租户 Id + */ + void removeWxOpenService(String tenantId); +} diff --git a/spring-boot-starters/wx-java-open-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/open/service/WxOpenMultiServicesImpl.java b/spring-boot-starters/wx-java-open-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/open/service/WxOpenMultiServicesImpl.java new file mode 100644 index 0000000000..76fb139e6c --- /dev/null +++ b/spring-boot-starters/wx-java-open-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/open/service/WxOpenMultiServicesImpl.java @@ -0,0 +1,35 @@ +package com.binarywang.spring.starter.wxjava.open.service; + +import me.chanjar.weixin.open.api.WxOpenService; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * 微信开放平台 {@link WxOpenMultiServices} 默认实现 + * + * @author Binary Wang + */ +public class WxOpenMultiServicesImpl implements WxOpenMultiServices { + private final Map services = new ConcurrentHashMap<>(); + + @Override + public WxOpenService getWxOpenService(String tenantId) { + return this.services.get(tenantId); + } + + /** + * 根据租户 Id,添加一个 WxOpenService 到列表 + * + * @param tenantId 租户 Id + * @param wxOpenService WxOpenService 实例 + */ + public void addWxOpenService(String tenantId, WxOpenService wxOpenService) { + this.services.put(tenantId, wxOpenService); + } + + @Override + public void removeWxOpenService(String tenantId) { + this.services.remove(tenantId); + } +} diff --git a/spring-boot-starters/wx-java-open-multi-spring-boot-starter/src/main/resources/META-INF/spring.factories b/spring-boot-starters/wx-java-open-multi-spring-boot-starter/src/main/resources/META-INF/spring.factories new file mode 100644 index 0000000000..a61d0018db --- /dev/null +++ b/spring-boot-starters/wx-java-open-multi-spring-boot-starter/src/main/resources/META-INF/spring.factories @@ -0,0 +1,2 @@ +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ +com.binarywang.spring.starter.wxjava.open.autoconfigure.WxOpenMultiAutoConfiguration \ No newline at end of file diff --git a/spring-boot-starters/wx-java-open-multi-spring-boot-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/spring-boot-starters/wx-java-open-multi-spring-boot-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 0000000000..ddc66af02c --- /dev/null +++ b/spring-boot-starters/wx-java-open-multi-spring-boot-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1 @@ +com.binarywang.spring.starter.wxjava.open.autoconfigure.WxOpenMultiAutoConfiguration \ No newline at end of file diff --git a/spring-boot-starters/wx-java-open-spring-boot-starter/README.md b/spring-boot-starters/wx-java-open-spring-boot-starter/README.md index fd00f03531..12650ac931 100644 --- a/spring-boot-starters/wx-java-open-spring-boot-starter/README.md +++ b/spring-boot-starters/wx-java-open-spring-boot-starter/README.md @@ -9,26 +9,31 @@
``` 2. 添加配置(application.properties) - ``` - # 开放平台配置(必填) - wx.open.appId = @appId - wx.open.secret = @secret - wx.open.token = @token - wx.open.aesKey = @aesKey - # 存储配置redis(可选), 优先使用(wx.open.config-storage.redis)配置的redis, 支持自定注入的JedisPool - wx.open.config-storage.type = redis # 可选值, memory(默认), redis - wx.open.config-storage.redis.host = 127.0.0.1 - wx.open.config-storage.redis.port = 6379 + ```properties + # 公众号配置(必填) + wx.open.appId = appId + wx.open.secret = @secret + wx.open.token = @token + wx.open.aesKey = @aesKey + # 存储配置redis(可选) + # 优先注入容器的(JedisPool, RedissonClient), 当配置了wx.open.config-storage.redis.host, 不会使用容器注入redis连接配置 + wx.open.config-storage.type = redis # 配置类型: memory(默认), redis(jedis), jedis, redisson, redistemplate + wx.open.config-storage.key-prefix = wx # 相关redis前缀配置: wx(默认) + wx.open.config-storage.redis.host = 127.0.0.1 + wx.open.config-storage.redis.port = 6379 + # http客户端配置 + wx.open.config-storage.http-client-type=httpclient # http客户端类型: httpclient(默认) + wx.open.config-storage.http-proxy-host= + wx.open.config-storage.http-proxy-port= + wx.open.config-storage.http-proxy-username= + wx.open.config-storage.http-proxy-password= + # 最大重试次数,默认:5 次,如果小于 0,则为 0 + wx.open.config-storage.max-retry-times=5 + # 重试时间间隔步进,默认:1000 毫秒,如果小于 0,则为 1000 + wx.open.config-storage.retry-sleep-millis=1000 ``` 3. 支持自动注入的类型: `WxOpenService, WxOpenMessageRouter, WxOpenComponentService` 4. 覆盖自动配置: 自定义注入的bean会覆盖自动注入的 - WxOpenConfigStorage - WxOpenService - - - - - - - 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 52ab7e3da5..9a25cd89d7 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 - 3.6.0 + 4.8.0 4.0.0 @@ -22,7 +22,14 @@ redis.clients jedis - compile + + + org.redisson + redisson + + + org.springframework.data + spring-data-redis diff --git a/spring-boot-starters/wx-java-open-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/open/config/WxOpenAutoConfiguration.java b/spring-boot-starters/wx-java-open-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/open/config/WxOpenAutoConfiguration.java index 0f7ecf3e8c..724d4a2f80 100644 --- a/spring-boot-starters/wx-java-open-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/open/config/WxOpenAutoConfiguration.java +++ b/spring-boot-starters/wx-java-open-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/open/config/WxOpenAutoConfiguration.java @@ -12,6 +12,9 @@ */ @Configuration @EnableConfigurationProperties(WxOpenProperties.class) -@Import({WxOpenStorageAutoConfiguration.class, WxOpenServiceAutoConfiguration.class}) +@Import({ + WxOpenStorageAutoConfiguration.class, + WxOpenServiceAutoConfiguration.class +}) public class WxOpenAutoConfiguration { } 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 a211486840..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 @@ -5,6 +5,7 @@ import me.chanjar.weixin.open.api.WxOpenService; import me.chanjar.weixin.open.api.impl.WxOpenMessageRouter; import me.chanjar.weixin.open.api.impl.WxOpenServiceImpl; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -19,13 +20,15 @@ public class WxOpenServiceAutoConfiguration { @Bean @ConditionalOnMissingBean - public WxOpenService wxOpenService(WxOpenConfigStorage configStorage) { + @ConditionalOnBean(WxOpenConfigStorage.class) + public WxOpenService wxOpenService(WxOpenConfigStorage wxOpenConfigStorage) { WxOpenService wxOpenService = new WxOpenServiceImpl(); - wxOpenService.setWxOpenConfigStorage(configStorage); + wxOpenService.setWxOpenConfigStorage(wxOpenConfigStorage); return wxOpenService; } @Bean + @ConditionalOnMissingBean public WxOpenMessageRouter wxOpenMessageRouter(WxOpenService wxOpenService) { return new WxOpenMessageRouter(wxOpenService); } @@ -34,6 +37,4 @@ public WxOpenMessageRouter wxOpenMessageRouter(WxOpenService wxOpenService) { public WxOpenComponentService wxOpenComponentService(WxOpenService wxOpenService) { return wxOpenService.getWxOpenComponentService(); } - - } diff --git a/spring-boot-starters/wx-java-open-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/open/config/WxOpenStorageAutoConfiguration.java b/spring-boot-starters/wx-java-open-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/open/config/WxOpenStorageAutoConfiguration.java index 5b57551973..efbefbe0a1 100644 --- a/spring-boot-starters/wx-java-open-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/open/config/WxOpenStorageAutoConfiguration.java +++ b/spring-boot-starters/wx-java-open-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/open/config/WxOpenStorageAutoConfiguration.java @@ -1,19 +1,11 @@ package com.binarywang.spring.starter.wxjava.open.config; -import com.binarywang.spring.starter.wxjava.open.properties.RedisProperties; -import com.binarywang.spring.starter.wxjava.open.properties.WxOpenProperties; -import lombok.RequiredArgsConstructor; -import me.chanjar.weixin.open.api.WxOpenConfigStorage; -import me.chanjar.weixin.open.api.impl.WxOpenInMemoryConfigStorage; -import me.chanjar.weixin.open.api.impl.WxOpenInRedisConfigStorage; -import org.apache.commons.lang3.StringUtils; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.context.annotation.Bean; +import com.binarywang.spring.starter.wxjava.open.config.storage.WxOpenInJedisConfigStorageConfiguration; +import com.binarywang.spring.starter.wxjava.open.config.storage.WxOpenInMemoryConfigStorageConfiguration; +import com.binarywang.spring.starter.wxjava.open.config.storage.WxOpenInRedisTemplateConfigStorageConfiguration; +import com.binarywang.spring.starter.wxjava.open.config.storage.WxOpenInRedissonConfigStorageConfiguration; import org.springframework.context.annotation.Configuration; -import redis.clients.jedis.JedisPool; -import redis.clients.jedis.JedisPoolConfig; +import org.springframework.context.annotation.Import; /** * 微信公众号存储策略自动配置. @@ -21,73 +13,11 @@ * @author someone */ @Configuration -@RequiredArgsConstructor +@Import({ + WxOpenInMemoryConfigStorageConfiguration.class, + WxOpenInRedisTemplateConfigStorageConfiguration.class, + WxOpenInJedisConfigStorageConfiguration.class, + WxOpenInRedissonConfigStorageConfiguration.class +}) public class WxOpenStorageAutoConfiguration { - private final WxOpenProperties properties; - - @Autowired(required = false) - private JedisPool jedisPool; - - @Value("${wx.open.config-storage.redis.host:}") - private String redisHost; - - @Bean - @ConditionalOnMissingBean(WxOpenConfigStorage.class) - public WxOpenConfigStorage wxOpenConfigStorage() { - WxOpenProperties.ConfigStorage storage = properties.getConfigStorage(); - WxOpenProperties.StorageType type = storage.getType(); - - if (type == WxOpenProperties.StorageType.redis) { - return getWxOpenInRedisConfigStorage(); - } - return getWxOpenInMemoryConfigStorage(); - } - - private WxOpenInMemoryConfigStorage getWxOpenInMemoryConfigStorage() { - WxOpenInMemoryConfigStorage config = new WxOpenInMemoryConfigStorage(); - setWxOpenInfo(config); - return config; - } - - private WxOpenInRedisConfigStorage getWxOpenInRedisConfigStorage() { - JedisPool poolToUse = jedisPool; - if (jedisPool == null || StringUtils.isNotEmpty(redisHost)) { - poolToUse = getJedisPool(); - } - WxOpenInRedisConfigStorage config = new WxOpenInRedisConfigStorage(poolToUse); - setWxOpenInfo(config); - return config; - } - - private void setWxOpenInfo(WxOpenConfigStorage config) { - config.setComponentAppId(properties.getAppId()); - config.setComponentAppSecret(properties.getSecret()); - config.setComponentToken(properties.getToken()); - config.setComponentAesKey(properties.getAesKey()); - } - - private JedisPool getJedisPool() { - WxOpenProperties.ConfigStorage storage = properties.getConfigStorage(); - RedisProperties redis = storage.getRedis(); - - JedisPoolConfig config = new JedisPoolConfig(); - if (redis.getMaxActive() != null) { - config.setMaxTotal(redis.getMaxActive()); - } - if (redis.getMaxIdle() != null) { - config.setMaxIdle(redis.getMaxIdle()); - } - if (redis.getMaxWaitMillis() != null) { - config.setMaxWaitMillis(redis.getMaxWaitMillis()); - } - if (redis.getMinIdle() != null) { - config.setMinIdle(redis.getMinIdle()); - } - config.setTestOnBorrow(true); - config.setTestWhileIdle(true); - - JedisPool pool = new JedisPool(config, redis.getHost(), redis.getPort(), - redis.getTimeout(), redis.getPassword(), redis.getDatabase()); - return pool; - } } 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 new file mode 100644 index 0000000000..91db545ab9 --- /dev/null +++ b/spring-boot-starters/wx-java-open-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/open/config/storage/AbstractWxOpenConfigStorageConfiguration.java @@ -0,0 +1,54 @@ +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 + */ +public abstract class AbstractWxOpenConfigStorageConfiguration { + + protected WxOpenInMemoryConfigStorage config(WxOpenInMemoryConfigStorage config, WxOpenProperties properties) { + WxOpenProperties.ConfigStorage storage = properties.getConfigStorage(); + config.setWxOpenInfo(properties.getAppId(), properties.getSecret(), properties.getToken(), properties.getAesKey()); + config.setHttpProxyHost(storage.getHttpProxyHost()); + config.setHttpProxyUsername(storage.getHttpProxyUsername()); + config.setHttpProxyPassword(storage.getHttpProxyPassword()); + Integer httpProxyPort = storage.getHttpProxyPort(); + if (httpProxyPort != null) { + config.setHttpProxyPort(httpProxyPort); + } + int maxRetryTimes = storage.getMaxRetryTimes(); + if (maxRetryTimes < 0) { + maxRetryTimes = 0; + } + int retrySleepMillis = storage.getRetrySleepMillis(); + if (retrySleepMillis < 0) { + retrySleepMillis = 1000; + } + 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/config/storage/WxOpenInJedisConfigStorageConfiguration.java b/spring-boot-starters/wx-java-open-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/open/config/storage/WxOpenInJedisConfigStorageConfiguration.java new file mode 100644 index 0000000000..73a0183d72 --- /dev/null +++ b/spring-boot-starters/wx-java-open-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/open/config/storage/WxOpenInJedisConfigStorageConfiguration.java @@ -0,0 +1,73 @@ +package com.binarywang.spring.starter.wxjava.open.config.storage; + +import com.binarywang.spring.starter.wxjava.open.properties.WxOpenProperties; +import com.binarywang.spring.starter.wxjava.open.properties.WxOpenRedisProperties; +import lombok.RequiredArgsConstructor; +import me.chanjar.weixin.open.api.WxOpenConfigStorage; +import me.chanjar.weixin.open.api.impl.WxOpenInMemoryConfigStorage; +import me.chanjar.weixin.open.api.impl.WxOpenInRedisConfigStorage; +import org.apache.commons.lang3.StringUtils; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import redis.clients.jedis.JedisPool; +import redis.clients.jedis.JedisPoolConfig; + +/** + * @author yl + */ +@Configuration +@ConditionalOnProperty( + prefix = WxOpenProperties.PREFIX + ".config-storage", name = "type", havingValue = "jedis" +) +@ConditionalOnClass({JedisPool.class, JedisPoolConfig.class}) +@RequiredArgsConstructor +public class WxOpenInJedisConfigStorageConfiguration extends AbstractWxOpenConfigStorageConfiguration { + private final WxOpenProperties properties; + private final ApplicationContext applicationContext; + + @Bean + @ConditionalOnMissingBean(WxOpenConfigStorage.class) + public WxOpenConfigStorage wxOpenConfigStorage() { + WxOpenInMemoryConfigStorage config = getWxOpenInRedisConfigStorage(); + return this.config(config, properties); + } + + private WxOpenInRedisConfigStorage getWxOpenInRedisConfigStorage() { + WxOpenRedisProperties wxOpenRedisProperties = properties.getConfigStorage().getRedis(); + JedisPool jedisPool; + if (wxOpenRedisProperties != null && StringUtils.isNotEmpty(wxOpenRedisProperties.getHost())) { + jedisPool = getJedisPool(); + } else { + jedisPool = applicationContext.getBean(JedisPool.class); + } + return new WxOpenInRedisConfigStorage(jedisPool, properties.getConfigStorage().getKeyPrefix()); + } + + private JedisPool getJedisPool() { + WxOpenProperties.ConfigStorage storage = properties.getConfigStorage(); + WxOpenRedisProperties redis = storage.getRedis(); + + JedisPoolConfig config = new JedisPoolConfig(); + if (redis.getMaxActive() != null) { + config.setMaxTotal(redis.getMaxActive()); + } + if (redis.getMaxIdle() != null) { + config.setMaxIdle(redis.getMaxIdle()); + } + if (redis.getMaxWaitMillis() != null) { + config.setMaxWaitMillis(redis.getMaxWaitMillis()); + } + if (redis.getMinIdle() != null) { + config.setMinIdle(redis.getMinIdle()); + } + config.setTestOnBorrow(true); + config.setTestWhileIdle(true); + + return new JedisPool(config, redis.getHost(), redis.getPort(), + redis.getTimeout(), redis.getPassword(), redis.getDatabase()); + } +} diff --git a/spring-boot-starters/wx-java-open-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/open/config/storage/WxOpenInMemoryConfigStorageConfiguration.java b/spring-boot-starters/wx-java-open-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/open/config/storage/WxOpenInMemoryConfigStorageConfiguration.java new file mode 100644 index 0000000000..ef17905493 --- /dev/null +++ b/spring-boot-starters/wx-java-open-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/open/config/storage/WxOpenInMemoryConfigStorageConfiguration.java @@ -0,0 +1,30 @@ +package com.binarywang.spring.starter.wxjava.open.config.storage; + +import com.binarywang.spring.starter.wxjava.open.properties.WxOpenProperties; +import lombok.RequiredArgsConstructor; +import me.chanjar.weixin.open.api.WxOpenConfigStorage; +import me.chanjar.weixin.open.api.impl.WxOpenInMemoryConfigStorage; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * @author yl + */ +@Configuration +@ConditionalOnProperty( + prefix = WxOpenProperties.PREFIX + ".config-storage", name = "type", + matchIfMissing = true, havingValue = "memory" +) +@RequiredArgsConstructor +public class WxOpenInMemoryConfigStorageConfiguration extends AbstractWxOpenConfigStorageConfiguration { + private final WxOpenProperties properties; + + @Bean + @ConditionalOnMissingBean(WxOpenConfigStorage.class) + public WxOpenConfigStorage wxOpenConfigStorage() { + WxOpenInMemoryConfigStorage config = new WxOpenInMemoryConfigStorage(); + return this.config(config, properties); + } +} diff --git a/spring-boot-starters/wx-java-open-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/open/config/storage/WxOpenInRedisTemplateConfigStorageConfiguration.java b/spring-boot-starters/wx-java-open-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/open/config/storage/WxOpenInRedisTemplateConfigStorageConfiguration.java new file mode 100644 index 0000000000..79521c921a --- /dev/null +++ b/spring-boot-starters/wx-java-open-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/open/config/storage/WxOpenInRedisTemplateConfigStorageConfiguration.java @@ -0,0 +1,41 @@ +package com.binarywang.spring.starter.wxjava.open.config.storage; + +import com.binarywang.spring.starter.wxjava.open.properties.WxOpenProperties; +import lombok.RequiredArgsConstructor; +import me.chanjar.weixin.open.api.WxOpenConfigStorage; +import me.chanjar.weixin.open.api.impl.WxOpenInMemoryConfigStorage; +import me.chanjar.weixin.open.api.impl.WxOpenInRedisTemplateConfigStorage; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.core.StringRedisTemplate; + +/** + * @author yl + */ +@Configuration +@ConditionalOnProperty( + prefix = WxOpenProperties.PREFIX + ".config-storage", name = "type", havingValue = "redistemplate" +) +@ConditionalOnClass(StringRedisTemplate.class) +@RequiredArgsConstructor +public class WxOpenInRedisTemplateConfigStorageConfiguration extends AbstractWxOpenConfigStorageConfiguration { + private final WxOpenProperties properties; + private final ApplicationContext applicationContext; + + @Bean + @ConditionalOnMissingBean(WxOpenConfigStorage.class) + public WxOpenConfigStorage wxOpenConfigStorage() { + WxOpenInMemoryConfigStorage config = getWxOpenInRedisTemplateConfigStorage(); + return this.config(config, properties); + } + + private WxOpenInRedisTemplateConfigStorage getWxOpenInRedisTemplateConfigStorage() { + StringRedisTemplate redisTemplate = applicationContext.getBean(StringRedisTemplate.class); + return new WxOpenInRedisTemplateConfigStorage(redisTemplate, properties.getConfigStorage().getKeyPrefix()); + } +} diff --git a/spring-boot-starters/wx-java-open-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/open/config/storage/WxOpenInRedissonConfigStorageConfiguration.java b/spring-boot-starters/wx-java-open-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/open/config/storage/WxOpenInRedissonConfigStorageConfiguration.java new file mode 100644 index 0000000000..ea1dce3670 --- /dev/null +++ b/spring-boot-starters/wx-java-open-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/open/config/storage/WxOpenInRedissonConfigStorageConfiguration.java @@ -0,0 +1,64 @@ +package com.binarywang.spring.starter.wxjava.open.config.storage; + +import com.binarywang.spring.starter.wxjava.open.properties.WxOpenProperties; +import com.binarywang.spring.starter.wxjava.open.properties.WxOpenRedisProperties; +import lombok.RequiredArgsConstructor; +import me.chanjar.weixin.open.api.WxOpenConfigStorage; +import me.chanjar.weixin.open.api.impl.WxOpenInMemoryConfigStorage; +import me.chanjar.weixin.open.api.impl.WxOpenInRedissonConfigStorage; +import org.apache.commons.lang3.StringUtils; +import org.redisson.Redisson; +import org.redisson.api.RedissonClient; +import org.redisson.config.Config; +import org.redisson.config.TransportMode; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * @author yl + */ +@Configuration +@ConditionalOnProperty( + prefix = WxOpenProperties.PREFIX + ".config-storage", name = "type", havingValue = "redisson" +) +@ConditionalOnClass({Redisson.class, RedissonClient.class}) +@RequiredArgsConstructor +public class WxOpenInRedissonConfigStorageConfiguration extends AbstractWxOpenConfigStorageConfiguration { + private final WxOpenProperties properties; + private final ApplicationContext applicationContext; + + @Bean + @ConditionalOnMissingBean(WxOpenConfigStorage.class) + public WxOpenConfigStorage wxOpenConfigStorage() { + WxOpenInMemoryConfigStorage config = getWxOpenInRedissonConfigStorage(); + return this.config(config, properties); + } + + private WxOpenInRedissonConfigStorage getWxOpenInRedissonConfigStorage() { + WxOpenRedisProperties wxOpenRedisProperties = properties.getConfigStorage().getRedis(); + RedissonClient redissonClient; + if (wxOpenRedisProperties != null && StringUtils.isNotEmpty(wxOpenRedisProperties.getHost())) { + redissonClient = getRedissonClient(); + } else { + redissonClient = applicationContext.getBean(RedissonClient.class); + } + return new WxOpenInRedissonConfigStorage(redissonClient, properties.getConfigStorage().getKeyPrefix()); + } + + private RedissonClient getRedissonClient() { + WxOpenProperties.ConfigStorage storage = properties.getConfigStorage(); + WxOpenRedisProperties redis = storage.getRedis(); + + Config config = new Config(); + config.useSingleServer() + .setAddress("redis://" + redis.getHost() + ":" + redis.getPort()) + .setDatabase(redis.getDatabase()) + .setPassword(redis.getPassword()); + config.setTransportMode(TransportMode.NIO); + return Redisson.create(config); + } +} diff --git a/spring-boot-starters/wx-java-open-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/open/properties/RedisProperties.java b/spring-boot-starters/wx-java-open-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/open/properties/RedisProperties.java deleted file mode 100644 index 565afa07f5..0000000000 --- a/spring-boot-starters/wx-java-open-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/open/properties/RedisProperties.java +++ /dev/null @@ -1,45 +0,0 @@ -package com.binarywang.spring.starter.wxjava.open.properties; - -import lombok.Data; - -import java.io.Serializable; - -/** - * Redis配置. - * - * @author someone - */ -@Data -public class RedisProperties implements Serializable { - private static final long serialVersionUID = -5924815351660074401L; - - /** - * 主机地址. - */ - private String host = "127.0.0.1"; - - /** - * 端口号. - */ - private int port = 6379; - - /** - * 密码. - */ - private String password; - - /** - * 超时. - */ - private int timeout = 2000; - - /** - * 数据库. - */ - private int database = 0; - - private Integer maxActive; - private Integer maxIdle; - private Integer maxWaitMillis; - private Integer minIdle; -} 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 64cc3d0672..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 @@ -2,6 +2,7 @@ import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.NestedConfigurationProperty; import java.io.Serializable; @@ -40,7 +41,19 @@ public class WxOpenProperties { private String aesKey; /** - * 存储策略, memory, redis. + * 自定义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; + + /** + * 存储策略. */ private ConfigStorage configStorage = new ConfigStorage(); @@ -49,9 +62,78 @@ public class WxOpenProperties { public static class ConfigStorage implements Serializable { private static final long serialVersionUID = 4815731027000065434L; + /** + * 存储类型. + */ private StorageType type = memory; - private RedisProperties redis = new RedisProperties(); + /** + * 指定key前缀. + */ + private String keyPrefix = "wx:open"; + + /** + * redis连接配置. + */ + @NestedConfigurationProperty + private WxOpenRedisProperties redis = new WxOpenRedisProperties(); + + /** + * http客户端类型. + */ + private HttpClientType httpClientType = HttpClientType.httpclient; + + /** + * http代理主机. + */ + private String httpProxyHost; + + /** + * http代理端口. + */ + private Integer httpProxyPort; + + /** + * http代理用户名. + */ + private String httpProxyUsername; + + /** + * http代理密码. + */ + private String httpProxyPassword; + + /** + * http 请求重试间隔 + *
+     *   {@link me.chanjar.weixin.mp.api.impl.BaseWxMpServiceImpl#setRetrySleepMillis(int)}
+     *   {@link cn.binarywang.wx.miniapp.api.impl.BaseWxMaServiceImpl#setRetrySleepMillis(int)}
+     * 
+ */ + private int retrySleepMillis = 1000; + /** + * http 请求最大重试次数 + *
+     *   {@link me.chanjar.weixin.mp.api.impl.BaseWxMpServiceImpl#setMaxRetryTimes(int)}
+     *   {@link cn.binarywang.wx.miniapp.api.impl.BaseWxMaServiceImpl#setMaxRetryTimes(int)}
+     * 
+ */ + private int maxRetryTimes = 5; + + /** + * 连接超时时间,单位毫秒 + */ + private int connectionTimeout = 5000; + + /** + * 读数据超时时间,即socketTimeout,单位毫秒 + */ + private int soTimeout = 5000; + + /** + * 从连接池获取链接的超时时间,单位毫秒 + */ + private int connectionRequestTimeout = 5000; } @@ -61,8 +143,23 @@ public enum StorageType { */ memory, /** - * redis. + * jedis. + */ + jedis, + /** + * redisson. + */ + redisson, + /** + * redistemplate + */ + redistemplate + } + + public enum HttpClientType { + /** + * HttpClient. */ - redis + httpclient } } diff --git a/spring-boot-starters/wx-java-open-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/open/properties/WxOpenRedisProperties.java b/spring-boot-starters/wx-java-open-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/open/properties/WxOpenRedisProperties.java new file mode 100644 index 0000000000..0aafc73da6 --- /dev/null +++ b/spring-boot-starters/wx-java-open-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/open/properties/WxOpenRedisProperties.java @@ -0,0 +1,45 @@ +package com.binarywang.spring.starter.wxjava.open.properties; + +import lombok.Data; + +import java.io.Serializable; + +/** + * Redis配置. + * + * @author someone + */ +@Data +public class WxOpenRedisProperties 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-open-spring-boot-starter/src/main/resources/META-INF/spring.factories b/spring-boot-starters/wx-java-open-spring-boot-starter/src/main/resources/META-INF/spring.factories index d46458f9db..0e5975cf18 100644 --- a/spring-boot-starters/wx-java-open-spring-boot-starter/src/main/resources/META-INF/spring.factories +++ b/spring-boot-starters/wx-java-open-spring-boot-starter/src/main/resources/META-INF/spring.factories @@ -1 +1,2 @@ -org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.binarywang.spring.starter.wxjava.open.config.WxOpenAutoConfiguration +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ +com.binarywang.spring.starter.wxjava.open.config.WxOpenAutoConfiguration diff --git a/spring-boot-starters/wx-java-open-spring-boot-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/spring-boot-starters/wx-java-open-spring-boot-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 0000000000..ce327ba462 --- /dev/null +++ b/spring-boot-starters/wx-java-open-spring-boot-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1 @@ +com.binarywang.spring.starter.wxjava.open.config.WxOpenAutoConfiguration diff --git a/spring-boot-starters/wx-java-pay-spring-boot-starter/README.md b/spring-boot-starters/wx-java-pay-spring-boot-starter/README.md index a4d91fade0..d87a38fb9c 100644 --- a/spring-boot-starters/wx-java-pay-spring-boot-starter/README.md +++ b/spring-boot-starters/wx-java-pay-spring-boot-starter/README.md @@ -8,20 +8,36 @@ ``` 2. 添加配置(application.yml) +###### 1)V2版本 ```yml wx: pay: appId: mchId: mchKey: - subAppId: - subMchId: keyPath: ``` - - - - - - - +###### 2)V3版本 +```yml +wx: + pay: + appId: xxxxxxxxxxx + mchId: 15xxxxxxxxx #商户id + apiV3Key: Dc1DBwSc094jACxxxxxxxxxxxxxxx #V3密钥 + certSerialNo: 62C6CEAA360BCxxxxxxxxxxxxxxx + privateKeyPath: classpath:cert/apiclient_key.pem #apiclient_key.pem证书文件的绝对路径或者以classpath:开头的类路径 + privateCertPath: classpath:cert/apiclient_cert.pem #apiclient_cert.pem证书文件的绝对路径或者以classpath:开头的类路径 +``` +###### 3)V3服务商版本 +```yml +wx: + pay: #微信服务商支付 + configs: + - appId: wxe97b2x9c2b3d #spAppId + mchId: 16486610 #服务商商户 + subAppId: wx118cexxe3c07679 #子appId + subMchId: 16496705 #子商户 + apiV3Key: Dc1DBwSc094jAKDGR5aqqb7PTHr #apiV3密钥 + privateKeyPath: classpath:cert/apiclient_key.pem #服务商证书文件,apiclient_key.pem证书文件的绝对路径或者以classpath:开头的类路径(可以配置绝对路径) + privateCertPath: classpath:cert/apiclient_cert.pem #apiclient_cert.pem证书文件的绝对路径或者以classpath:开头的类路径 +``` 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 6c643615c0..8b67ade1ea 100644 --- a/spring-boot-starters/wx-java-pay-spring-boot-starter/pom.xml +++ b/spring-boot-starters/wx-java-pay-spring-boot-starter/pom.xml @@ -5,7 +5,7 @@ wx-java-spring-boot-starters com.github.binarywang - 3.6.0 + 4.8.0 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 43b2114e6e..758fd929a1 100644 --- a/spring-boot-starters/wx-java-pay-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/pay/config/WxPayAutoConfiguration.java +++ b/spring-boot-starters/wx-java-pay-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/pay/config/WxPayAutoConfiguration.java @@ -49,6 +49,22 @@ public WxPayService wxPayService() { payConfig.setSubAppId(StringUtils.trimToNull(this.properties.getSubAppId())); 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())); + payConfig.setRefundNotifyUrl(StringUtils.trimToNull(this.properties.getRefundNotifyUrl())); + //以下是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 fe8a215650..25f7d7c02e 100644 --- a/spring-boot-starters/wx-java-pay-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/pay/properties/WxPayProperties.java +++ b/spring-boot-starters/wx-java-pay-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/pay/properties/WxPayProperties.java @@ -43,4 +43,82 @@ public class WxPayProperties { * apiclient_cert.p12文件的绝对路径,或者如果放在项目中,请以classpath:开头指定. */ private String keyPath; + + /** + * 微信支付分serviceId + */ + private String serviceId; + + /** + * 证书序列号 + */ + private String certSerialNo; + + /** + * apiV3秘钥 + */ + private String apiv3Key; + + /** + * 微信支付异步回调地址,通知url必须为直接可访问的url,不能携带参数 + */ + private String notifyUrl; + + /** + * 退款结果异步回调地址,通知url必须为直接可访问的url,不能携带参数 + */ + private String refundNotifyUrl; + + /** + * 微信支付分回调地址 + */ + private String payScoreNotifyUrl; + + /** + * 微信支付分授权回调地址 + */ + private String payScorePermissionNotifyUrl; + + /** + * apiv3 商户apiclient_key.pem + */ + private String privateKeyPath; + + /** + * apiv3 商户apiclient_cert.pem + */ + private String privateCertPath; + + /** + * 公钥ID + */ + private String publicKeyId; + + /** + * pub_key.pem证书文件的绝对路径或者以classpath:开头的类路径. + */ + private String publicKeyPath; + + /** + * 微信支付是否使用仿真测试环境. + * 默认不使用 + */ + private boolean useSandboxEnv; + + /** + * 自定义API主机地址,用于替换默认的 https://api.mch.weixin.qq.com + * 例如:http://proxy.company.com:8080 + */ + private String apiHostUrl; + + /** + * 是否将全部v3接口的请求都添加Wechatpay-Serial请求头,默认不添加 + */ + private boolean strictlyNeedWechatPaySerial = false; + + /** + * 是否完全使用公钥模式(用以微信从平台证书到公钥的灰度切换),默认不使用 + */ + private boolean fullPublicKeyModel = false; + } diff --git a/spring-boot-starters/wx-java-pay-spring-boot-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/spring-boot-starters/wx-java-pay-spring-boot-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 0000000000..28cbbace5f --- /dev/null +++ b/spring-boot-starters/wx-java-pay-spring-boot-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1 @@ +com.binarywang.spring.starter.wxjava.pay.config.WxPayAutoConfiguration diff --git a/spring-boot-starters/wx-java-qidian-spring-boot-starter/README.md b/spring-boot-starters/wx-java-qidian-spring-boot-starter/README.md new file mode 100644 index 0000000000..34069fa1fe --- /dev/null +++ b/spring-boot-starters/wx-java-qidian-spring-boot-starter/README.md @@ -0,0 +1,45 @@ +# wx-java-qidian-spring-boot-starter + +## 快速开始 + +1. 引入依赖 + ```xml + + com.github.binarywang + wx-java-qidian-spring-boot-starter + ${version} + + ``` +2. 添加配置(application.properties) + ```properties + # 公众号配置(必填) + wx.qidian.appId = appId + wx.qidian.secret = @secret + wx.qidian.token = @token + wx.qidian.aesKey = @aesKey + # 存储配置redis(可选) + wx.qidian.config-storage.type = Jedis # 配置类型: Memory(默认), Jedis, RedisTemplate + wx.qidian.config-storage.key-prefix = wx # 相关redis前缀配置: wx(默认) + wx.qidian.config-storage.redis.host = 127.0.0.1 + wx.qidian.config-storage.redis.port = 6379 + #单机和sentinel同时存在时,优先使用sentinel配置 + #wx.qidian.config-storage.redis.sentinel-ips=127.0.0.1:16379,127.0.0.1:26379 + #wx.qidian.config-storage.redis.sentinel-name=mymaster + # http客户端配置 + wx.qidian.config-storage.http-client-type=httpclient # http客户端类型: HttpClient(默认), OkHttp, JoddHttp + wx.qidian.config-storage.http-proxy-host= + wx.qidian.config-storage.http-proxy-port= + wx.qidian.config-storage.http-proxy-username= + wx.qidian.config-storage.http-proxy-password= + # 公众号地址host配置 + #wx.qidian.hosts.api-host=http://proxy.com/ + #wx.qidian.hosts.open-host=http://proxy.com/ + #wx.qidian.hosts.mp-host=http://proxy.com/ + ``` +3. 自动注入的类型 + +- `WxQidianService` +- `WxQidianConfigStorage` + +4、参考 demo: +https://github.com/binarywang/wx-java-mp-demo 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 new file mode 100644 index 0000000000..a0fc329434 --- /dev/null +++ b/spring-boot-starters/wx-java-qidian-spring-boot-starter/pom.xml @@ -0,0 +1,66 @@ + + + + wx-java-spring-boot-starters + com.github.binarywang + 4.8.0 + + 4.0.0 + + wx-java-qidian-spring-boot-starter + WxJava - Spring Boot Starter for QiDian + 腾讯企点的 Spring Boot Starter + + + + com.github.binarywang + weixin-java-qidian + ${project.version} + + + redis.clients + jedis + 4.3.2 + compile + + + org.springframework.data + spring-data-redis + provided + + + org.jodd + jodd-http + provided + + + com.squareup.okhttp3 + okhttp + 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-qidian-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/qidian/config/WxQidianAutoConfiguration.java b/spring-boot-starters/wx-java-qidian-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/qidian/config/WxQidianAutoConfiguration.java new file mode 100644 index 0000000000..bb66fde262 --- /dev/null +++ b/spring-boot-starters/wx-java-qidian-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/qidian/config/WxQidianAutoConfiguration.java @@ -0,0 +1,17 @@ +package com.binarywang.spring.starter.wxjava.qidian.config; + +import com.binarywang.spring.starter.wxjava.qidian.properties.WxQidianProperties; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; + +/** + * . + * + * @author someone + */ +@Configuration +@EnableConfigurationProperties(WxQidianProperties.class) +@Import({ WxQidianStorageAutoConfiguration.class, WxQidianServiceAutoConfiguration.class }) +public class WxQidianAutoConfiguration { +} diff --git a/spring-boot-starters/wx-java-qidian-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/qidian/config/WxQidianServiceAutoConfiguration.java b/spring-boot-starters/wx-java-qidian-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/qidian/config/WxQidianServiceAutoConfiguration.java new file mode 100644 index 0000000000..3af628d01e --- /dev/null +++ b/spring-boot-starters/wx-java-qidian-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/qidian/config/WxQidianServiceAutoConfiguration.java @@ -0,0 +1,63 @@ +package com.binarywang.spring.starter.wxjava.qidian.config; + +import com.binarywang.spring.starter.wxjava.qidian.enums.HttpClientType; +import com.binarywang.spring.starter.wxjava.qidian.properties.WxQidianProperties; +import me.chanjar.weixin.qidian.api.WxQidianService; +import me.chanjar.weixin.qidian.api.impl.WxQidianServiceHttpClientImpl; +import me.chanjar.weixin.qidian.api.impl.WxQidianServiceImpl; +import me.chanjar.weixin.qidian.api.impl.WxQidianServiceJoddHttpImpl; +import me.chanjar.weixin.qidian.api.impl.WxQidianServiceOkHttpImpl; +import me.chanjar.weixin.qidian.config.WxQidianConfigStorage; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * 腾讯企点相关服务自动注册. + * + * @author alegria + */ +@Configuration +public class WxQidianServiceAutoConfiguration { + + @Bean + @ConditionalOnMissingBean + public WxQidianService wxQidianService(WxQidianConfigStorage configStorage, WxQidianProperties wxQidianProperties) { + HttpClientType httpClientType = wxQidianProperties.getConfigStorage().getHttpClientType(); + WxQidianService wxQidianService; + switch (httpClientType) { + case OkHttp: + wxQidianService = newWxQidianServiceOkHttpImpl(); + break; + case JoddHttp: + wxQidianService = newWxQidianServiceJoddHttpImpl(); + break; + case HttpClient: + wxQidianService = newWxQidianServiceHttpClientImpl(); + break; + default: + wxQidianService = newWxQidianServiceImpl(); + break; + } + + wxQidianService.setWxMpConfigStorage(configStorage); + return wxQidianService; + } + + private WxQidianService newWxQidianServiceImpl() { + return new WxQidianServiceImpl(); + } + + private WxQidianService newWxQidianServiceHttpClientImpl() { + return new WxQidianServiceHttpClientImpl(); + } + + private WxQidianService newWxQidianServiceOkHttpImpl() { + return new WxQidianServiceOkHttpImpl(); + } + + private WxQidianService newWxQidianServiceJoddHttpImpl() { + return new WxQidianServiceJoddHttpImpl(); + } + +} 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 new file mode 100644 index 0000000000..01ba91b565 --- /dev/null +++ b/spring-boot-starters/wx-java-qidian-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/qidian/config/WxQidianStorageAutoConfiguration.java @@ -0,0 +1,168 @@ +package com.binarywang.spring.starter.wxjava.qidian.config; + +import com.binarywang.spring.starter.wxjava.qidian.enums.StorageType; +import com.binarywang.spring.starter.wxjava.qidian.properties.RedisProperties; +import com.binarywang.spring.starter.wxjava.qidian.properties.WxQidianProperties; +import com.google.common.collect.Sets; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import me.chanjar.weixin.common.redis.JedisWxRedisOps; +import me.chanjar.weixin.common.redis.RedisTemplateWxRedisOps; +import me.chanjar.weixin.common.redis.WxRedisOps; +import me.chanjar.weixin.qidian.bean.WxQidianHostConfig; +import me.chanjar.weixin.qidian.config.WxQidianConfigStorage; +import me.chanjar.weixin.qidian.config.impl.WxQidianDefaultConfigImpl; +import me.chanjar.weixin.qidian.config.impl.WxQidianRedisConfigImpl; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.core.StringRedisTemplate; +import redis.clients.jedis.Jedis; +import redis.clients.jedis.JedisPool; +import redis.clients.jedis.JedisPoolConfig; +import redis.clients.jedis.JedisSentinelPool; +import redis.clients.jedis.util.Pool; + +import java.util.Set; + +/** + * 腾讯企点存储策略自动配置. + * + * @author alegria + */ +@Slf4j +@Configuration +@RequiredArgsConstructor +public class WxQidianStorageAutoConfiguration { + private final ApplicationContext applicationContext; + + private final WxQidianProperties wxQidianProperties; + + @Value("${wx.mp.config-storage.redis.host:") + private String redisHost; + + @Value("${wx.mp.configStorage.redis.host:") + private String redisHost2; + + @Bean + @ConditionalOnMissingBean(WxQidianConfigStorage.class) + public WxQidianConfigStorage wxQidianConfigStorage() { + StorageType type = wxQidianProperties.getConfigStorage().getType(); + WxQidianConfigStorage config; + switch (type) { + case Jedis: + config = jedisConfigStorage(); + break; + case RedisTemplate: + config = redisTemplateConfigStorage(); + break; + default: + config = defaultConfigStorage(); + break; + } + // wx host config + if (null != wxQidianProperties.getHosts() && StringUtils.isNotEmpty(wxQidianProperties.getHosts().getApiHost())) { + WxQidianHostConfig hostConfig = new WxQidianHostConfig(); + hostConfig.setApiHost(wxQidianProperties.getHosts().getApiHost()); + hostConfig.setQidianHost(wxQidianProperties.getHosts().getQidianHost()); + hostConfig.setOpenHost(wxQidianProperties.getHosts().getOpenHost()); + config.setHostConfig(hostConfig); + } + return config; + } + + private WxQidianConfigStorage defaultConfigStorage() { + WxQidianDefaultConfigImpl config = new WxQidianDefaultConfigImpl(); + setWxMpInfo(config); + return config; + } + + private WxQidianConfigStorage jedisConfigStorage() { + Pool jedisPool; + if (StringUtils.isNotEmpty(redisHost) || StringUtils.isNotEmpty(redisHost2)) { + jedisPool = getJedisPool(); + } else { + jedisPool = applicationContext.getBean(JedisPool.class); + } + WxRedisOps redisOps = new JedisWxRedisOps(jedisPool); + WxQidianRedisConfigImpl wxQidianRedisConfig = new WxQidianRedisConfigImpl(redisOps, + wxQidianProperties.getConfigStorage().getKeyPrefix()); + setWxMpInfo(wxQidianRedisConfig); + return wxQidianRedisConfig; + } + + private WxQidianConfigStorage redisTemplateConfigStorage() { + StringRedisTemplate redisTemplate = null; + try { + redisTemplate = applicationContext.getBean(StringRedisTemplate.class); + } catch (Exception e) { + log.error(e.getMessage(), e); + } + try { + if (null == redisTemplate) { + redisTemplate = (StringRedisTemplate) applicationContext.getBean("stringRedisTemplate"); + } + } catch (Exception e) { + log.error(e.getMessage(), e); + } + + if (null == redisTemplate) { + redisTemplate = (StringRedisTemplate) applicationContext.getBean("redisTemplate"); + } + + WxRedisOps redisOps = new RedisTemplateWxRedisOps(redisTemplate); + WxQidianRedisConfigImpl wxMpRedisConfig = new WxQidianRedisConfigImpl(redisOps, + wxQidianProperties.getConfigStorage().getKeyPrefix()); + + setWxMpInfo(wxMpRedisConfig); + return wxMpRedisConfig; + } + + private void setWxMpInfo(WxQidianDefaultConfigImpl config) { + WxQidianProperties properties = wxQidianProperties; + WxQidianProperties.ConfigStorage configStorageProperties = properties.getConfigStorage(); + config.setAppId(properties.getAppId()); + config.setSecret(properties.getSecret()); + config.setToken(properties.getToken()); + config.setAesKey(properties.getAesKey()); + + config.setHttpProxyHost(configStorageProperties.getHttpProxyHost()); + config.setHttpProxyUsername(configStorageProperties.getHttpProxyUsername()); + config.setHttpProxyPassword(configStorageProperties.getHttpProxyPassword()); + if (configStorageProperties.getHttpProxyPort() != null) { + config.setHttpProxyPort(configStorageProperties.getHttpProxyPort()); + } + } + + private Pool getJedisPool() { + WxQidianProperties.ConfigStorage storage = wxQidianProperties.getConfigStorage(); + RedisProperties redis = storage.getRedis(); + + JedisPoolConfig config = new JedisPoolConfig(); + if (redis.getMaxActive() != null) { + config.setMaxTotal(redis.getMaxActive()); + } + if (redis.getMaxIdle() != null) { + config.setMaxIdle(redis.getMaxIdle()); + } + if (redis.getMaxWaitMillis() != null) { + config.setMaxWaitMillis(redis.getMaxWaitMillis()); + } + if (redis.getMinIdle() != null) { + config.setMinIdle(redis.getMinIdle()); + } + config.setTestOnBorrow(true); + config.setTestWhileIdle(true); + if (StringUtils.isNotEmpty(redis.getSentinelIps())) { + + Set sentinels = Sets.newHashSet(redis.getSentinelIps().split(",")); + return new JedisSentinelPool(redis.getSentinelName(), sentinels,config); + } + + return new JedisPool(config, redis.getHost(), redis.getPort(), redis.getTimeout(), redis.getPassword(), + redis.getDatabase()); + } +} diff --git a/spring-boot-starters/wx-java-qidian-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/qidian/enums/HttpClientType.java b/spring-boot-starters/wx-java-qidian-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/qidian/enums/HttpClientType.java new file mode 100644 index 0000000000..04589a911b --- /dev/null +++ b/spring-boot-starters/wx-java-qidian-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/qidian/enums/HttpClientType.java @@ -0,0 +1,26 @@ +package com.binarywang.spring.starter.wxjava.qidian.enums; + +/** + * httpclient类型. + * + * @author Binary Wang + * created on 2020-08-30 + */ +public enum HttpClientType { + /** + * HttpClient. + */ + HttpClient, + /** + * OkHttp. + */ + OkHttp, + /** + * JoddHttp. + */ + JoddHttp, + /** + * HttpComponents. + */ + HttpComponents, +} diff --git a/spring-boot-starters/wx-java-qidian-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/qidian/enums/StorageType.java b/spring-boot-starters/wx-java-qidian-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/qidian/enums/StorageType.java new file mode 100644 index 0000000000..f4e26bc156 --- /dev/null +++ b/spring-boot-starters/wx-java-qidian-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/qidian/enums/StorageType.java @@ -0,0 +1,26 @@ +package com.binarywang.spring.starter.wxjava.qidian.enums; + +/** + * storage类型. + * + * @author Binary Wang + * created on 2020-08-30 + */ +public enum StorageType { + /** + * 内存. + */ + Memory, + /** + * redis(JedisClient). + */ + Jedis, + /** + * redis(Redisson). + */ + Redisson, + /** + * redis(RedisTemplate). + */ + RedisTemplate +} diff --git a/spring-boot-starters/wx-java-qidian-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/qidian/properties/HostConfig.java b/spring-boot-starters/wx-java-qidian-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/qidian/properties/HostConfig.java new file mode 100644 index 0000000000..92ade849fa --- /dev/null +++ b/spring-boot-starters/wx-java-qidian-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/qidian/properties/HostConfig.java @@ -0,0 +1,18 @@ +package com.binarywang.spring.starter.wxjava.qidian.properties; + +import lombok.Data; + +import java.io.Serializable; + +@Data +public class HostConfig implements Serializable { + + private static final long serialVersionUID = -4172767630740346001L; + + private String apiHost; + + private String openHost; + + private String qidianHost; + +} diff --git a/spring-boot-starters/wx-java-qidian-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/qidian/properties/RedisProperties.java b/spring-boot-starters/wx-java-qidian-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/qidian/properties/RedisProperties.java new file mode 100644 index 0000000000..abfad572e7 --- /dev/null +++ b/spring-boot-starters/wx-java-qidian-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/qidian/properties/RedisProperties.java @@ -0,0 +1,56 @@ +package com.binarywang.spring.starter.wxjava.qidian.properties; + +import lombok.Data; + +import java.io.Serializable; + +/** + * redis 配置属性. + * + * @author Binary Wang + * created on 2020-08-30 + */ +@Data +public class RedisProperties implements Serializable { + private static final long serialVersionUID = -5924815351660074401L; + + /** + * 主机地址. + */ + private String host = "127.0.0.1"; + + /** + * 端口号. + */ + private int port = 6379; + + /** + * 密码. + */ + private String password; + + /** + * 超时. + */ + private int timeout = 2000; + + /** + * 数据库. + */ + private int database = 0; + + /** + * sentinel ips + */ + private String sentinelIps; + + /** + * sentinel name + */ + private String sentinelName; + + private Integer maxActive; + private Integer maxIdle; + private Integer maxWaitMillis; + private Integer minIdle; +} diff --git a/spring-boot-starters/wx-java-qidian-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/qidian/properties/WxQidianProperties.java b/spring-boot-starters/wx-java-qidian-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/qidian/properties/WxQidianProperties.java new file mode 100644 index 0000000000..bec5dfcce0 --- /dev/null +++ b/spring-boot-starters/wx-java-qidian-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/qidian/properties/WxQidianProperties.java @@ -0,0 +1,99 @@ +package com.binarywang.spring.starter.wxjava.qidian.properties; + +import com.binarywang.spring.starter.wxjava.qidian.enums.HttpClientType; +import com.binarywang.spring.starter.wxjava.qidian.enums.StorageType; +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; + +import java.io.Serializable; + +import static com.binarywang.spring.starter.wxjava.qidian.enums.StorageType.Memory; +import static com.binarywang.spring.starter.wxjava.qidian.properties.WxQidianProperties.PREFIX; + +/** + * 企点接入相关配置属性. + * + * @author someone + */ +@Data +@ConfigurationProperties(PREFIX) +public class WxQidianProperties { + public static final String PREFIX = "wx.qidian"; + + /** + * 设置腾讯企点的appid. + */ + private String appId; + + /** + * 设置腾讯企点的app secret. + */ + private String secret; + + /** + * 设置腾讯企点的token. + */ + private String token; + + /** + * 设置腾讯企点的EncodingAESKey. + */ + private String aesKey; + + /** + * 自定义host配置 + */ + private HostConfig hosts; + + /** + * 存储策略 + */ + private ConfigStorage configStorage = new ConfigStorage(); + + @Data + public static class ConfigStorage implements Serializable { + private static final long serialVersionUID = 4815731027000065434L; + + /** + * 存储类型. + */ + private StorageType type = Memory; + + /** + * 指定key前缀. + */ + private String keyPrefix = "wx"; + + /** + * redis连接配置. + */ + private RedisProperties redis = new RedisProperties(); + + /** + * http客户端类型. + */ + private HttpClientType httpClientType = HttpClientType.HttpComponents; + + /** + * http代理主机. + */ + private String httpProxyHost; + + /** + * http代理端口. + */ + private Integer httpProxyPort; + + /** + * http代理用户名. + */ + private String httpProxyUsername; + + /** + * http代理密码. + */ + private String httpProxyPassword; + + } + +} diff --git a/spring-boot-starters/wx-java-qidian-spring-boot-starter/src/main/resources/META-INF/spring.factories b/spring-boot-starters/wx-java-qidian-spring-boot-starter/src/main/resources/META-INF/spring.factories new file mode 100644 index 0000000000..bfcb7bf919 --- /dev/null +++ b/spring-boot-starters/wx-java-qidian-spring-boot-starter/src/main/resources/META-INF/spring.factories @@ -0,0 +1 @@ +org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.binarywang.spring.starter.wxjava.qidian.config.WxQidianAutoConfiguration diff --git a/spring-boot-starters/wx-java-qidian-spring-boot-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/spring-boot-starters/wx-java-qidian-spring-boot-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 0000000000..6e7e511448 --- /dev/null +++ b/spring-boot-starters/wx-java-qidian-spring-boot-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1 @@ +com.binarywang.spring.starter.wxjava.qidian.config.WxQidianAutoConfiguration diff --git a/weixin-graal/pom.xml b/weixin-graal/pom.xml new file mode 100644 index 0000000000..3a220b2888 --- /dev/null +++ b/weixin-graal/pom.xml @@ -0,0 +1,40 @@ + + + 4.0.0 + + com.github.binarywang + wx-java + 4.8.0 + + + weixin-graal + WxJava - Graal + 微信开发Java内部配合graal以产生native-image配置的辅助工具, 可以通过项目的 native-image Profile 来启用: mvn -P native-image ... + + + + + + org.projectlombok + lombok + compile + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.5.1 + + none + + + + + + diff --git a/weixin-graal/src/main/java/com/github/binarywang/wx/graal/GraalProcessor.java b/weixin-graal/src/main/java/com/github/binarywang/wx/graal/GraalProcessor.java new file mode 100644 index 0000000000..a983a51897 --- /dev/null +++ b/weixin-graal/src/main/java/com/github/binarywang/wx/graal/GraalProcessor.java @@ -0,0 +1,177 @@ +package com.github.binarywang.wx.graal; + +import lombok.Data; + +import javax.annotation.processing.AbstractProcessor; +import javax.annotation.processing.RoundEnvironment; +import javax.annotation.processing.SupportedAnnotationTypes; +import javax.annotation.processing.SupportedSourceVersion; +import javax.lang.model.SourceVersion; +import javax.lang.model.element.TypeElement; +import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.util.ElementFilter; +import javax.tools.FileObject; +import javax.tools.StandardLocation; +import java.io.IOException; +import java.io.Writer; +import java.util.Set; +import java.util.SortedSet; +import java.util.TreeSet; + +/** + * 目前仅仅处理@Data,且必须在lombok自己的processor之前执行,千万注意!!!!! + * + * @author outersky + */ +@SupportedAnnotationTypes("lombok.Data") +@SupportedSourceVersion(SourceVersion.RELEASE_8) +public class GraalProcessor extends AbstractProcessor { + private static final String REFLECTION_CONFIG_JSON = "reflection-config.json"; + private static final String NATIVE_IMAGE_PROPERTIES = "native-image.properties"; + + private final SortedSet classSet = new TreeSet<>(); + private String shortestPackageName = null; + + @Override + public boolean process(Set annotations, RoundEnvironment roundEnv) { + for (TypeElement annotatedClass : ElementFilter.typesIn(roundEnv.getElementsAnnotatedWith(Data.class))) { + registerClass(annotatedClass.getQualifiedName().toString()); + handleSuperClass(annotatedClass); + } + + //只有最后一轮才可以写文件,否则文件会被重复打开,报错! + if (!roundEnv.processingOver()) { + return false; + } + + // 如果没有文件要写,跳过 + if (classSet.isEmpty()) { + return false; + } + + writeFiles(); + + //必须返回false,以便让lombok能继续处理。 + return false; + } + + /** + * 设置当前最短的package名称 + * + * @param packageName 包名 + */ + private void setShortestPackageName(String packageName) { + if (shortestPackageName == null) { + shortestPackageName = packageName; + } else if (packageName.length() < shortestPackageName.length()) { + shortestPackageName = packageName; + } + } + + /** + * 更加完整的类名来获取package名称 + * + * @param fullClassName 完整的类名 + * @return package name + */ + private String getPackageName(String fullClassName) { + int last = fullClassName.lastIndexOf('.'); + if (last == -1) { + return fullClassName; + } + return fullClassName.substring(0, last); + } + + /** + * 保存文件 + * META-INF/native-image/.../reflection-config.json + * META-INF/native-image/.../native-image.properties + */ + private void writeFiles() { + String basePackage = shortestPackageName; + + String module; + if (basePackage.contains(".")) { + final int i = basePackage.lastIndexOf('.'); + module = basePackage.substring(i + 1); + basePackage = basePackage.substring(0, i); + } else { + module = basePackage; + } + + String path = "META-INF/native-image/" + basePackage + "/" + module + "/"; + String reflectFile = path + REFLECTION_CONFIG_JSON; + String propsFile = path + NATIVE_IMAGE_PROPERTIES; + try { + FileObject fileObject = processingEnv.getFiler().createResource(StandardLocation.CLASS_OUTPUT, "", propsFile); + try (Writer writer = fileObject.openWriter();) { + writer.append("Args = -H:ReflectionConfigurationResources=${.}/" + REFLECTION_CONFIG_JSON); + } + } catch (IOException e) { + e.printStackTrace(); + } + + try { + FileObject fileObject = processingEnv.getFiler().createResource(StandardLocation.CLASS_OUTPUT, "", reflectFile); + try (Writer writer = fileObject.openWriter();) { + writer.write("[\n"); + boolean first = true; + for (String name : classSet) { + if (first) { + first = false; + } else { + writer.write(","); + } + writer.write(assetGraalJsonElement(name)); + writer.append('\n'); + } + writer.write("]"); + } + } catch (IOException e) { + e.printStackTrace(); + } + + } + + private String assetGraalJsonElement(String className) { + return "{\n" + + " \"name\" : \"" + className + "\",\n" + + " \"allDeclaredFields\":true,\n" + + " \"allDeclaredMethods\":true,\n" + + " \"allDeclaredConstructors\":true,\n" + + " \"allPublicMethods\" : true\n" + + "}"; + } + + /** + * 登记一个class + * + * @param className 完整的类名 + */ + private void registerClass(String className) { + classSet.add(className); + setShortestPackageName(getPackageName(className)); + } + + /** + * 获取一个类型的所有的父类,并登记 + * + * @param typeElement 类型元素 + */ + private void handleSuperClass(TypeElement typeElement) { + TypeMirror superclass = typeElement.getSuperclass(); + if (superclass.getKind() == TypeKind.DECLARED) { + TypeElement s = (TypeElement) ((DeclaredType) superclass).asElement(); + String sName = s.toString(); + // ignore java.**/javax.** + if (sName.startsWith("java.") || sName.startsWith("javax.")) { + return; + } + registerClass(sName); + handleSuperClass(s); + } + } + +} diff --git a/weixin-graal/src/main/resources/META-INF/services/javax.annotation.processing.Processor b/weixin-graal/src/main/resources/META-INF/services/javax.annotation.processing.Processor new file mode 100644 index 0000000000..f358b92ef9 --- /dev/null +++ b/weixin-graal/src/main/resources/META-INF/services/javax.annotation.processing.Processor @@ -0,0 +1 @@ +com.github.binarywang.wx.graal.GraalProcessor diff --git a/weixin-java-channel/pom.xml b/weixin-java-channel/pom.xml new file mode 100644 index 0000000000..28b3e2ed6c --- /dev/null +++ b/weixin-java-channel/pom.xml @@ -0,0 +1,173 @@ + + + 4.0.0 + + com.github.binarywang + wx-java + 4.8.0 + + + weixin-java-channel + WxJava - Channel Java SDK + 微信视频号/微信小店 Java SDK + + + 2.18.4 + + + + + com.github.binarywang + weixin-java-common + ${project.version} + + + + org.jodd + jodd-http + provided + + + org.apache.httpcomponents + httpclient + provided + + + org.apache.httpcomponents + httpmime + provided + + + org.apache.httpcomponents.client5 + httpclient5 + provided + + + + com.fasterxml.jackson.core + jackson-core + ${jackson.version} + + + com.fasterxml.jackson.core + jackson-annotations + ${jackson.version} + + + com.fasterxml.jackson.dataformat + jackson-dataformat-xml + ${jackson.version} + true + + + + org.bouncycastle + bcpkix-jdk18on + + + org.projectlombok + lombok + + + org.redisson + redisson + + + + com.squareup.okhttp3 + okhttp + provided + + + + org.testng + testng + test + + + ch.qos.logback + logback-classic + test + + + com.google.inject + guice + test + + + org.eclipse.jetty + jetty-server + test + + + org.eclipse.jetty + jetty-servlet + test + + + org.assertj + assertj-guava + test + + + redis.clients + jedis + + + + com.github.jedis-lock + jedis-lock + true + + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + + src/test/resources/testng.xml + + + + + + + + + native-image + + false + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.5.1 + + + com.github.binarywang.wx.graal.GraalProcessor,lombok.launch.AnnotationProcessorHider$AnnotationProcessor,lombok.launch.AnnotationProcessorHider$ClaimingProcessor + + + + com.github.binarywang + weixin-graal + ${project.version} + + + + + + + + + + + diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/BaseWxChannelMessageService.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/BaseWxChannelMessageService.java new file mode 100644 index 0000000000..a908da9479 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/BaseWxChannelMessageService.java @@ -0,0 +1,540 @@ +package me.chanjar.weixin.channel.api; + +import java.util.Map; +import me.chanjar.weixin.channel.bean.message.after.AfterSaleMessage; +import me.chanjar.weixin.channel.bean.message.after.ComplaintMessage; +import me.chanjar.weixin.channel.bean.message.coupon.CouponActionMessage; +import me.chanjar.weixin.channel.bean.message.coupon.CouponReceiveMessage; +import me.chanjar.weixin.channel.bean.message.coupon.UserCouponExpireMessage; +import me.chanjar.weixin.channel.bean.message.fund.AccountNotifyMessage; +import me.chanjar.weixin.channel.bean.message.fund.QrNotifyMessage; +import me.chanjar.weixin.channel.bean.message.fund.WithdrawNotifyMessage; +import me.chanjar.weixin.channel.bean.message.order.OrderCancelMessage; +import me.chanjar.weixin.channel.bean.message.order.OrderConfirmMessage; +import me.chanjar.weixin.channel.bean.message.order.OrderDeliveryMessage; +import me.chanjar.weixin.channel.bean.message.order.OrderExtMessage; +import me.chanjar.weixin.channel.bean.message.order.OrderIdMessage; +import me.chanjar.weixin.channel.bean.message.order.OrderPayMessage; +import me.chanjar.weixin.channel.bean.message.order.OrderSettleMessage; +import me.chanjar.weixin.channel.bean.message.order.OrderStatusMessage; +import me.chanjar.weixin.channel.bean.message.product.BrandMessage; +import me.chanjar.weixin.channel.bean.message.product.CategoryAuditMessage; +import me.chanjar.weixin.channel.bean.message.product.SpuAuditMessage; +import me.chanjar.weixin.channel.bean.message.product.SpuStockMessage; +import me.chanjar.weixin.channel.bean.message.store.CloseStoreMessage; +import me.chanjar.weixin.channel.bean.message.store.NicknameUpdateMessage; +import me.chanjar.weixin.channel.bean.message.supplier.SupplierItemMessage; +import me.chanjar.weixin.channel.bean.message.vip.ExchangeInfoMessage; +import me.chanjar.weixin.channel.bean.message.vip.UserInfoMessage; +import me.chanjar.weixin.channel.bean.message.voucher.VoucherMessage; +import me.chanjar.weixin.channel.message.WxChannelMessage; +import me.chanjar.weixin.channel.message.WxChannelMessageRouterRule; +import me.chanjar.weixin.common.session.WxSessionManager; + +/** + * @author Zeyes + */ +public interface BaseWxChannelMessageService { + + /** + * 路由微信消息 + * + * @param message 消息 + * @param content 消息原始内容 + * @param appId appId + * @param service 服务实例 + * @return Object + */ + Object route(final WxChannelMessage message, final String content, final String appId, + final WxChannelService service); + + /** + * 添加一条规则进入路由器 + * + * @param rule 规则 + */ + void addRule(WxChannelMessageRouterRule rule); + + /** + * 订单下单 + * + * @param message 消息 + * @param content 消息原始内容 + * @param appId appId + * @param context 上下文 + * @param sessionManager session管理器 + */ + void orderNew(final OrderIdMessage message, final String content, final String appId, + final Map context, final WxSessionManager sessionManager); + + /** + * 订单取消 + * + * @param message 消息 + * @param content 消息原始内容 + * @param appId appId + * @param context 上下文 + * @param sessionManager session管理器 + */ + void orderCancel(OrderCancelMessage message, final String content, final String appId, + final Map context, final WxSessionManager sessionManager); + + /** + * 订单支付成功 + * + * @param message 消息 + * @param content 消息原始内容 + * @param appId appId + * @param context 上下文 + * @param sessionManager session管理器 + */ + void orderPay(OrderPayMessage message, final String content, final String appId, final Map context, + final WxSessionManager sessionManager); + + /** + * 订单待发货 + * + * @param message 消息 + * @param content 消息原始内容 + * @param appId appId + * @param context 上下文 + * @param sessionManager session管理器 + */ + void orderWaitShipping(OrderIdMessage message, final String content, final String appId, final Map context, + final WxSessionManager sessionManager); + + /** + * 订单发货 + * + * @param message 消息 + * @param content 消息原始内容 + * @param appId appId + * @param context 上下文 + * @param sessionManager session管理器 + */ + void orderDelivery(OrderDeliveryMessage message, final String content, final String appId, + final Map context, final WxSessionManager sessionManager); + + /** + * 订单确认收货 + * + * @param message 消息 + * @param content 消息原始内容 + * @param appId appId + * @param context 上下文 + * @param sessionManager session管理器 + */ + void orderConfirm(OrderConfirmMessage message, final String content, final String appId, + final Map context, final WxSessionManager sessionManager); + + /** + * 订单结算成功 + * + * @param message 消息 + * @param content 消息原始内容 + * @param appId appId + * @param context 上下文 + * @param sessionManager session管理器 + */ + void orderSettle(OrderSettleMessage message, final String content, final String appId, + final Map context, final WxSessionManager sessionManager); + + /** + * 订单其他信息更新 + * + * @param message 消息 + * @param content 消息原始内容 + * @param appId appId + * @param context 上下文 + * @param sessionManager session管理器 + */ + void orderExtInfoUpdate(OrderExtMessage message, final String content, final String appId, + final Map context, final WxSessionManager sessionManager); + + /** + * 订单状态更新 + * + * @param message 消息 + * @param content 消息原始内容 + * @param appId appId + * @param context 上下文 + * @param sessionManager session管理器 + */ + void orderStatusUpdate(OrderStatusMessage message, final String content, final String appId, + final Map context, final WxSessionManager sessionManager); + + /** + * 商品审核结果 + * + * @param message 消息 + * @param content 消息原始内容 + * @param appId appId + * @param context 上下文 + * @param sessionManager session管理器 + */ + void spuAudit(SpuAuditMessage message, final String content, final String appId, final Map context, + final WxSessionManager sessionManager); + + /** + * 商品系统下架通知 + * + * @param message 消息 + * @param content 消息原始内容 + * @param appId appId + * @param context 上下文 + * @param sessionManager session管理器 + */ + void spuStatusUpdate(SpuAuditMessage message, final String content, final String appId, + final Map context, final WxSessionManager sessionManager); + + /** + * 商品更新通知 + * + * @param message 消息 + * @param content 消息原始内容 + * @param appId appId + * @param context 上下文 + * @param sessionManager session管理器 + */ + void spuUpdate(SpuAuditMessage message, final String content, final String appId, final Map context, + final WxSessionManager sessionManager); + + /** + * 商品库存不足通知 + * + * @param message 消息 + * @param content 消息原始内容 + * @param appId appId + * @param context 上下文 + * @param sessionManager session管理器 + */ + void stockNoEnough(SpuStockMessage message, final String content, final String appId, + final Map context, final WxSessionManager sessionManager); + + /** + * 类目审核结果 + * + * @param message 消息 + * @param content 消息原始内容 + * @param appId appId + * @param context 上下文 + * @param sessionManager session管理器 + */ + void categoryAudit(CategoryAuditMessage message, final String content, final String appId, + final Map context, final WxSessionManager sessionManager); + + /** + * 品牌更新 + * + * @param message 消息 + * @param content 消息原始内容 + * @param appId appId + * @param context 上下文 + * @param sessionManager session管理器 + */ + void brandUpdate(BrandMessage message, final String content, final String appId, final Map context, + final WxSessionManager sessionManager); + + /** + * 售后单状态更新 + * + * @param message 消息 + * @param content 消息原始内容 + * @param appId appId + * @param context 上下文 + * @param sessionManager session管理器 + */ + void afterSaleStatusUpdate(AfterSaleMessage message, final String content, final String appId, + final Map context, final WxSessionManager sessionManager); + + /** + * 纠纷回调 + * + * @param message 消息 + * @param content 消息原始内容 + * @param appId appId + * @param context 上下文 + * @param sessionManager session管理器 + */ + void complaintNotify(ComplaintMessage message, final String content, final String appId, + final Map context, final WxSessionManager sessionManager); + + /** + * 用户领券通知 + * + * @param message 消息 + * @param content 消息原始内容 + * @param appId appId + * @param context 上下文 + * @param sessionManager session管理器 + */ + void couponReceive(CouponReceiveMessage message, final String content, final String appId, + final Map context, final WxSessionManager sessionManager); + + /** + * 创建优惠券通知 + * + * @param message 消息 + * @param content 消息原始内容 + * @param appId appId + * @param context 上下文 + * @param sessionManager session管理器 + */ + void couponCreate(CouponActionMessage message, final String content, final String appId, + final Map context, final WxSessionManager sessionManager); + + /** + * 优惠券删除通知 + * + * @param message 消息 + * @param content 消息原始内容 + * @param appId appId + * @param context 上下文 + * @param sessionManager session管理器 + */ + void couponDelete(CouponActionMessage message, final String content, final String appId, + final Map context, final WxSessionManager sessionManager); + + /** + * 优惠券过期通知 + * + * @param message 消息 + * @param content 消息原始内容 + * @param appId appId + * @param context 上下文 + * @param sessionManager session管理器 + */ + void couponExpire(CouponActionMessage message, final String content, final String appId, + final Map context, final WxSessionManager sessionManager); + + /** + * 更新优惠券信息通知 + * + * @param message 消息 + * @param content 消息原始内容 + * @param appId appId + * @param context 上下文 + * @param sessionManager session管理器 + */ + void couponUpdate(CouponActionMessage message, final String content, final String appId, + final Map context, final WxSessionManager sessionManager); + + /** + * 优惠券作废通知 + * + * @param message 消息 + * @param content 消息原始内容 + * @param appId appId + * @param context 上下文 + * @param sessionManager session管理器 + */ + void couponInvalid(CouponActionMessage message, final String content, final String appId, + final Map context, final WxSessionManager sessionManager); + + /** + * 用户优惠券过期通知 + * + * @param message 消息 + * @param content 消息原始内容 + * @param appId appId + * @param context 上下文 + * @param sessionManager session管理器 + */ + void userCouponExpire(UserCouponExpireMessage message, final String content, final String appId, + final Map context, final WxSessionManager sessionManager); + + /** + * 用户优惠券使用通知 + * + * @param message 消息 + * @param content 消息原始内容 + * @param appId appId + * @param context 上下文 + * @param sessionManager session管理器 + */ + void userCouponUse(UserCouponExpireMessage message, final String content, final String appId, + final Map context, final WxSessionManager sessionManager); + + /** + * 用户优惠券返还通知 + * + * @param message 消息 + * @param content 消息原始内容 + * @param appId appId + * @param context 上下文 + * @param sessionManager session管理器 + */ + void userCouponUnuse(UserCouponExpireMessage message, final String content, final String appId, + final Map context, final WxSessionManager sessionManager); + + /** + * 发放团购优惠成功回调 + * + * @param message 消息 + * @param content 消息原始内容 + * @param appId appId + * @param context 上下文 + * @param sessionManager session管理器 + */ + void voucherSendSucc(VoucherMessage message, final String content, final String appId, + final Map context, final WxSessionManager sessionManager); + /** + * 结算账户变更回调 + * + * @param message 消息 + * @param content 消息原始内容 + * @param appId appId + * @param context 上下文 + * @param sessionManager session管理器 + */ + void accountNotify(AccountNotifyMessage message, final String content, final String appId, + final Map context, final WxSessionManager sessionManager); + + /** + * 提现回调 + * + * @param message 消息 + * @param content 消息原始内容 + * @param appId appId + * @param context 上下文 + * @param sessionManager session管理器 + */ + void withdrawNotify(WithdrawNotifyMessage message, final String content, final String appId, + final Map context, final WxSessionManager sessionManager); + + /** + * 提现二维码回调 + * + * @param message 消息 + * @param content 消息原始内容 + * @param appId appId + * @param context 上下文 + * @param sessionManager session管理器 + */ + void qrNotify(QrNotifyMessage message, final String content, final String appId, final Map context, + final WxSessionManager sessionManager); + + /** + * 团长商品变更 + * + * @param message 消息 + * @param content 消息原始内容 + * @param appId appId + * @param context 上下文 + * @param sessionManager session管理器 + */ + void supplierItemUpdate(SupplierItemMessage message, final String content, final String appId, + final Map context, final WxSessionManager sessionManager); + + + /** + * 用户加入会员. + * + * @param message the message + * @param content the content + * @param appId the app id + * @param context the context + * @param sessionManager the session manager + */ + public void vipJoin(UserInfoMessage message, final String content, final String appId, + final Map context, final WxSessionManager sessionManager); + + /** + * 用户注销会员. + * + * @param message the message + * @param content the content + * @param appId the app id + * @param context the context + * @param sessionManager the session manager + */ + void vipClose(UserInfoMessage message, final String content, final String appId, + final Map context, final WxSessionManager sessionManager); + + /** + * 用户等级更新. + * + * @param message the message + * @param content the content + * @param appId the app id + * @param context the context + * @param sessionManager the session manager + */ + void vipGradeUpdate(UserInfoMessage message, final String content, final String appId, + final Map context, final WxSessionManager sessionManager); + + /** + * 用户积分更新. + * + * @param message the message + * @param content the content + * @param appId the app id + * @param context the context + * @param sessionManager the session manager + */ + void vipScoreUpdate(UserInfoMessage message, final String content, final String appId, + final Map context, final WxSessionManager sessionManager); + + /** + * 用户积分兑换 + * + * @param message the message + * @param content the content + * @param appId the app id + * @param context the context + * @param sessionManager the session manager + */ + void vipScoreExchange(ExchangeInfoMessage message, final String content, final String appId, + final Map context, final WxSessionManager sessionManager); + + /** + * 小店注销 + * + * @param message 消息 + * @param content 消息原始内容 + * @param appId appId + * @param context 上下文 + * @param sessionManager session管理器 + */ + void closeStore(CloseStoreMessage message, final String content, final String appId, + final Map context, final WxSessionManager sessionManager); + + + /** + * 小店修改名称 + * + * @param message 消息 + * @param content 消息原始内容 + * @param appId appId + * @param context 上下文 + * @param sessionManager session管理器 + */ + void updateNickname(NicknameUpdateMessage message, final String content, final String appId, + final Map context, final WxSessionManager sessionManager); + + /** + * 默认消息处理 + * + * @param message 消息 + * @param content 消息原始内容 + * @param appId appId + * @param context 上下文 + * @param sessionManager session管理器 + * @return Object + */ + Object defaultMessageHandler(WxChannelMessage message, final String content, final String appId, + final Map context, final WxSessionManager sessionManager); + + + /** + * 分享员变更 + * + * @param message the message + * @param content the content + * @param appId the app id + * @param context the context + * @param sessionManager the session manager + */ + void sharerChange(WxChannelMessage message, final String content, final String appId, + final Map context, final WxSessionManager sessionManager); +} 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 new file mode 100644 index 0000000000..07278da7ef --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/BaseWxChannelService.java @@ -0,0 +1,135 @@ +package me.chanjar.weixin.channel.api; + +import me.chanjar.weixin.channel.config.WxChannelConfig; +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.common.service.WxService; +import me.chanjar.weixin.common.util.http.MediaUploadRequestExecutor; +import me.chanjar.weixin.common.util.http.RequestExecutor; +import me.chanjar.weixin.common.util.http.RequestHttp; + +/** + * The interface Wx Channel service + * + * @author Zeyes + */ +public interface BaseWxChannelService extends WxService { + + /** + *
+   * 验证消息的确来自微信服务器.
+   * 详情请见: 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 + */ + boolean checkSignature(String timestamp, String nonce, String signature); + + /** + * 获取access_token, 不强制刷新access_token. + * + * @return the access token + * + * @throws WxErrorException the wx error exception + * @see #getAccessToken(boolean) #getAccessToken(boolean) + */ + String getAccessToken() throws WxErrorException; + + /** + *
+   * 获取access_token,本方法线程安全.
+   * 且在多线程同时刷新时只刷新一次,避免超出2000次/日的调用次数上限
+   * 使用【稳定版接口】获取access_token时,限制【20次/日】,连续使用该模式时,请保证调用时间隔至少为30s,否则不会刷新
+   *
+   * 程序员在非必要情况下尽量不要主动调用此方法
+   *
+   * 详情请见: 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 + */ + String getAccessToken(boolean forceRefresh) throws WxErrorException; + + /** + *
+   * 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 + */ + T execute(RequestExecutor executor, String uri, E data) throws WxErrorException; + + /** + * 执行器 + * + * @param . + * @param . + * @param executor 执行器 + * @param uri 接口请求地址 + * @param data 参数或请求数据 + * @return T + * + * @throws WxErrorException the wx error exception + */ + T executeWithoutLog(RequestExecutor executor, String uri, E data) throws WxErrorException; + + /** + *
+   * 设置当微信系统响应系统繁忙时,要等待多少 retrySleepMillis(ms) * 2^(重试次数 - 1) 再发起重试.
+   * 默认:1000ms
+   * 
+ * + * @param retrySleepMillis 重试等待毫秒数 + */ + void setRetrySleepMillis(int retrySleepMillis); + + /** + *
+   * 设置当微信系统响应系统繁忙时,最大重试次数.
+   * 默认:5次
+   * 
+ * + * @param maxRetryTimes 最大重试次数 + */ + void setMaxRetryTimes(int maxRetryTimes); + + /** + * WxChannelConfig对象 + * + * @return WxMaConfig wx channel config + */ + WxChannelConfig getConfig(); + + /** + * 注入 {@link WxChannelConfig} 的实现. + * + * @param config config + */ + void setConfig(WxChannelConfig config); + + /** + * 初始化http请求对象. + */ + void initHttp(); + + /** + * 请求http请求相关信息. + * + * @return . request http + */ + RequestHttp getRequestHttp(); +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxAssistantService.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxAssistantService.java new file mode 100644 index 0000000000..7adaf30a3e --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxAssistantService.java @@ -0,0 +1,55 @@ +package me.chanjar.weixin.channel.api; + +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; +import me.chanjar.weixin.channel.bean.window.request.AddWindowProductRequest; +import me.chanjar.weixin.channel.bean.window.request.GetWindowProductListRequest; +import me.chanjar.weixin.channel.bean.window.request.WindowProductRequest; +import me.chanjar.weixin.channel.bean.window.response.GetWindowProductListResponse; +import me.chanjar.weixin.channel.bean.window.response.GetWindowProductResponse; +import me.chanjar.weixin.common.error.WxErrorException; + +/** + * 视频号助手 橱窗管理服务
+ * 关于橱窗商品ID的说明:
+ * 不支持带货中心来源的商品,其余商品的橱窗商品ID与商品来源处的平台内部商品ID相同,对应关系如下
+ *
+ * 商品来源	橱窗ID说明
+ * 视频号小店	视频号小店商品的 product_id 字段
+ * 交易组件	组件商品的 product_id 字段
+ * 
+ * + * @author imyzt + */ +public interface WxAssistantService { + + /** + * 上架商品到橱窗 + * @param req 商品信息 + * @return 操作结果 + */ + WxChannelBaseResponse addWindowProduct(AddWindowProductRequest req) throws WxErrorException; + + /** + * 获取橱窗商品详情 + * + * @param req 商品信息 + * @return 橱窗商品详情 + */ + GetWindowProductResponse getWindowProduct(WindowProductRequest req) throws WxErrorException; + + /** + * 获取已添加到橱窗的商品列表 + * 接口限制了 page_size × page_index ≤ 10000。命中限制时建议改用传last_buffer顺序翻页的请求方式 + * @param req 商品信息 + * @return 已添加到橱窗的商品列表 + */ + GetWindowProductListResponse getWindowProductList(GetWindowProductListRequest req) throws WxErrorException; + + /** + * 下架橱窗商品 + * @param req 商品信息 + * @return 操作结果 + */ + WxChannelBaseResponse offWindowProduct(WindowProductRequest req) throws WxErrorException; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxChannelAddressService.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxChannelAddressService.java new file mode 100644 index 0000000000..063dd53948 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxChannelAddressService.java @@ -0,0 +1,68 @@ +package me.chanjar.weixin.channel.api; + + +import me.chanjar.weixin.channel.bean.address.AddressDetail; +import me.chanjar.weixin.channel.bean.address.AddressIdResponse; +import me.chanjar.weixin.channel.bean.address.AddressInfoResponse; +import me.chanjar.weixin.channel.bean.address.AddressListResponse; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; +import me.chanjar.weixin.common.error.WxErrorException; + +/** + * 视频号小店 地址管理服务 + * + * @author Zeyes + */ +public interface WxChannelAddressService { + + /** + * 获取地址列表 + * + * @param offset 起始位置 + * @param limit 拉取个数 + * @return 列表 + * + * @throws WxErrorException 异常 + */ + AddressListResponse listAddress(Integer offset, Integer limit) throws WxErrorException; + + /** + * 获取地址详情 + * + * @param addressId 地址id + * @return 地址详情 + * + * @throws WxErrorException 异常 + */ + AddressInfoResponse getAddress(String addressId) throws WxErrorException; + + /** + * 添加地址 + * + * @param addressDetail 地址 + * @return AddressIdResponse + * + * @throws WxErrorException 异常 + */ + AddressIdResponse addAddress(AddressDetail addressDetail) throws WxErrorException; + + /** + * 更新地址 + * + * @param addressDetail 地址 + * @return BaseResponse + * + * @throws WxErrorException 异常 + */ + WxChannelBaseResponse updateAddress(AddressDetail addressDetail) throws WxErrorException; + + /** + * 删除地址 + * + * @param addressId 地址id + * @return BaseResponse + * + * @throws WxErrorException 异常 + */ + WxChannelBaseResponse deleteAddress(String addressId) throws WxErrorException; +} 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 new file mode 100644 index 0000000000..85c945d428 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxChannelAfterSaleService.java @@ -0,0 +1,186 @@ +package me.chanjar.weixin.channel.api; + + +import java.util.List; + +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; + +/** + * 视频号小店 售后服务接口 + * + * @author Zeyes + */ +public interface WxChannelAfterSaleService { + + /** + * 获取售后单列表 + * + * @param beginCreateTime 订单创建启始时间 unix时间戳 + * @param endCreateTime 订单创建结束时间,end_create_time减去begin_create_time不得大于24小时 + * @param nextKey 翻页参数,从第二页开始传,来源于上一页的返回值 + * @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; + + /** + * 获取售后单详情 + * + * @param afterSaleOrderId 售后单号 + * @return 售后单信息 + * + * @throws WxErrorException 异常 + */ + AfterSaleInfoResponse get(String afterSaleOrderId) throws WxErrorException; + + /** + * 同意售后 + * 文档地址 https://developers.weixin.qq.com/doc/channels/API/aftersale/acceptapply.html + * + * @param afterSaleOrderId 售后单号 + * @param addressId 同意退货时传入地址id + * @param acceptType 1. 同意退货退款,并通知用户退货; 2. 确认收到货并退款给用户。 如果不填则将根据当前的售后单状态自动选择相应操作。对于仅退款的情况,由于只存在一种同意的场景,无需填写此字段。 + * @return BaseResponse + * + * @throws WxErrorException 异常 + */ + WxChannelBaseResponse accept(String afterSaleOrderId, String addressId, Integer acceptType) throws WxErrorException; + + /** + * 拒绝售后 + * 文档地址 https://developers.weixin.qq.com/doc/channels/API/aftersale/rejectapply.html + * + * @param afterSaleOrderId 售后单号 + * @param rejectReason 拒绝原因 + * @param rejectReasonType 拒绝原因枚举值 + * @see #getRejectReason() + * @return BaseResponse + * + * @throws WxErrorException 异常 + */ + WxChannelBaseResponse reject(String afterSaleOrderId, String rejectReason, Integer rejectReasonType) throws WxErrorException; + + /** + * 上传退款凭证 + * + * @param afterSaleOrderId 售后单号 + * @param desc 退款凭证描述 + * @param certificates 退款凭证图片列表 + * @return BaseResponse + * + * @throws WxErrorException 异常 + */ + WxChannelBaseResponse uploadRefundEvidence(String afterSaleOrderId, String desc, List certificates) + throws WxErrorException; + + /** + * 商家补充纠纷单留言 + * + * @param complaintId 纠纷单号 + * @param content 留言内容,最多500字 + * @param mediaIds 图片media_id列表,所有留言总图片数量最多20张 + * @return BaseResponse + * + * @throws WxErrorException 异常 + */ + WxChannelBaseResponse addComplaintMaterial(String complaintId, String content, List mediaIds) + throws WxErrorException; + + /** + * 商家举证 + * + * @param complaintId 纠纷单号 + * @param content 举证内容,最多500字 + * @param mediaIds 图片media_id列表,所有留言总图片数量最多20张 + * @return BaseResponse + * + * @throws WxErrorException 异常 + */ + WxChannelBaseResponse addComplaintEvidence(String complaintId, String content, List mediaIds) + throws WxErrorException; + + /** + * 获取纠纷单 + * + * @param complaintId 纠纷单号 + * @return BaseResponse + * + * @throws WxErrorException 异常 + */ + ComplaintOrderResponse getComplaint(String complaintId) throws WxErrorException; + + + /** + * 获取全量售后原因 + * 文档地址:https://developers.weixin.qq.com/doc/channels/API/aftersale/getaftersalereason.html + * + * @return 售后原因 + * + * @throws WxErrorException 异常 + */ + AfterSaleReasonResponse getAllReason() throws WxErrorException; + + /** + * 获取拒绝售后原因 + * 文档地址:https://developers.weixin.qq.com/doc/channels/API/aftersale/getrejectreason.html + * + * @return 拒绝售后原因 + * + * @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/WxChannelBasicService.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxChannelBasicService.java new file mode 100644 index 0000000000..a687aaeb5c --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxChannelBasicService.java @@ -0,0 +1,73 @@ +package me.chanjar.weixin.channel.api; + +import java.io.File; +import me.chanjar.weixin.channel.bean.address.AddressCodeResponse; +import me.chanjar.weixin.channel.bean.image.ChannelImageInfo; +import me.chanjar.weixin.channel.bean.image.ChannelImageResponse; +import me.chanjar.weixin.channel.bean.image.QualificationFileResponse; +import me.chanjar.weixin.channel.bean.shop.ShopInfoResponse; +import me.chanjar.weixin.common.error.WxErrorException; + +/** + * 基础接口 + * + * @author Zeyes + */ +public interface WxChannelBasicService { + + /** + * 获取店铺基本信息 + * + * @return 店铺基本信息 + */ + ShopInfoResponse getShopInfo() throws WxErrorException; + + /** + * 上传图片 + * + * @param respType 0:media_id和pay_media_id;1:图片链接(商品信息相关图片请务必使用此参数得到链接) + * @param imgUrl 图片url + * @return 图片信息 + * + * @throws WxErrorException 异常 + */ + ChannelImageInfo uploadImg(int respType, String imgUrl) throws WxErrorException; + + /** + * 上传图片 + * + * @param respType 0:media_id和pay_media_id;1:图片链接(商品信息相关图片请务必使用此参数得到链接) + * @param file 图片文件 + * @param height 图片的高,单位:像素 + * @param width 图片的宽,单位:像素 + * @return 图片信息 + * + * @throws WxErrorException 异常 + */ + ChannelImageInfo uploadImg(int respType, File file, int height, int width) throws WxErrorException; + + /** + * 上传资质图片 + * + * @param file 资质图片 + * @return 结果 + * + * @throws WxErrorException 异常 + */ + QualificationFileResponse uploadQualificationFile(File file) throws WxErrorException; + + /** + * 根据media_id获取图片 + * + * @param mediaId media_id + */ + ChannelImageResponse getImg(String mediaId) throws WxErrorException; + + /** + * 获取地址编码(最多获取4级) + * + * @param code 地址行政编码,不填或者填0时,拉取全国的省级行政编码 + * @return AddressCodeResponse + */ + AddressCodeResponse getAddressCode(Integer code) throws WxErrorException; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxChannelBrandService.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxChannelBrandService.java new file mode 100644 index 0000000000..905d354955 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxChannelBrandService.java @@ -0,0 +1,103 @@ +package me.chanjar.weixin.channel.api; + + +import me.chanjar.weixin.channel.bean.audit.AuditApplyResponse; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; +import me.chanjar.weixin.channel.bean.brand.Brand; +import me.chanjar.weixin.channel.bean.brand.BrandApplyListResponse; +import me.chanjar.weixin.channel.bean.brand.BrandInfoResponse; +import me.chanjar.weixin.channel.bean.brand.BrandListResponse; +import me.chanjar.weixin.common.error.WxErrorException; + +/** + * 视频号小店 品牌服务接口 + * + * @author Zeyes + */ +public interface WxChannelBrandService { + + /** + * 获取品牌库列表 + * + * @param pageSize 每页数量(默认10, 不超过50) + * @param nextKey 由上次请求返回, 记录翻页的上下文, 传入时会从上次返回的结果往后翻一页, 不传默认拉取第一页数据 + * @return 品牌库列表 + * + * @throws WxErrorException 异常 + */ + BrandListResponse listAllBrand(Integer pageSize, String nextKey) throws WxErrorException; + + /** + * 新增品牌资质 + * + * @param brand 品牌参数 + * @return 审核id + * + * @throws WxErrorException 异常 + */ + AuditApplyResponse addBrandApply(Brand brand) throws WxErrorException; + + /** + * 修改品牌资质 + * + * @param brand 品牌参数 + * @return 审核id + * + * @throws WxErrorException 异常 + */ + AuditApplyResponse updateBrandApply(Brand brand) throws WxErrorException; + + /** + * 撤回品牌资质审核 + * + * @param brandId 品牌id + * @param auditId 审核id + * @return 审核id + * + * @throws WxErrorException 异常 + */ + WxChannelBaseResponse cancelBrandApply(String brandId, String auditId) throws WxErrorException; + + /** + * 删除品牌资质 + * + * @param brandId 品牌id + * @return 结果 + * + * @throws WxErrorException 异常 + */ + WxChannelBaseResponse deleteBrandApply(String brandId) throws WxErrorException; + + /** + * 获取品牌资质申请详情 + * + * @param brandId 品牌id + * @return 品牌信息 + * + * @throws WxErrorException 异常 + */ + BrandInfoResponse getBrandApply(String brandId) throws WxErrorException; + + /** + * 获取品牌资质申请列表 + * + * @param pageSize 每页数量(默认10, 不超过50) + * @param nextKey 由上次请求返回, 记录翻页的上下文, 传入时会从上次返回的结果往后翻一页, 不传默认拉取第一页数据 + * @param status 审核单状态, 不填默认拉全部商品 + * @return 品牌列表 + * + * @throws WxErrorException 异常 + */ + BrandApplyListResponse listBrandApply(Integer pageSize, String nextKey, Integer status) throws WxErrorException; + + /** + * 获取生效中的品牌资质列表 + * + * @param pageSize 每页数量(默认10, 不超过50) + * @param nextKey 由上次请求返回, 记录翻页的上下文, 传入时会从上次返回的结果往后翻一页, 不传默认拉取第一页数据 + * @return 品牌列表 + * + * @throws WxErrorException 异常 + */ + BrandApplyListResponse listValidBrandApply(Integer pageSize, String nextKey) throws WxErrorException; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxChannelCategoryService.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxChannelCategoryService.java new file mode 100644 index 0000000000..ad86697614 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxChannelCategoryService.java @@ -0,0 +1,132 @@ +package me.chanjar.weixin.channel.api; + +import java.io.File; +import java.util.List; +import me.chanjar.weixin.channel.bean.audit.AuditApplyResponse; +import me.chanjar.weixin.channel.bean.audit.AuditResponse; +import me.chanjar.weixin.channel.bean.audit.CategoryAuditInfo; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; +import me.chanjar.weixin.channel.bean.category.*; +import me.chanjar.weixin.common.error.WxErrorException; + +/** + * 视频号小店 商品类目相关接口 + * + * @author Zeyes + * @see 新旧类目树差异 + */ +public interface WxChannelCategoryService { + + /** + * 获取所有的类目 + * + * @return 所有类目以及资质信息 + * + * @throws WxErrorException 异常 + */ + CategoryQualificationResponse listAllCategory() throws WxErrorException; + + /** + * 获取商品类目列表(全量) 有频率限制 + * + * @param fCatId 类目父id + * @return 类目列表 + * + * @throws WxErrorException 异常 + * @deprecated 接口返回更新,请使用 {@link #listAvailableCategories(String)} + */ + @Deprecated + List listAvailableCategory(String fCatId) throws WxErrorException; + + /** + * 获取可用的子类目详情 + * + * 1.f_cat_id 为旧类目树中的非叶子类目,仅设置 cat_list 字段。 + * 2.f_cat_id 为新类目树中的非叶子类目,仅设置 cat_list_v2 字段。 + * 3.f_cat_id 为0,同时设置 cat_list 和 cat_list_v2 字段 + * + * @param fCatId 父类目ID,可先填0获取根部类目 + * @return 类目列表 + * @throws WxErrorException 异常 + */ + ShopCategoryResponse listAvailableCategories(String fCatId) throws WxErrorException; + + /** + * 获取类目信息 + * + * @param id 三级类目id + * @return 类目信息 + * + * @throws WxErrorException 异常 + */ + CategoryDetailResult getCategoryDetail(String id) throws WxErrorException; + + /** + * 上传类目资质 + * + * @param level1 一级类目ID + * @param level2 二级类目ID + * @param level3 三级类目ID + * @param certificate 资质材料,图片mediaid,图片类型,最多不超过10张 + * @return 审核id + * + * @throws WxErrorException 异常 + * @see WxChannelBasicService#uploadQualificationFile(File) + * @deprecated 请使用 {@link #addCategory(CategoryAuditInfo)} + */ + @Deprecated + AuditApplyResponse addCategory(String level1, String level2, String level3, List certificate) + throws WxErrorException; + + /** + * 上传类目资质 + * + * @param info 类目资质信息 + * @return 审核id + * + * @throws WxErrorException 异常 + * @see WxChannelBasicService#uploadQualificationFile(File) + */ + AuditApplyResponse addCategory(CategoryAuditInfo info) throws WxErrorException; + + /** + * 取消类目提审 + * + * @param auditId 提交审核时返回的id + * @return BaseResponse + * + * @throws WxErrorException 异常 + */ + WxChannelBaseResponse cancelCategoryAudit(String auditId) throws WxErrorException; + + /** + * 查询类目审核结果 + * + * @param auditId 审核id + * @return 审核结果 + * + * @throws WxErrorException 异常 + */ + AuditResponse getAudit(String auditId) throws WxErrorException; + + /** + * 获取账号申请通过的类目和资质信息 + * + * @return 类目和资质信息 + * + * @throws WxErrorException 异常 + */ + PassCategoryResponse listPassCategory() throws WxErrorException; + + /** + * 获取店铺的类目权限列表 + * + * @param isFilterStatus 是否按状态筛选 + * @param status 类目状态(当 isFilterStatus 为 true 时有效) + * @return 类目权限列表 + * + * @throws WxErrorException 异常 + */ + RelationCategoryResponse listRelationCategory(Boolean isFilterStatus, Integer status) throws WxErrorException; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxChannelCompassFinderService.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxChannelCompassFinderService.java new file mode 100644 index 0000000000..db123f61a4 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxChannelCompassFinderService.java @@ -0,0 +1,58 @@ +package me.chanjar.weixin.channel.api; + +import me.chanjar.weixin.channel.bean.compass.finder.OverallResponse; +import me.chanjar.weixin.channel.bean.compass.finder.ProductDataResponse; +import me.chanjar.weixin.channel.bean.compass.finder.ProductListResponse; +import me.chanjar.weixin.channel.bean.compass.finder.SaleProfileDataResponse; +import me.chanjar.weixin.common.error.WxErrorException; + +/** + * 视频号助手 罗盘达人版服务 + * + * @author Winnie + */ +public interface WxChannelCompassFinderService { + + /** + * 获取电商概览数据 + * + * @param ds 日期,格式 yyyyMMdd + * @return 电商概览数据 + * + * @throws WxErrorException 异常 + */ + OverallResponse getOverall(String ds) throws WxErrorException; + + /** + * 获取带货商品数据 + * + * @param ds 日期,格式 yyyyMMdd + * @param productId 商品id + * @return 带货商品数据 + * + * @throws WxErrorException 异常 + */ + ProductDataResponse getProductData(String ds, String productId) throws WxErrorException; + + /** + * 获取带货商品列表 + * + * @param ds 日期,格式 yyyyMMdd + * @return 带货商品列表 + * + * @throws WxErrorException 异常 + */ + ProductListResponse getProductList(String ds) throws WxErrorException; + + /** + * 获取带货人群数据 + * + * @param ds 日期,格式 yyyyMMdd + * @param type 用户类型,1=商品曝光用户, 2=商品点击用户, 3=购买用户, 4=首购用户, 5=复购用户, 6=直播观看用户 + * @return 带货人群数据 + * + * @throws WxErrorException 异常 + */ + SaleProfileDataResponse getSaleProfileData(String ds, Integer type) throws WxErrorException; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxChannelCompassShopService.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxChannelCompassShopService.java new file mode 100644 index 0000000000..aa3a85fa74 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxChannelCompassShopService.java @@ -0,0 +1,126 @@ +package me.chanjar.weixin.channel.api; + +import me.chanjar.weixin.channel.bean.compass.shop.FinderAuthListResponse; +import me.chanjar.weixin.channel.bean.compass.shop.FinderListResponse; +import me.chanjar.weixin.channel.bean.compass.shop.FinderOverallResponse; +import me.chanjar.weixin.channel.bean.compass.shop.FinderProductListResponse; +import me.chanjar.weixin.channel.bean.compass.shop.FinderProductOverallResponse; +import me.chanjar.weixin.channel.bean.compass.shop.ShopLiveListResponse; +import me.chanjar.weixin.channel.bean.compass.shop.ShopOverallResponse; +import me.chanjar.weixin.channel.bean.compass.shop.ShopProductDataResponse; +import me.chanjar.weixin.channel.bean.compass.shop.ShopProductListResponse; +import me.chanjar.weixin.channel.bean.compass.shop.ShopSaleProfileDataResponse; +import me.chanjar.weixin.common.error.WxErrorException; + +/** + * 视频号/微信小店 罗盘商家版服务 + * + * @author Zeyes + */ +public interface WxChannelCompassShopService { + + /** + * 获取电商概览数据 + * + * @param ds 日期,格式 yyyyMMdd + * @return 电商概览数据 + * + * @throws WxErrorException 异常 + */ + ShopOverallResponse getShopOverall(String ds) throws WxErrorException; + + /** + * 获取授权视频号列表 + * + * @return 获取授权视频号列表 + * + * @throws WxErrorException 异常 + */ + FinderAuthListResponse getFinderAuthorizationList() throws WxErrorException; + + /** + * 获取带货达人列表 + * + * @param ds 日期,格式 yyyyMMdd + * @return 带货达人列表 + * + * @throws WxErrorException 异常 + */ + FinderListResponse getFinderList(String ds) throws WxErrorException; + + /** + * 获取带货数据概览 + * + * @param ds 日期,格式 yyyyMMdd + * @return 带货数据概览 + * + * @throws WxErrorException 异常 + */ + FinderOverallResponse getFinderOverall(String ds) throws WxErrorException; + + /** + * 获取带货达人商品列表 + * + * @param ds 日期,格式YYYYMMDD + * @param finderId 视频号ID + * @return 带货达人商品列表 + * + * @throws WxErrorException 异常 + */ + FinderProductListResponse getFinderProductList(String ds, String finderId) throws WxErrorException; + + /** + * 获取带货达人详情 + * + * @param ds 日期,格式YYYYMMDD + * @param finderId 视频号ID + * @return 带货达人详情 + * + * @throws WxErrorException 异常 + */ + FinderProductOverallResponse getFinderProductOverall(String ds, String finderId) throws WxErrorException; + + /** + * 获取店铺开播列表 + * + * @param ds 日期,格式YYYYMMDD + * @param finderId 视频号ID + * @return 店铺开播列表 + * + * @throws WxErrorException 异常 + */ + ShopLiveListResponse getShopLiveList(String ds, String finderId) throws WxErrorException; + + /** + * 获取商品详细信息 + * + * @param ds 日期,格式YYYYMMDD + * @param productId 商品id + * @return 商品详细信息 + * + * @throws WxErrorException 异常 + */ + ShopProductDataResponse getShopProductData(String ds, String productId) throws WxErrorException; + + /** + * 获取商品列表 + * + * @param ds 日期,格式YYYYMMDD + * @return 商品列表 + * + * @throws WxErrorException 异常 + */ + ShopProductListResponse getShopProductList(String ds) throws WxErrorException; + + /** + * 获取店铺人群数据 + * + * @param ds 日期,格式 yyyyMMdd + * @param type 用户类型,1商品曝光用户 2商品点击用户 3购买用户 4首购用户 5复购用户 + * @return 店铺人群数据 + * + * @throws WxErrorException 异常 + */ + ShopSaleProfileDataResponse getShopSaleProfileData(String ds, Integer type) throws WxErrorException; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxChannelCouponService.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxChannelCouponService.java new file mode 100644 index 0000000000..df59fdc8b9 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxChannelCouponService.java @@ -0,0 +1,92 @@ +package me.chanjar.weixin.channel.api; + +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; +import me.chanjar.weixin.channel.bean.coupon.CouponIdResponse; +import me.chanjar.weixin.channel.bean.coupon.CouponInfoResponse; +import me.chanjar.weixin.channel.bean.coupon.CouponListParam; +import me.chanjar.weixin.channel.bean.coupon.CouponListResponse; +import me.chanjar.weixin.channel.bean.coupon.CouponParam; +import me.chanjar.weixin.channel.bean.coupon.UserCouponListParam; +import me.chanjar.weixin.channel.bean.coupon.UserCouponListResponse; +import me.chanjar.weixin.channel.bean.coupon.UserCouponResponse; +import me.chanjar.weixin.common.error.WxErrorException; + +/** + * 视频号小店 优惠券服务 + * + * @author Zeyes + */ +public interface WxChannelCouponService { + + /** + * 创建优惠券 + * + * @param coupon 优惠券 + * @return 优惠券ID + * + * @throws WxErrorException 异常 + */ + CouponIdResponse createCoupon(CouponParam coupon) throws WxErrorException; + + /** + * 更新优惠券 + * + * @param coupon 优惠券 + * @return 优惠券ID + * + * @throws WxErrorException 异常 + */ + CouponIdResponse updateCoupon(CouponParam coupon) throws WxErrorException; + + /** + * 更新优惠券状态 + * + * @param couponId 优惠券ID + * @param status 状态 2生效 4已作废 5删除 {@link me.chanjar.weixin.channel.enums.WxCouponStatus} + * @return BaseResponse + * + * @throws WxErrorException 异常 + */ + WxChannelBaseResponse updateCouponStatus(String couponId, Integer status) throws WxErrorException; + + /** + * 获取优惠券详情 + * + * @param couponId 优惠券ID + * @return CouponInfoResponse + * + * @throws WxErrorException 异常 + */ + CouponInfoResponse getCoupon(String couponId) throws WxErrorException; + + /** + * 获取优惠券ID列表 + * + * @param param 条件参数 + * @return 优惠券ID列表 + * + * @throws WxErrorException 异常 + */ + CouponListResponse getCouponList(CouponListParam param) throws WxErrorException; + + /** + * 获取用户优惠券 + * + * @param openId 用户openid + * @param userCouponId 用户优惠券ID + * @return UserCouponResponse + * + * @throws WxErrorException 异常 + */ + UserCouponResponse getUserCoupon(String openId, String userCouponId) throws WxErrorException; + + /** + * 获取用户优惠券ID列表 + * + * @param param 条件参数 + * @return UserCouponListResponse + * + * @throws WxErrorException 异常 + */ + UserCouponListResponse getUserCouponList(UserCouponListParam param) throws WxErrorException; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxChannelFreightTemplateService.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxChannelFreightTemplateService.java new file mode 100644 index 0000000000..188b33464b --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxChannelFreightTemplateService.java @@ -0,0 +1,57 @@ +package me.chanjar.weixin.channel.api; + + +import me.chanjar.weixin.channel.bean.freight.FreightTemplate; +import me.chanjar.weixin.channel.bean.freight.TemplateIdResponse; +import me.chanjar.weixin.channel.bean.freight.TemplateInfoResponse; +import me.chanjar.weixin.channel.bean.freight.TemplateListResponse; +import me.chanjar.weixin.common.error.WxErrorException; + +/** + * 视频号小店 运费模板服务接口 + * + * @author Zeyes + */ +public interface WxChannelFreightTemplateService { + + /** + * 获取运费模板列表 + * + * @param offset 起始位置 + * @param limit 拉取个数 + * @return 列表 + * + * @throws WxErrorException 异常 + */ + TemplateListResponse listTemplate(Integer offset, Integer limit) throws WxErrorException; + + /** + * 获取运费模板 + * + * @param templateId 模板id + * @return 运费模板 + * + * @throws WxErrorException 异常 + */ + TemplateInfoResponse getTemplate(String templateId) throws WxErrorException; + + /** + * 添加运费模板 + * + * @param template 运费模板 + * @return TemplateIdResponse + * + * @throws WxErrorException 异常 + */ + TemplateIdResponse addTemplate(FreightTemplate template) throws WxErrorException; + + /** + * 更新运费模板 + * + * @param template 运费模板 + * @return TemplateIdResponse + * + * @throws WxErrorException 异常 + */ + TemplateIdResponse updateTemplate(FreightTemplate template) throws WxErrorException; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxChannelFundService.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxChannelFundService.java new file mode 100644 index 0000000000..cb0f5aab79 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxChannelFundService.java @@ -0,0 +1,189 @@ +package me.chanjar.weixin.channel.api; + + +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; +import me.chanjar.weixin.channel.bean.fund.AccountInfo; +import me.chanjar.weixin.channel.bean.fund.AccountInfoResponse; +import me.chanjar.weixin.channel.bean.fund.BalanceInfoResponse; +import me.chanjar.weixin.channel.bean.fund.FlowListResponse; +import me.chanjar.weixin.channel.bean.fund.FundsFlowResponse; +import me.chanjar.weixin.channel.bean.fund.FundsListParam; +import me.chanjar.weixin.channel.bean.fund.WithdrawDetailResponse; +import me.chanjar.weixin.channel.bean.fund.WithdrawListResponse; +import me.chanjar.weixin.channel.bean.fund.WithdrawSubmitResponse; +import me.chanjar.weixin.channel.bean.fund.bank.BankCityResponse; +import me.chanjar.weixin.channel.bean.fund.bank.BankInfoResponse; +import me.chanjar.weixin.channel.bean.fund.bank.BankListResponse; +import me.chanjar.weixin.channel.bean.fund.bank.BankProvinceResponse; +import me.chanjar.weixin.channel.bean.fund.bank.BranchInfoResponse; +import me.chanjar.weixin.channel.bean.fund.qrcode.QrCheckResponse; +import me.chanjar.weixin.channel.bean.fund.qrcode.QrCodeResponse; +import me.chanjar.weixin.common.error.WxErrorException; + +/** + * 资金相关服务 + * + * @author Zeyes + */ +public interface WxChannelFundService { + + /** + * 获取账户余额 + * + * @return 账户余额 + * + * @throws WxErrorException 异常 + */ + BalanceInfoResponse getBalance() throws WxErrorException; + + /** + * 获取结算账户 + * + * @return 结算账户 + * + * @throws WxErrorException 异常 + */ + AccountInfoResponse getBankAccount() throws WxErrorException; + + /** + * 获取资金流水详情 + * + * @param flowId 资金流水号 + * @return 资金流水详情 + * + * @throws WxErrorException 异常 + */ + FundsFlowResponse getFundsFlowDetail(String flowId) throws WxErrorException; + + /** + * 获取资金流水列表 + * + * @param param 资金流水列表参数 + * @return 资金流水列表 + * + * @throws WxErrorException 异常 + */ + FlowListResponse listFundsFlow(FundsListParam param) throws WxErrorException; + + /** + * 获取提现记录 + * + * @param withdrawId 提现单号 + * @return 提现记录 + * + * @throws WxErrorException 异常 + */ + WithdrawDetailResponse getWithdrawDetail(String withdrawId) throws WxErrorException; + + /** + * 获取提现记录列表 + * + * @param pageNum 页码 + * @param pageSize 每页大小 + * @param startTime 开始时间 + * @param endTime 结束时间 + * @return 提现记录列表 + * + * @throws WxErrorException 异常 + */ + WithdrawListResponse listWithdraw(Integer pageNum, Integer pageSize, Long startTime, Long endTime) + throws WxErrorException; + + /** + * 修改结算账户 + * + * @param accountInfo 结算账户信息 + * @return 修改结果 + * + * @throws WxErrorException 异常 + */ + WxChannelBaseResponse setBankAccount(AccountInfo accountInfo) throws WxErrorException; + + /*** + * 商户提现 + * + * @param amount 提现金额(单位:分) + * @param remark 提现备注 + * @param bankMemo 银行附言 + * @return 提现结果 + * @throws WxErrorException 异常 + */ + WithdrawSubmitResponse submitWithdraw(Integer amount, String remark, String bankMemo) throws WxErrorException; + + /** + * 根据卡号查银行信息 + * + * @param accountNumber 卡号 + * @return 银行信息 + * + * @throws WxErrorException 异常 + */ + BankInfoResponse getBankInfoByCardNo(String accountNumber) throws WxErrorException; + + /** + * 搜索银行列表 + * + * @param offset 偏移量 + * @param limit 每页数据大小 + * @param keywords 银行关键字 + * @param bankType 银行类型(1:对私银行,2:对公银行; 默认对公) + * @return 银行列表 + * + * @throws WxErrorException 异常 + */ + BankListResponse searchBankList(Integer offset, Integer limit, String keywords, Integer bankType) + throws WxErrorException; + + /** + * 查询城市列表 + * + * @param provinceCode 省份编码 + * @return 城市列表 + * + * @throws WxErrorException 异常 + */ + BankCityResponse searchCityList(String provinceCode) throws WxErrorException; + + /** + * 查询大陆银行省份列表 + * + * @return 省份列表 + * + * @throws WxErrorException 异常 + */ + BankProvinceResponse getProvinceList() throws WxErrorException; + + /** + * 查询支行列表 + * + * @param bankCode 银行编码 + * @param cityCode 城市编码 + * @param offset 偏移量 + * @param limit 每页数据大小 + * @return 支行列表 + * + * @throws WxErrorException 异常 + */ + BranchInfoResponse searchBranchList(String bankCode, String cityCode, Integer offset, Integer limit) + throws WxErrorException; + + /** + * 获取二维码 + * + * @param qrcodeTicket 二维码ticket + * @return 二维码响应 + * + * @throws WxErrorException 异常 + */ + QrCodeResponse getQrCode(String qrcodeTicket) throws WxErrorException; + + /** + * 查询扫码状态 + * + * @param qrcodeTicket 二维码ticket + * @return 扫码状态 + * + * @throws WxErrorException 异常 + */ + QrCheckResponse checkQrStatus(String qrcodeTicket) throws WxErrorException; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxChannelLiveDashboardService.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxChannelLiveDashboardService.java new file mode 100644 index 0000000000..be93b06a97 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxChannelLiveDashboardService.java @@ -0,0 +1,34 @@ +package me.chanjar.weixin.channel.api; + +import me.chanjar.weixin.channel.bean.live.dashboard.LiveDataResponse; +import me.chanjar.weixin.channel.bean.live.dashboard.LiveListResponse; +import me.chanjar.weixin.common.error.WxErrorException; + +/** + * 视频号助手 直播大屏数据服务 + * + * @author Winnie + */ +public interface WxChannelLiveDashboardService { + + /** + * 获取直播大屏直播列表 + * + * @param ds 日期,格式 yyyyMMdd + * @return 播大屏直播列表 + * + * @throws WxErrorException 异常 + */ + LiveListResponse getLiveList(Long ds) throws WxErrorException; + + /** + * 获取直播大屏数据 + * + * @param exportId 直播唯一ID + * @return 播大屏数据 + * + * @throws WxErrorException 异常 + */ + LiveDataResponse getLiveData(String exportId) 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 new file mode 100644 index 0000000000..7be0382bac --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxChannelOrderService.java @@ -0,0 +1,203 @@ +package me.chanjar.weixin.channel.api; + +import java.util.List; +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.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.OrderInfoResponse; +import me.chanjar.weixin.channel.bean.order.OrderListParam; +import me.chanjar.weixin.channel.bean.order.OrderListResponse; +import me.chanjar.weixin.channel.bean.order.OrderSearchParam; +import me.chanjar.weixin.channel.bean.order.VirtualTelNumberResponse; +import me.chanjar.weixin.common.error.WxErrorException; + +/** + * 视频号小店 订单服务接口 + * + * @author Zeyes + * @link 订单接口文档 + */ +public interface WxChannelOrderService { + + /** + * 获取订单 + * + * @param orderId 订单id + * @return 订单详情 + * + * @throws WxErrorException 异常 + */ + OrderInfoResponse getOrder(String orderId) throws WxErrorException; + + /** + * 获取订单详情 + * + * @param orderId 订单id + * @param encodeSensitiveInfo 是否编码敏感信息 + * @return 订单详情 + * + * @throws WxErrorException 异常 + */ + OrderInfoResponse getOrder(String orderId, Boolean encodeSensitiveInfo) throws WxErrorException; + + /** + * 获取订单列表 + * + * @param param 搜索条件 + * @return 订单列表 + * + * @throws WxErrorException 异常 + */ + OrderListResponse getOrders(OrderListParam param) throws WxErrorException; + + /** + * 订单搜索 + * + * @param param 搜索条件 + * @return 订单列表 + * + * @throws WxErrorException 异常 + */ + OrderListResponse searchOrder(OrderSearchParam param) throws WxErrorException; + + /** + * 更改订单价格 + * + * @param orderId 订单id + * @param expressFee 运费价格(以分为单位)(不填不改) + * @param changeOrderInfos 改价列表 + * @return 结果 + * + * @throws WxErrorException 异常 + */ + WxChannelBaseResponse updatePrice(String orderId, Integer expressFee, List changeOrderInfos) + throws WxErrorException; + + /** + * 更改订单备注 + * + * @param orderId 订单id + * @param merchantNotes 备注 + * @return BaseResponse + * + * @throws WxErrorException 异常 + */ + WxChannelBaseResponse updateRemark(String orderId, String merchantNotes) throws WxErrorException; + + /** + * 更新订单地址 + * + * @param orderId 订单id + * @param userAddress 用户地址 + * @return BaseResponse + * + * @throws WxErrorException 异常 + */ + WxChannelBaseResponse updateAddress(String orderId, AddressInfo userAddress) throws WxErrorException; + + /** + * 修改物流信息
发货完成的订单可以修改,最多修改1次 拆包发货的订单暂不允许修改物流 虚拟商品订单暂不允许修改物流 + * + * @param param 物流信息 + * @return BaseResponse + * + * @throws WxErrorException 异常 + */ + WxChannelBaseResponse updateDelivery(DeliveryUpdateParam param) throws WxErrorException; + + /** + * 同意用户修改收货地址请求 + * + * @param orderId 订单id + * @return BaseResponse + * + * @throws WxErrorException 异常 + */ + WxChannelBaseResponse acceptAddressModify(String orderId) throws WxErrorException; + + /** + * 拒接用户修改收货地址请求 + * + * @param orderId 订单id + * @return BaseResponse + * + * @throws WxErrorException 异常 + */ + WxChannelBaseResponse rejectAddressModify(String orderId) throws WxErrorException; + + /** + * 关闭订单 (需要订单状态为未付款状态) + * + * @param orderId 订单id + * @return BaseResponse + */ + WxChannelBaseResponse closeOrder(String orderId); + + /** + * 获取快递公司列表-旧 + * + * @return 快递公司列表 + * + * @throws WxErrorException 异常 + */ + DeliveryCompanyResponse listDeliveryCompany() throws WxErrorException; + + /** + * 获取快递公司列表 + * + * @param ewaybillOnly 是否仅返回支持电子面单功能的快递公司 + * @return 快递公司列表 + * + * @throws WxErrorException 异常 + */ + DeliveryCompanyResponse listDeliveryCompany(Boolean ewaybillOnly) throws WxErrorException; + + /** + * 订单发货 + * + * @param orderId 订单id + * @param deliveryList 物流信息 + * @return BaseResponse + * + * @throws WxErrorException 异常 + */ + WxChannelBaseResponse deliveryOrder(String orderId, List deliveryList) throws WxErrorException; + + /** + * 上传生鲜质检信息
+ * + * 注意事项:
+ * 1. 非生鲜质检的订单不能进行上传
+ * 2. 图片url必须用图片上传接口获取 {@link WxChannelBasicService#uploadImg(int, String)}
+ * + * @param orderId 订单id + * @param items 商品打包信息 + * @return BaseResponse + * + * @throws WxErrorException 异常 + */ + WxChannelBaseResponse uploadFreshInspect(String orderId, List items) throws WxErrorException; + + /** + * 兑换虚拟号 + * + * @param orderId 订单id + * @return 虚拟号信息 + * @throws WxErrorException 异常 + */ + VirtualTelNumberResponse getVirtualTelNumber(String orderId) throws WxErrorException; + + /** + * 解码订单包含的敏感数据 + * + * @param orderId 订单id + * @return 解码结果 + * @throws WxErrorException 异常 + */ + DecodeSensitiveInfoResponse decodeSensitiveInfo(String orderId) throws WxErrorException; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxChannelProductService.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxChannelProductService.java new file mode 100644 index 0000000000..7064adf70f --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxChannelProductService.java @@ -0,0 +1,250 @@ +package me.chanjar.weixin.channel.api; + + +import java.util.List; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; +import me.chanjar.weixin.channel.bean.limit.LimitTaskAddResponse; +import me.chanjar.weixin.channel.bean.limit.LimitTaskListResponse; +import me.chanjar.weixin.channel.bean.limit.LimitTaskParam; +import me.chanjar.weixin.channel.bean.product.SkuStockBatchResponse; +import me.chanjar.weixin.channel.bean.product.SkuStockResponse; +import me.chanjar.weixin.channel.bean.product.SpuFastInfo; +import me.chanjar.weixin.channel.bean.product.SpuGetResponse; +import me.chanjar.weixin.channel.bean.product.SpuInfo; +import me.chanjar.weixin.channel.bean.product.SpuListResponse; +import me.chanjar.weixin.channel.bean.product.SpuUpdateInfo; +import me.chanjar.weixin.channel.bean.product.SpuUpdateResponse; +import me.chanjar.weixin.channel.bean.product.link.ProductH5UrlResponse; +import me.chanjar.weixin.channel.bean.product.link.ProductQrCodeResponse; +import me.chanjar.weixin.channel.bean.product.link.ProductTagLinkResponse; +import me.chanjar.weixin.common.error.WxErrorException; + +/** + * 视频号小店 商品服务接口 + * + * @author Zeyes + * @see 商品状态流转图 + */ +public interface WxChannelProductService { + + /** + * 添加商品 + * + * @param info 商品信息 + * @return 返回商品的状态和id + * + * @throws WxErrorException 异常 + */ + SpuUpdateResponse addProduct(SpuUpdateInfo info) throws WxErrorException; + + /** + * 更新商品 + * + * @param info 商品信息 + * @return 返回商品的状态和id + * + * @throws WxErrorException 异常 + */ + SpuUpdateResponse updateProduct(SpuUpdateInfo info) throws WxErrorException; + + /** + * 添加商品 + * + * @param info 商品信息 + * @return 返回商品的状态和id + * + * @throws WxErrorException 异常 + * @deprecated 请使用 {@link #addProduct(SpuUpdateInfo)} + */ + @Deprecated + SpuUpdateResponse addProduct(SpuInfo info) throws WxErrorException; + + /** + * 更新商品 + * + * @param info 商品信息 + * @return 返回商品的状态和id + * + * @throws WxErrorException 异常 + * @deprecated 请使用 {@link #updateProduct(SpuUpdateInfo)} + */ + @Deprecated + SpuUpdateResponse updateProduct(SpuInfo info) throws WxErrorException; + + /** + * 免审更新商品 + * + * @param info 商品信息 + * @return 返回商品的状态和id + * + * @throws WxErrorException 异常 + */ + WxChannelBaseResponse updateProductAuditFree(SpuFastInfo info) throws WxErrorException; + + /** + * 更新商品库存 (仅对edit_status != 2 的商品适用,其他状态的商品无法通过该接口修改库存) + * + * @param productId 内部商品ID + * @param skuId 内部sku_id + * @param diffType 修改类型 1增加 2减少 3设置 + * 建议使用1或2,不建议使用3,因为使用3在高并发场景可能会出现预期外表现 + * @param num 增加、减少或者设置的库存值 + * @return WxChannelBaseResponse + * + * @throws WxErrorException 异常 + */ + WxChannelBaseResponse updateStock(String productId, String skuId, Integer diffType, Integer num) + throws WxErrorException; + + /** + * 删除商品 + * + * @param productId 商品ID + * @return 是否成功 + * + * @throws WxErrorException 异常 + */ + WxChannelBaseResponse deleteProduct(String productId) throws WxErrorException; + + /** + * 撤回商品审核 + * + * @param productId 商品ID + * @return 是否成功 + * + * @throws WxErrorException 异常 + */ + WxChannelBaseResponse cancelProductAudit(String productId) throws WxErrorException; + + /** + * 获取商品 + * + * @param productId 商品ID + * @param dataType 默认取1 1:获取线上数据 2:获取草稿数据 3:同时获取线上和草稿数据(注意:需成功上架后才有线上数据) + * @return 商品信息 + * + * @throws WxErrorException 异常 + */ + SpuGetResponse getProduct(String productId, Integer dataType) throws WxErrorException; + + /** + * 获取商品列表 + * + * @param pageSize 每页数量(默认10,不超过30) + * @param nextKey 由上次请求返回,记录翻页的上下文。传入时会从上次返回的结果往后翻一页,不传默认拉取第一页数据。 + * @param status 商品状态,不填默认拉全部商品(不包含回收站) {@link me.chanjar.weixin.channel.enums.SpuStatus} + * @return List + * + * @throws WxErrorException 异常 + */ + SpuListResponse listProduct(Integer pageSize, String nextKey, Integer status) throws WxErrorException; + + /** + * 上架商品 + * + * @param productId 商品ID + * @return 是否成功 + * + * @throws WxErrorException 异常 + */ + WxChannelBaseResponse upProduct(String productId) throws WxErrorException; + + /** + * 下架商品 + * + * @param productId 商品ID + * @return 是否成功 + * + * @throws WxErrorException 异常 + */ + WxChannelBaseResponse downProduct(String productId) throws WxErrorException; + + /** + * 获取商品实时库存 + * + * @param productId 商品ID + * @param skuId skuId + * @return SkuStockResponse + * + * @throws WxErrorException 异常 + */ + SkuStockResponse getSkuStock(String productId, String skuId) throws WxErrorException; + + /** + * 批量获取库存信息 (单次请求不能超过50个商品ID) + * + * @param productIds 商品ID列表 + * @return 库存信息 + * @throws WxErrorException 异常 + */ + SkuStockBatchResponse getSkuStockBatch(List productIds) throws WxErrorException; + + /** + * 获取商品H5链接 + * + * @param productId 商品ID + * @return 商品H5链接 + * @throws WxErrorException 异常 + */ + ProductH5UrlResponse getProductH5Url(String productId) throws WxErrorException; + + /** + * 获取商品二维码 + * + * @param productId 商品ID + * @return 商品二维码 + * @throws WxErrorException 异常 + */ + ProductQrCodeResponse getProductQrCode(String productId) throws WxErrorException; + + /** + * 获取商品口令 + * + * @param productId 商品ID + * @return 商品口令 + * @throws WxErrorException 异常 + */ + ProductTagLinkResponse getProductTagLink(String productId) throws WxErrorException; + + /** + * 添加限时抢购任务 + * + * @param param 限时抢购任务 + * @return LimitTaskAddResponse + * + * @throws WxErrorException 异常 + */ + LimitTaskAddResponse addLimitTask(LimitTaskParam param) throws WxErrorException; + + /** + * 拉取限时抢购任务列表 + * + * @param pageSize 每页数量(默认10,不超过50) + * @param nextKey 由上次请求返回,记录翻页的上下文。传入时会从上次返回的结果往后翻一页,不传默认拉取第一页数据 + * @param status 抢购活动状态 + * @return LimitTaskListResponse + * + * @throws WxErrorException 异常 + */ + LimitTaskListResponse listLimitTask(Integer pageSize, String nextKey, Integer status) throws WxErrorException; + + /** + * 停止限时抢购任务 + * + * @param taskId 限时抢购任务ID + * @return BaseResponse + * + * @throws WxErrorException 异常 + */ + WxChannelBaseResponse stopLimitTask(String taskId) throws WxErrorException; + + /** + * 停止限时抢购任务 + * + * @param taskId 限时抢购任务ID + * @return BaseResponse + * + * @throws WxErrorException 异常 + */ + WxChannelBaseResponse deleteLimitTask(String taskId) throws WxErrorException; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxChannelService.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxChannelService.java new file mode 100644 index 0000000000..50a029c196 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxChannelService.java @@ -0,0 +1,185 @@ +package me.chanjar.weixin.channel.api; + +/** + * The interface Wx Channel service + * + * @author Zeyes + */ +public interface WxChannelService extends BaseWxChannelService { + + /** + * 基础接口服务 + * + * @return 基础接口服务 + */ + WxChannelBasicService getBasicService(); + + /** + * 商品类目服务 + * + * @return 商品类目服务 + */ + WxChannelCategoryService getCategoryService(); + + /** + * 品牌服务 + * + * @return 品牌服务 + */ + WxChannelBrandService getBrandService(); + + /** + * 商品服务 + * + * @return 商品服务 + */ + WxChannelProductService getProductService(); + + /** + * 仓库服务 + * + * @return 仓库服务 + */ + WxChannelWarehouseService getWarehouseService(); + + /** + * 订单服务 + * + * @return 订单服务 + */ + WxChannelOrderService getOrderService(); + + /** + * 售后服务 + * + * @return 售后服务 + */ + WxChannelAfterSaleService getAfterSaleService(); + + /** + * 运费模板服务 + * + * @return 运费模板服务 + */ + WxChannelFreightTemplateService getFreightTemplateService(); + + /** + * 地址服务 + * + * @return 地址服务 + */ + WxChannelAddressService getAddressService(); + + /** + * 优惠券服务 + * + * @return 优惠券服务 + */ + WxChannelCouponService getCouponService(); + + /** + * 分享员服务 + * + * @return 分享员服务 + */ + WxChannelSharerService getSharerService(); + + /** + * 资金服务 + * + * @return 资金服务 + */ + WxChannelFundService getFundService(); + + /** + * 主页管理服务 + * + * @return 主页管理服务 + */ + WxStoreHomePageService getHomePageService(); + + /** + * 合作账号服务 + * + * @return 团长合作服务 + */ + WxStoreCooperationService getCooperationService(); + + /** + * 视频号/微信小店 罗盘商家版服务 + * + * @return 罗盘商家版服务 + */ + WxChannelCompassShopService getCompassShopService(); + + /** + * 优选联盟-团长合作达人管理服务 + * + * @return 团长合作达人管理服务 + */ + WxLeagueWindowService getLeagueWindowService(); + + /** + * 优选联盟-团长服务 + * + * @return 团长服务 + */ + WxLeagueSupplierService getLeagueSupplierService(); + + /** + * 优选联盟-达人服务 + * + * @return 达人服务 + */ + WxLeaguePromoterService getLeaguePromoterService(); + + /** + * 优选联盟-商品服务 + * + * @return 商品服务 + */ + WxLeagueProductService getLeagueProductService(); + + /** + * 视频号助手 留资组件管理服务 + * + * @return 留资组件管理服务 + */ + WxLeadComponentService getLeadComponentService(); + + /** + * 视频号助手 留资服务的直播数据服务 + * + * @return 留资服务的直播数据服务 + */ + WxFinderLiveService getFinderLiveService(); + + /** + * 视频号助手 橱窗管理服务 + * + * @return 橱窗管理服务 + */ + WxAssistantService getAssistantService(); + + /** + * 会员功能 + * + * @return 会员服务 + */ + WxChannelVipService getVipService(); + + /** + * 视频号助手-罗盘达人版服务 + * + * @return 罗盘达人版服务 + */ + WxChannelCompassFinderService getCompassFinderService(); + + /** + * 视频号助手-直播大屏数据服务 + * + * @return 直播大屏数据服务 + */ + WxChannelLiveDashboardService getLiveDashboardService(); + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxChannelSharerService.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxChannelSharerService.java new file mode 100644 index 0000000000..300493158b --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxChannelSharerService.java @@ -0,0 +1,71 @@ +package me.chanjar.weixin.channel.api; + +import java.util.List; +import me.chanjar.weixin.channel.bean.sharer.SharerBindResponse; +import me.chanjar.weixin.channel.bean.sharer.SharerInfoResponse; +import me.chanjar.weixin.channel.bean.sharer.SharerOrderParam; +import me.chanjar.weixin.channel.bean.sharer.SharerOrderResponse; +import me.chanjar.weixin.channel.bean.sharer.SharerSearchResponse; +import me.chanjar.weixin.channel.bean.sharer.SharerUnbindResponse; +import me.chanjar.weixin.common.error.WxErrorException; + +/** + * 视频号小店 分享员服务接口 + * + * @author Zeyes + */ +public interface WxChannelSharerService { + + /** + * 邀请分享员 + * + * @param username 邀请的用户微信号 + * @return SharerBindResponse + * + * @throws WxErrorException 异常 + */ + SharerBindResponse bindSharer(String username) throws WxErrorException; + + /** + * 获取绑定的分享员 + * + * @param openid 分享员openid + * @param username 分享员微信号(二选一) + * @return SharerSearchResponse + * + * @throws WxErrorException 异常 + */ + SharerSearchResponse searchSharer(String openid, String username) throws WxErrorException; + + /** + * 获取绑定的分享员列表 + * + * @param page 分页参数,页数 + * @param pageSize 分页参数,每页分享员数(不超过100 + * @param sharerType 分享员类型 + * @return 分享员列表 + * + * @throws WxErrorException 异常 + */ + SharerInfoResponse listSharer(Integer page, Integer pageSize, Integer sharerType) throws WxErrorException; + + /** + * 获取分享员订单列表 + * + * @param param 参数 + * @return 列表 + * + * @throws WxErrorException 异常 + */ + SharerOrderResponse listSharerOrder(SharerOrderParam param) throws WxErrorException; + + /** + * 解绑分享员 + * + * @param openIds openid列表 + * @return 状态 + * + * @throws WxErrorException 异常 + */ + SharerUnbindResponse unbindSharer(List openIds) throws WxErrorException; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxChannelVipService.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxChannelVipService.java new file mode 100644 index 0000000000..4100659200 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxChannelVipService.java @@ -0,0 +1,97 @@ +package me.chanjar.weixin.channel.api; + +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; +import me.chanjar.weixin.channel.bean.vip.VipInfoResponse; +import me.chanjar.weixin.channel.bean.vip.VipListResponse; +import me.chanjar.weixin.channel.bean.vip.VipScoreResponse; +import me.chanjar.weixin.common.error.WxErrorException; + +/** + * 视频号小店 会员功能接口 + * + * @author aushiye + * @link 会员功能接口文档 + */ +public interface WxChannelVipService { + /** 拉取用户详情 */ + // String VIP_USER_INFO_URL = "https://api.weixin.qq.com/channels/ec/vip/user/info/get"; + // /** 拉取用户列表 */ + // String VIP_USER_LIST_URL = "https://api.weixin.qq.com/channels/ec/vip/user/list/get"; + // + // /** 获取用户积分 */ + // String VIP_SCORE_URL = "https://api.weixin.qq.com/channels/ec/vip/user/score/get"; + // /** 增加用户积分 */ + // String SCORE_INCREASE_URL = "https://api.weixin.qq.com/channels/ec/vip/user/score/increase"; + // /** 减少用户积分 */ + // String SCORE_DECREASE_URL = "https://api.weixin.qq.com/channels/ec/vip/user/score/decrease"; + // + // /** 更新用户等级 */ + // String GRADE_UPDATE_URL = "https://api.weixin.qq.com/channels/ec/vip/user/grade/update"; + + + /** + * 获取用户详情 + * + * @param openId the open id + * @param needPhoneNumber the need phone number + * @return the vip info + * @throws WxErrorException the wx error exception + */ + VipInfoResponse getVipInfo(String openId, Boolean needPhoneNumber) throws WxErrorException; + + + /** + * 获取用户积分 + * + * @param needPhoneNumber the need phone number + * @param pageNum the page num + * @param pageSize the page size + * @return the vip list + * @throws WxErrorException the wx error exception + */ + VipListResponse getVipList(Boolean needPhoneNumber, Integer pageNum, Integer pageSize) throws WxErrorException; + + /** + * 获取用户积分 + * + * @param openId the open id + * @return the vip score + * @throws WxErrorException the wx error exception + */ + VipScoreResponse getVipScore(String openId) throws WxErrorException; + + /** + * 增加用户积分 + * + * @param openId the open id + * @param score the score + * @param remark the remark + * @param requestId the request id + * @return the wx channel base response + * @throws WxErrorException the wx error exception + */ + WxChannelBaseResponse increaseVipScore(String openId, String score, String remark, String requestId) throws WxErrorException; + + /** + * 减少用户积分 + * + * @param openId the open id + * @param score the score + * @param remark the remark + * @param requestId the request id + * @return the wx channel base response + * @throws WxErrorException the wx error exception + */ + WxChannelBaseResponse decreaseVipScore(String openId, String score, String remark, String requestId) throws WxErrorException; + + /** + * 更新用户等级 + * + * @param openId the open id + * @param score the score + * @return the wx channel base response + * @throws WxErrorException the wx error exception + */ + WxChannelBaseResponse updateVipGrade(String openId, Integer score) throws WxErrorException; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxChannelWarehouseService.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxChannelWarehouseService.java new file mode 100644 index 0000000000..1bb00885f5 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxChannelWarehouseService.java @@ -0,0 +1,137 @@ +package me.chanjar.weixin.channel.api; + + +import java.util.List; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; +import me.chanjar.weixin.channel.bean.warehouse.LocationPriorityResponse; +import me.chanjar.weixin.channel.bean.warehouse.PriorityLocationParam; +import me.chanjar.weixin.channel.bean.warehouse.WarehouseIdsResponse; +import me.chanjar.weixin.channel.bean.warehouse.WarehouseLocation; +import me.chanjar.weixin.channel.bean.warehouse.WarehouseParam; +import me.chanjar.weixin.channel.bean.warehouse.WarehouseResponse; +import me.chanjar.weixin.channel.bean.warehouse.WarehouseStockParam; +import me.chanjar.weixin.channel.bean.warehouse.WarehouseStockResponse; +import me.chanjar.weixin.common.error.WxErrorException; + + +/** + * 视频号小店 区域仓库服务 + * + * @author Zeyes + */ +public interface WxChannelWarehouseService { + + /** + * 创建仓库 + * + * @param param 仓库信息 + * @return 响应 + * + * @throws WxErrorException 异常 + */ + WxChannelBaseResponse createWarehouse(WarehouseParam param) throws WxErrorException; + + /** + * 查询仓库列表 + * + * @param pageSize 每页数量(最大不超过10) + * @param nextKey 由上次请求返回,记录翻页的上下文。传入时会从上次返回的结果往后翻一页,不传默认拉取第一页数据 + * @return 响应 + * + * @throws WxErrorException 异常 + */ + WarehouseIdsResponse listWarehouse(Integer pageSize, String nextKey) throws WxErrorException; + + /** + * 获取仓库详情 + * + * @param outWarehouseId 外部仓库ID + * @return 响应 + * + * @throws WxErrorException 异常 + */ + WarehouseResponse getWarehouse(String outWarehouseId) throws WxErrorException; + + /** + * 修改仓库详情 + * + * @param outWarehouseId 外部仓库ID + * @param name 仓库名称 + * @param intro 仓库介绍 + * @return 响应 + * + * @throws WxErrorException 异常 + */ + WxChannelBaseResponse updateWarehouse(String outWarehouseId, String name, String intro) throws WxErrorException; + + /** + * 批量增加覆盖区域 + * + * @param outWarehouseId 外部仓库ID + * @param coverLocations 覆盖区域 + * @return 响应 + * + * @throws WxErrorException 异常 + */ + WxChannelBaseResponse addWarehouseArea(String outWarehouseId, List coverLocations) + throws WxErrorException; + + /** + * 批量删除覆盖区域 + * + * @param outWarehouseId 外部仓库ID + * @param coverLocations 覆盖区域 + * @return 响应 + * + * @throws WxErrorException 异常 + */ + WxChannelBaseResponse deleteWarehouseArea(String outWarehouseId, List coverLocations) + throws WxErrorException; + + /** + * 设置指定地址下的仓的优先级 + * + * @param param 参数 + * @return 响应 + * + * @throws WxErrorException 异常 + */ + WxChannelBaseResponse setWarehousePriority(PriorityLocationParam param) throws WxErrorException; + + /** + * 获取指定地址下的仓的优先级 + * + * @param addressId1 省份地址编码 + * @param addressId2 市地址编码 + * @param addressId3 区地址编码 + * @param addressId4 街道地址编码 + * @return 仓的优先级 + * + * @throws WxErrorException 异常 + */ + LocationPriorityResponse getWarehousePriority(Integer addressId1, Integer addressId2, Integer addressId3, + Integer addressId4) throws WxErrorException; + + /** + * 更新区域仓库存数量 + * + * @param param 参数 + * @return 响应 + * + * @throws WxErrorException 异常 + */ + WxChannelBaseResponse updateWarehouseStock(WarehouseStockParam param) throws WxErrorException; + + /** + * 获取区域仓库存数量 + * + * @param productId 商品ID + * @param outWarehouseId 外部仓库ID + * @param skuId 商品skuId + * @return 响应 + * + * @throws WxErrorException 异常 + */ + WarehouseStockResponse getWarehouseStock(String productId, String skuId, String outWarehouseId) + throws WxErrorException; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxFinderLiveService.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxFinderLiveService.java new file mode 100644 index 0000000000..6e98134bcb --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxFinderLiveService.java @@ -0,0 +1,41 @@ +package me.chanjar.weixin.channel.api; + +import me.chanjar.weixin.channel.bean.lead.component.request.GetFinderLiveDataListRequest; +import me.chanjar.weixin.channel.bean.lead.component.request.GetFinderLiveLeadsDataRequest; +import me.chanjar.weixin.channel.bean.lead.component.response.FinderAttrResponse; +import me.chanjar.weixin.channel.bean.lead.component.response.GetFinderLiveDataListResponse; +import me.chanjar.weixin.channel.bean.lead.component.response.GetFinderLiveLeadsDataResponse; +import me.chanjar.weixin.common.error.WxErrorException; + +/** + * 视频号助手 留资服务的直播数据服务 + * + * @author imyzt + */ +public interface WxFinderLiveService { + + /** + * 获取视频号账号信息 + * + * @return 视频号账号信息 + */ + FinderAttrResponse getFinderAttrByAppid() throws WxErrorException; + + /** + * 获取留资直播间数据详情 + * + * @param req 留资组件信息 + * @return 留资信息详情 + */ + GetFinderLiveDataListResponse getFinderLiveDataList(GetFinderLiveDataListRequest req) throws WxErrorException; + + /** + * 获取账号收集的留资数量 + * 说明:该接口只统计2023.9.13号起的数据,所以start_time应大于等于1694534400 + * + * @param req 留资组件信息 + * @return 留资信息列表 + */ + GetFinderLiveLeadsDataResponse getFinderLiveLeadsData(GetFinderLiveLeadsDataRequest req) throws WxErrorException; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxLeadComponentService.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxLeadComponentService.java new file mode 100644 index 0000000000..36ae14bed3 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxLeadComponentService.java @@ -0,0 +1,60 @@ +package me.chanjar.weixin.channel.api; + +import me.chanjar.weixin.channel.bean.lead.component.request.GetLeadInfoByComponentRequest; +import me.chanjar.weixin.channel.bean.lead.component.request.GetLeadsComponentIdRequest; +import me.chanjar.weixin.channel.bean.lead.component.request.GetLeadsComponentPromoteRecordRequest; +import me.chanjar.weixin.channel.bean.lead.component.request.GetLeadsInfoByRequestIdRequest; +import me.chanjar.weixin.channel.bean.lead.component.request.GetLeadsRequestIdRequest; +import me.chanjar.weixin.channel.bean.lead.component.response.GetLeadsComponentIdResponse; +import me.chanjar.weixin.channel.bean.lead.component.response.GetLeadsComponentPromoteRecordResponse; +import me.chanjar.weixin.channel.bean.lead.component.response.GetLeadsRequestIdResponse; +import me.chanjar.weixin.channel.bean.lead.component.response.LeadInfoResponse; +import me.chanjar.weixin.common.error.WxErrorException; + +/** + * 视频号助手 留资组件管理服务 + * + * @author imyzt + */ +public interface WxLeadComponentService { + + /** + * 按时间获取留资信息详情 + * + * @param req 留资组件信息 + * @return 留资信息详情 + */ + LeadInfoResponse getLeadsInfoByComponentId(GetLeadInfoByComponentRequest req) throws WxErrorException; + + /** + * 按直播场次获取留资信息详情 + * + * @param req 留资组件信息 + * @return 留资信息详情 + */ + LeadInfoResponse getLeadsInfoByRequestId(GetLeadsInfoByRequestIdRequest req) throws WxErrorException; + + /** + * 获取留资request_id列表详情 + * + * @param req 留资组件信息 + * @return 留资信息列表 + */ + GetLeadsRequestIdResponse getLeadsRequestId(GetLeadsRequestIdRequest req) throws WxErrorException; + + /** + * 获取留资组件直播推广记录信息详情 + * + * @param req 留资组件信息 + * @return 留资组件直播推广记录信息详情 + */ + GetLeadsComponentPromoteRecordResponse getLeadsComponentPromoteRecord(GetLeadsComponentPromoteRecordRequest req) throws WxErrorException; + + /** + * 获取留资组件Id列表详情 + * + * @param req 留资组件信息 + * @return 留资组件Id列表 + */ + GetLeadsComponentIdResponse getLeadsComponentId(GetLeadsComponentIdRequest req) throws WxErrorException; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxLeagueProductService.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxLeagueProductService.java new file mode 100644 index 0000000000..d8d6781505 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxLeagueProductService.java @@ -0,0 +1,62 @@ +package me.chanjar.weixin.channel.api; + +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; +import me.chanjar.weixin.channel.bean.league.product.BatchAddParam; +import me.chanjar.weixin.channel.bean.league.product.BatchAddResponse; +import me.chanjar.weixin.channel.bean.league.product.ProductDetailParam; +import me.chanjar.weixin.channel.bean.league.product.ProductDetailResponse; +import me.chanjar.weixin.channel.bean.league.product.ProductListParam; +import me.chanjar.weixin.channel.bean.league.product.ProductListResponse; +import me.chanjar.weixin.channel.bean.league.product.ProductUpdateParam; +import me.chanjar.weixin.channel.bean.league.product.ProductUpdateResponse; +import me.chanjar.weixin.common.error.WxErrorException; + +/** + * 优选联盟 商品操作服务 + * + * @author Zeyes + */ +public interface WxLeagueProductService { + + /** + * 批量新增联盟商品 + * + * @param param 参数 + * @return 结果 + */ + BatchAddResponse batchAddProduct(BatchAddParam param) throws WxErrorException; + + /** + * 更新联盟商品信息 + * + * @param param 参数 + * @return 结果 + */ + ProductUpdateResponse updateProduct(ProductUpdateParam param) throws WxErrorException; + + /** + * 删除联盟商品 + * + * @param type 1普通推广商品 2定向推广商品 3专属推广商品 + * @param productId 商品id type为普通推广商品时必填 + * @param infoId 特殊推广商品计划id type为特殊推广商品时必填 + * @return + */ + WxChannelBaseResponse deleteProduct(Integer type, String productId, String infoId) throws WxErrorException; + + /** + * 拉取联盟商品详情 + * + * @param param 参数 + * @return 结果 + */ + ProductDetailResponse getProductDetail(ProductDetailParam param) throws WxErrorException; + + /** + * 拉取联盟商品推广列表 + * + * @param param 参数 + * @return 结果 + */ + ProductListResponse listProduct(ProductListParam param) throws WxErrorException; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxLeaguePromoterService.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxLeaguePromoterService.java new file mode 100644 index 0000000000..60cf112271 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxLeaguePromoterService.java @@ -0,0 +1,98 @@ +package me.chanjar.weixin.channel.api; + +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; +import me.chanjar.weixin.channel.bean.league.promoter.PromoterInfoResponse; +import me.chanjar.weixin.channel.bean.league.promoter.PromoterListResponse; +import me.chanjar.weixin.common.error.WxErrorException; + +/** + * 优选联盟 达人服务 + * + * @author Zeyes + */ +public interface WxLeaguePromoterService { + + /** + * 新增达人 + * + * @param finderId 视频号finder_id,待废除 + * @return 结果 + * @deprecated 使用 {@link #addPromoterV2(String)} + */ + @Deprecated + WxChannelBaseResponse addPromoter(String finderId) throws WxErrorException; + + /** + * 编辑达人 + * + * @param finderId 视频号finder_id,待废除 + * @param type 操作 1取消邀请 2结束合作 + * @return 结果 + * @deprecated 使用 {@link #updatePromoterV2(String, int)} + */ + @Deprecated + WxChannelBaseResponse updatePromoter(String finderId, int type) throws WxErrorException; + + /** + * 删除达人 + * + * @param finderId 视频号finder_id,待废除 + * @return 结果 + * @deprecated 使用 {@link #deletePromoterV2(String)} + */ + @Deprecated + WxChannelBaseResponse deletePromoter(String finderId) throws WxErrorException; + + /** + * 获取达人详情信息 + * + * @param finderId 视频号finder_id,待废除 + * @return 结果 + * @deprecated 使用 {@link #getPromoterInfoV2(String)} + */ + @Deprecated + PromoterInfoResponse getPromoterInfo(String finderId) throws WxErrorException; + + /** + * 新增达人 + * + * @param promoterId 达人带货id + * @return 结果 + */ + WxChannelBaseResponse addPromoterV2(String promoterId) throws WxErrorException; + + /** + * 编辑达人 + * + * @param promoterId 达人带货id + * @param type 操作 1取消邀请 2结束合作 + * @return 结果 + */ + WxChannelBaseResponse updatePromoterV2(String promoterId, int type) throws WxErrorException; + + /** + * 删除达人 + * + * @param promoterId 达人带货id + * @return 结果 + */ + WxChannelBaseResponse deletePromoterV2(String promoterId) throws WxErrorException; + + /** + * 获取达人详情信息 + * + * @param promoterId 达人带货id + * @return 结果 + */ + PromoterInfoResponse getPromoterInfoV2(String promoterId) throws WxErrorException; + + /** + * 获取达人列表 + * + * @param pageIndex 页面下标,下标从1开始,默认为1 + * @param pageSize 单页达人数(不超过200) + * @param status 拉取该状态下的达人列表 + * @return 结果 + */ + PromoterListResponse listPromoter(Integer pageIndex, Integer pageSize, Integer status) throws WxErrorException; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxLeagueSupplierService.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxLeagueSupplierService.java new file mode 100644 index 0000000000..cde96843f1 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxLeagueSupplierService.java @@ -0,0 +1,98 @@ +package me.chanjar.weixin.channel.api; + +import me.chanjar.weixin.channel.bean.league.supplier.CommissionOrderListParam; +import me.chanjar.weixin.channel.bean.league.supplier.CommissionOrderListResponse; +import me.chanjar.weixin.channel.bean.league.supplier.CommissionOrderResponse; +import me.chanjar.weixin.channel.bean.league.supplier.CoopProductListResponse; +import me.chanjar.weixin.channel.bean.league.supplier.CoopProductResponse; +import me.chanjar.weixin.channel.bean.league.supplier.FlowListParam; +import me.chanjar.weixin.channel.bean.league.supplier.ShopDetailResponse; +import me.chanjar.weixin.channel.bean.league.supplier.ShopListResponse; +import me.chanjar.weixin.channel.bean.league.supplier.SupplierBalanceResponse; +import me.chanjar.weixin.channel.bean.league.supplier.SupplierFlowDetailResponse; +import me.chanjar.weixin.channel.bean.league.supplier.SupplierFlowListResponse; +import me.chanjar.weixin.common.error.WxErrorException; + +/** + * 优选联盟 团长数据服务 + * + * @author Zeyes + */ +public interface WxLeagueSupplierService { + + /** + * 获取团长账户余额 + * + * @return 余额 + */ + SupplierBalanceResponse getBalanceInfo() throws WxErrorException; + + /** + * 获取资金流水详情 + * + * @param flowId 流水ID + * @return 流水详情 + */ + SupplierFlowDetailResponse getFlowDetail(String flowId) throws WxErrorException; + + /** + * 获取团长资金流水列表 + * + * @param param 查询参数 + * @return 流水列表 + */ + SupplierFlowListResponse getFlowList(FlowListParam param) throws WxErrorException; + + /** + * 获取合作商品详情 + * + * @param productId 商品ID + * @param appId 团长商品 所属小店appid + * @return 商品详情 + */ + CoopProductResponse getProductDetail(String productId, String appId) throws WxErrorException; + + /** + * 获取合作商品列表 + * + * @param appid 团长商品 所属小店appid + * @param pageSize 单页商品数(不超过30) + * @param nextKey 由上次请求返回,顺序翻页时需要传入, 会从上次返回的结果往后翻一页 + * @return 商品列表 + */ + CoopProductListResponse getProductList(String appid, Integer pageSize, String nextKey) throws WxErrorException; + + /** + * 获取佣金单详情 + * + * @param orderId 订单号,可从获取佣金单列表中获得 + * @param skuId 商品skuId + * @return 订单详情 + */ + CommissionOrderResponse getCommissionOrder(String orderId, String skuId) throws WxErrorException; + + /** + * 获取佣金单列表 + * + * @param param 查询参数 + * @return 佣金单列表 + */ + CommissionOrderListResponse getCommissionOrderList(CommissionOrderListParam param) throws WxErrorException; + + /** + * 获取合作小店详情 + * + * @param appid 小店appid + * @return 小店详情 + */ + ShopDetailResponse getShopDetail(String appid) throws WxErrorException; + + /** + * 获取合作小店列表 + * + * @param pageSize 单页小店数(不超过30) + * @param nextKey 由上次请求返回,顺序翻页时需要传入, 会从上次返回的结果往后翻一页 + * @return 小店列表 + */ + ShopListResponse getShopList(Integer pageSize, String nextKey) throws WxErrorException; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxLeagueWindowService.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxLeagueWindowService.java new file mode 100644 index 0000000000..c4af1571d0 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxLeagueWindowService.java @@ -0,0 +1,72 @@ +package me.chanjar.weixin.channel.api; + +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; +import me.chanjar.weixin.channel.bean.league.window.AuthInfoResponse; +import me.chanjar.weixin.channel.bean.league.window.AuthStatusResponse; +import me.chanjar.weixin.channel.bean.league.window.ProductSearchParam; +import me.chanjar.weixin.channel.bean.league.window.WindowProductListResponse; +import me.chanjar.weixin.channel.bean.league.window.WindowProductResponse; +import me.chanjar.weixin.common.error.WxErrorException; + +/** + * 优选联盟 团长合作达人管理服务 + * + * @author Zeyes + */ +public interface WxLeagueWindowService { + + /** + * 添加团长商品到橱窗 + * + * @param appid 团长appid + * @param openfinderid 视频号openfinderid + * @param productId 团长商品ID + * @return 结果 + */ + WxChannelBaseResponse addProduct(String appid, String openfinderid, String productId) throws WxErrorException; + + /** + * 查询橱窗上团长商品列表 + * + * @param param 查询参数 + * @return 团长商品列表 + */ + WindowProductListResponse listProduct(ProductSearchParam param) throws WxErrorException; + + /** + * 从橱窗移除团长商品 + * + * @param appid 团长appid + * @param openfinderid 视频号openfinderid + * @param productId 团长商品ID + * @return 结果 + */ + WxChannelBaseResponse removeProduct(String appid, String openfinderid, String productId) throws WxErrorException; + + /** + * 查询橱窗上团长商品详情 + * + * @param appid 团长appid + * @param openfinderid 视频号openfinderid + * @param productId 团长商品ID + * @return 结果 + */ + WindowProductResponse getProductDetail(String appid, String openfinderid, String productId) + throws WxErrorException; + + /** + * 获取达人橱窗授权链接 + * + * @param finderId 视频号finder_id + * @return 授权链接 + */ + AuthInfoResponse getWindowAuthInfo(String finderId) throws WxErrorException; + + /** + * 获取达人橱窗授权状态 + * + * @param finderId 视频号finder_id + * @return 授权链接 + */ + AuthStatusResponse getWindowAuthStatus(String finderId) throws WxErrorException; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxStoreCooperationService.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxStoreCooperationService.java new file mode 100644 index 0000000000..96d2ff5f8d --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxStoreCooperationService.java @@ -0,0 +1,70 @@ +package me.chanjar.weixin.channel.api; + +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; +import me.chanjar.weixin.channel.bean.cooperation.CooperationListResponse; +import me.chanjar.weixin.channel.bean.cooperation.CooperationQrCodeResponse; +import me.chanjar.weixin.channel.bean.cooperation.CooperationStatusResponse; +import me.chanjar.weixin.common.error.WxErrorException; + +/** + * 微信小店 合作账号相关接口 + * + * @author Zeyes + * @see 合作账号状态机 + */ +public interface WxStoreCooperationService { + + /** + * 获取合作账号列表 + * + * @param sharerType 合作账号类型 2公众号 3小程序 + * @return 合作账号列表 + * + * @throws WxErrorException 异常 + */ + CooperationListResponse listCooperation(Integer sharerType) throws WxErrorException; + + /** + * 获取合作账号状态 + * + * @param sharerId 合作账号id 公众号: gh_开头id 小程序: appid + * @param sharerType 合作账号类型 2公众号 3小程序 + * @return 合作账号状态 + * + * @throws WxErrorException 异常 + */ + CooperationStatusResponse getCooperationStatus(String sharerId, Integer sharerType) throws WxErrorException; + + /** + * 生成合作账号邀请二维码 + * + * @param sharerId 合作账号id 公众号: gh_开头id 小程序: appid + * @param sharerType 合作账号类型 2公众号 3小程序 + * @return 二维码 + * + * @throws WxErrorException 异常 + */ + CooperationQrCodeResponse generateQrCode(String sharerId, Integer sharerType) throws WxErrorException; + + /** + * 取消合作账号邀请 + * + * @param sharerId 合作账号id 公众号: gh_开头id 小程序: appid + * @param sharerType 合作账号类型 2公众号 3小程序 + * @return WxChannelBaseResponse + * + * @throws WxErrorException 异常 + */ + WxChannelBaseResponse cancelInvitation(String sharerId, Integer sharerType) throws WxErrorException; + + /** + * 解绑合作账号 + * + * @param sharerId 合作账号id 公众号: gh_开头id 小程序: appid + * @param sharerType 合作账号类型 2公众号 3小程序 + * @return WxChannelBaseResponse + * + * @throws WxErrorException 异常 + */ + WxChannelBaseResponse unbind(String sharerId, Integer sharerType) throws WxErrorException; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxStoreHomePageService.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxStoreHomePageService.java new file mode 100644 index 0000000000..bd11e471b3 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxStoreHomePageService.java @@ -0,0 +1,188 @@ +package me.chanjar.weixin.channel.api; + +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; +import me.chanjar.weixin.channel.bean.home.background.BackgroundApplyResponse; +import me.chanjar.weixin.channel.bean.home.background.BackgroundGetResponse; +import me.chanjar.weixin.channel.bean.home.banner.BannerApplyResponse; +import me.chanjar.weixin.channel.bean.home.banner.BannerGetResponse; +import me.chanjar.weixin.channel.bean.home.banner.BannerInfo; +import me.chanjar.weixin.channel.bean.home.tree.TreeProductEditInfo; +import me.chanjar.weixin.channel.bean.home.tree.TreeProductListInfo; +import me.chanjar.weixin.channel.bean.home.tree.TreeProductListResponse; +import me.chanjar.weixin.channel.bean.home.tree.TreeShowGetResponse; +import me.chanjar.weixin.channel.bean.home.tree.TreeShowInfo; +import me.chanjar.weixin.channel.bean.home.tree.TreeShowSetResponse; +import me.chanjar.weixin.channel.bean.home.window.WindowProductSettingResponse; +import me.chanjar.weixin.common.error.WxErrorException; + +/** + * 微信小店 主页管理相关接口 + * + * @author Zeyes + */ +public interface WxStoreHomePageService { + + /** + * 添加分类关联的商品 + * + * @param info 商品分类以及商品id + * @return BaseResponse + * + * @throws WxErrorException 异常 + */ + WxChannelBaseResponse addTreeProduct(TreeProductEditInfo info) throws WxErrorException; + + /** + * 删除分类关联的商品 + * + * @param info 商品分类以及商品id + * @return BaseResponse + * + * @throws WxErrorException 异常 + */ + WxChannelBaseResponse delTreeProduct(TreeProductEditInfo info) throws WxErrorException; + + /** + * 获取分类关联的商品ID列表 + * + * @param info 分类id、分页大小、分页上下文 + * @return 商品id、分页上下文 + * + * @throws WxErrorException 异常 + */ + TreeProductListResponse getTreeProductList(TreeProductListInfo info) throws WxErrorException; + + /** + * 设置展示在店铺主页的商品分类 + * + * @param info 分类id + * @return 商品分类审核结果 + * + * @throws WxErrorException 异常 + */ + TreeShowSetResponse setShowTree(TreeShowInfo info) throws WxErrorException; + + /** + * 获取展示在店铺主页的商品分类 + * + * @return 商品分类信息 + * + * @throws WxErrorException 异常 + */ + TreeShowGetResponse getShowTree() throws WxErrorException; + + /** + * 获取主页展示商品列表 + * + * @param pageSize 分页大小 + * @param nextKey 分页上下文 + * @return WindowProductSettingResponse + * + * @throws WxErrorException 异常 + */ + WindowProductSettingResponse listWindowProduct(Integer pageSize, String nextKey) throws WxErrorException; + + /** + * 删除主页展示商品 + * + * @param productId 商品id + * @param indexNum 商品重新排序后的新序号,最大移动步长为500(即新序号与当前序号的距离小于500) + * @return BaseResponse + * + * @throws WxErrorException 异常 + */ + WxChannelBaseResponse reorderWindowProduct(String productId, Integer indexNum) throws WxErrorException; + + /** + * 隐藏小店主页商品 + * + * @param productId 商品id + * @param setHide 是否隐藏。1-隐藏,0-取消隐藏 + * @return BaseResponse + * + * @throws WxErrorException 异常 + */ + WxChannelBaseResponse hideWindowProduct(String productId, Integer setHide) throws WxErrorException; + + /** + * 置顶小店主页商品 + * + * @param productId 商品id + * @param setTop 是否顶置。1-置顶,0-取消置顶 + * @return BaseResponse + * + * @throws WxErrorException 异常 + */ + WxChannelBaseResponse topWindowProduct(String productId, Integer setTop) throws WxErrorException; + + /** + * 提交背景图申请 + * + * @param imgUrl 图片链接。请务必使用接口上传图片(参数resp_type=1),并将返回的img_url填入此处,不接受其他任何格式的图片url。 + * 若url曾经做过转换(url前缀为mmecimage.cn/p/),则可以直接提交。 + * @return 申请编号 + * + * @throws WxErrorException 异常 + * @see WxChannelBasicService#uploadImg(int, String) + */ + BackgroundApplyResponse applyBackground(String imgUrl) throws WxErrorException; + + /** + * 查询背景图 + * + * @return 背景图信息 + * @throws WxErrorException 异常 + */ + BackgroundGetResponse getBackground() throws WxErrorException; + + /** + * 撤销主页背景图申请 + * + * @param applyId 申请编号 + * @return BaseResponse + * @throws WxErrorException 异常 + */ + WxChannelBaseResponse cancelBackground(Integer applyId) throws WxErrorException; + + /** + * 清空主页背景图并撤销流程中的申请 + * + * @return BaseResponse + * @throws WxErrorException 异常 + */ + WxChannelBaseResponse removeBackground() throws WxErrorException; + + /** + * 提交精选展示位申请 + * + * @param info 展示位信息 + * @return 申请编号 + * @throws WxErrorException 异常 + */ + BannerApplyResponse applyBanner(BannerInfo info) throws WxErrorException; + + /** + * 查询精选展示位 + * + * @return 展示位信息 + * @throws WxErrorException 异常 + */ + BannerGetResponse getBanner() throws WxErrorException; + + /** + * 撤销精选展示位申请 + * + * @param applyId 申请编号 + * @return BaseResponse + * @throws WxErrorException 异常 + */ + WxChannelBaseResponse cancelBanner(Integer applyId) throws WxErrorException; + + /** + * 清空精选展示位并撤销流程中的申请 + * + * @return BaseResponse + * @throws WxErrorException 异常 + */ + WxChannelBaseResponse removeBanner() throws WxErrorException; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/BaseWxChannelMessageServiceImpl.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/BaseWxChannelMessageServiceImpl.java new file mode 100644 index 0000000000..0aeabdd7c6 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/BaseWxChannelMessageServiceImpl.java @@ -0,0 +1,419 @@ +package me.chanjar.weixin.channel.api.impl; + +import java.util.Map; +import lombok.extern.slf4j.Slf4j; +import me.chanjar.weixin.channel.api.BaseWxChannelMessageService; +import me.chanjar.weixin.channel.api.WxChannelService; +import me.chanjar.weixin.channel.bean.message.after.AfterSaleMessage; +import me.chanjar.weixin.channel.bean.message.after.ComplaintMessage; +import me.chanjar.weixin.channel.bean.message.coupon.CouponActionMessage; +import me.chanjar.weixin.channel.bean.message.coupon.CouponReceiveMessage; +import me.chanjar.weixin.channel.bean.message.coupon.UserCouponExpireMessage; +import me.chanjar.weixin.channel.bean.message.fund.AccountNotifyMessage; +import me.chanjar.weixin.channel.bean.message.fund.QrNotifyMessage; +import me.chanjar.weixin.channel.bean.message.fund.WithdrawNotifyMessage; +import me.chanjar.weixin.channel.bean.message.order.OrderCancelMessage; +import me.chanjar.weixin.channel.bean.message.order.OrderConfirmMessage; +import me.chanjar.weixin.channel.bean.message.order.OrderDeliveryMessage; +import me.chanjar.weixin.channel.bean.message.order.OrderExtMessage; +import me.chanjar.weixin.channel.bean.message.order.OrderIdMessage; +import me.chanjar.weixin.channel.bean.message.order.OrderPayMessage; +import me.chanjar.weixin.channel.bean.message.order.OrderSettleMessage; +import me.chanjar.weixin.channel.bean.message.order.OrderStatusMessage; +import me.chanjar.weixin.channel.bean.message.product.BrandMessage; +import me.chanjar.weixin.channel.bean.message.product.CategoryAuditMessage; +import me.chanjar.weixin.channel.bean.message.product.SpuAuditMessage; +import me.chanjar.weixin.channel.bean.message.product.SpuStockMessage; +import me.chanjar.weixin.channel.bean.message.sharer.SharerChangeMessage; +import me.chanjar.weixin.channel.bean.message.store.CloseStoreMessage; +import me.chanjar.weixin.channel.bean.message.store.NicknameUpdateMessage; +import me.chanjar.weixin.channel.bean.message.supplier.SupplierItemMessage; +import me.chanjar.weixin.channel.bean.message.vip.ExchangeInfoMessage; +import me.chanjar.weixin.channel.bean.message.vip.UserInfoMessage; +import me.chanjar.weixin.channel.bean.message.voucher.VoucherMessage; +import me.chanjar.weixin.channel.message.WxChannelMessage; +import me.chanjar.weixin.channel.message.WxChannelMessageRouter; +import me.chanjar.weixin.channel.message.WxChannelMessageRouterRule; +import me.chanjar.weixin.channel.message.rule.HandlerConsumer; +import me.chanjar.weixin.channel.util.JsonUtils; +import me.chanjar.weixin.common.session.WxSessionManager; + +import static me.chanjar.weixin.channel.constant.MessageEventConstants.*; + +/** + * @author Zeyes + */ +@Slf4j +public abstract class BaseWxChannelMessageServiceImpl implements BaseWxChannelMessageService { + + /** 消息路由器 */ + protected WxChannelMessageRouter router; + + public BaseWxChannelMessageServiceImpl(WxChannelMessageRouter router) { + this.router = router; + this.addDefaultRule(); + } + + /** + * 添加默认的回调规则 + */ + protected void addDefaultRule() { + /* 品牌资质事件回调 */ + this.addRule(BrandMessage.class, BRAND, this::brandUpdate); + /* 商品审核结果 */ + this.addRule(SpuAuditMessage.class, PRODUCT_SPU_AUDIT, this::spuAudit); + /* 商品上下架 */ + this.addRule(SpuAuditMessage.class, PRODUCT_SPU_STATUS_UPDATE, this::spuStatusUpdate); + /* 商品更新 */ + this.addRule(SpuAuditMessage.class, PRODUCT_SPU_UPDATE, this::spuUpdate); + /* 商品库存不足 */ + this.addRule(SpuStockMessage.class, PRODUCT_STOCK_NO_ENOUGH, this::stockNoEnough); + /* 类目审核结果 */ + this.addRule(CategoryAuditMessage.class, PRODUCT_CATEGORY_AUDIT, this::categoryAudit); + /* 订单下单 */ + this.addRule(OrderIdMessage.class, ORDER_NEW, this::orderNew); + /* 订单取消 */ + this.addRule(OrderCancelMessage.class, ORDER_CANCEL, this::orderCancel); + /* 订单支付成功 */ + this.addRule(OrderPayMessage.class, ORDER_PAY, this::orderPay); + /* 订单待发货 */ + this.addRule(OrderIdMessage.class, ORDER_WAIT_SHIPPING, this::orderWaitShipping); + /* 订单发货 */ + this.addRule(OrderDeliveryMessage.class, ORDER_DELIVER, this::orderDelivery); + /* 订单确认收货 */ + this.addRule(OrderConfirmMessage.class, ORDER_CONFIRM, this::orderConfirm); + /* 订单结算成功 */ + this.addRule(OrderSettleMessage.class, ORDER_SETTLE, this::orderSettle); + /* 订单其他信息更新 */ + this.addRule(OrderExtMessage.class, ORDER_EXT_INFO_UPDATE, this::orderExtInfoUpdate); + /* 订单状态更新 */ + this.addRule(OrderStatusMessage.class, ORDER_STATUS_UPDATE, this::orderStatusUpdate); + /* 售后单更新通知 */ + this.addRule(AfterSaleMessage.class, AFTER_SALE_UPDATE, this::afterSaleStatusUpdate); + /* 纠纷更新通知 */ + this.addRule(ComplaintMessage.class, COMPLAINT_NOTIFY, this::complaintNotify); + /* 优惠券领取通知 */ + this.addRule(CouponReceiveMessage.class, RECEIVE_COUPON, this::couponReceive); + /* 优惠券使用通知 */ + this.addRule(CouponActionMessage.class, CREATE_COUPON, this::couponCreate); + /* 优惠券删除通知 */ + this.addRule(CouponActionMessage.class, DELETE_COUPON, this::couponDelete); + /* 优惠券过期通知 */ + this.addRule(CouponActionMessage.class, EXPIRE_COUPON, this::couponExpire); + /* 更新优惠券信息通知 */ + this.addRule(CouponActionMessage.class, UPDATE_COUPON_INFO, this::couponUpdate); + /* 更新优惠券信息通知 */ + this.addRule(CouponActionMessage.class, INVALID_COUPON, this::couponInvalid); + /* 用户优惠券过期通知 */ + this.addRule(UserCouponExpireMessage.class, USER_COUPON_EXPIRE, this::userCouponExpire); + /* 用户优惠券过期通知 */ + this.addRule(UserCouponExpireMessage.class, USER_COUPON_UNUSE, this::userCouponUnuse); + /* 优惠券返还通知 */ + this.addRule(UserCouponExpireMessage.class, USER_COUPON_USE, this::userCouponUse); + /* 发放团购优惠成功通知 */ + this.addRule(VoucherMessage.class, VOUCHER_SEND_SUCC, this::voucherSendSucc); + /* 结算账户变更回调 */ + this.addRule(AccountNotifyMessage.class, ACCOUNT_NOTIFY, this::accountNotify); + /* 提现回调 */ + this.addRule(WithdrawNotifyMessage.class, WITHDRAW_NOTIFY, this::withdrawNotify); + /* 提现二维码回调 */ + this.addRule(QrNotifyMessage.class, QRCODE_STATUS, this::qrNotify); + /* 团长 */ + this.addRule(SupplierItemMessage.class, SUPPLIER_ITEM_UPDATE, this::supplierItemUpdate); + + /* 用户加入会员 */ + this.addRule(UserInfoMessage.class, USER_VIP_JOIN, false, this::vipJoin); + /* 用户注销会员 */ + this.addRule(UserInfoMessage.class, USER_VIP_CLOSE,false, this::vipClose); + /* 用户等级信息更新 */ + this.addRule(UserInfoMessage.class, USER_VIP_GRADE_INFO_UPDATE, false, this::vipGradeUpdate); + /* 用户积分更新 */ + this.addRule(UserInfoMessage.class, USER_VIP_SCORE_UPDATE, false, this::vipScoreUpdate); + /* 用户积分兑换 */ + this.addRule(ExchangeInfoMessage.class, USER_VIP_SCORE_EXCHANGE, false, this::vipScoreExchange); + + /* 分享员变更 */ + this.addRule(SharerChangeMessage.class,SHARER_CHANGE,false,this::sharerChange); + + /* 小店注销 */ + this.addRule(CloseStoreMessage.class, CLOSE_STORE, this::closeStore); + /* 小店修改名称 */ + this.addRule(NicknameUpdateMessage.class, SET_SHOP_NICKNAME, this::updateNickname); + } + + /** + * 添加一条规则进入路由器 + * + * @param clazz 消息类型 + * @param event 事件类型 + * @param consumer 处理器 + * @param 消息类型 + */ + protected void addRule(Class clazz, String event, Boolean async, + HandlerConsumer, WxSessionManager> consumer) { + WxChannelMessageRouterRule rule = new WxChannelMessageRouterRule<>(); + rule.setMessageClass(clazz).setEvent(event).setAsync(async); + rule.getHandlers().add((message, content, appId, context, sessionManager) -> { + consumer.accept(message, content, appId, context, sessionManager); + return "success"; + }); + rule.setNext(true); + this.addRule(rule); + } + + protected void addRule(Class clazz, String event, + HandlerConsumer, WxSessionManager> consumer) { + this.addRule(clazz, event, true, consumer); + } + + @Override + public void addRule(WxChannelMessageRouterRule rule) { + router.getRules().add(rule); + } + + @Override + public Object route(WxChannelMessage message, String content, String appId, final WxChannelService service) { + return router.route(message, content, appId, service); + } + + + @Override + public void orderNew(OrderIdMessage message, String content, String appId, + Map context, WxSessionManager sessionManager) { + log.info("订单下单:{}", JsonUtils.encode(message)); + } + + @Override + public void orderCancel(OrderCancelMessage message, String content, String appId, + Map context, WxSessionManager sessionManager) { + log.info("订单取消:{}", JsonUtils.encode(message)); + } + + @Override + public void orderPay(OrderPayMessage message, String content, String appId, + Map context, WxSessionManager sessionManager) { + log.info("订单支付成功:{}", JsonUtils.encode(message)); + } + + @Override + public void orderWaitShipping(OrderIdMessage message, String content, String appId, + Map context, WxSessionManager sessionManager) { + log.info("订单待发货:{}", JsonUtils.encode(message)); + } + + @Override + public void orderDelivery(OrderDeliveryMessage message, String content, String appId, + Map context, WxSessionManager sessionManager) { + log.info("订单发货:{}", JsonUtils.encode(message)); + } + + @Override + public void orderConfirm(OrderConfirmMessage message, String content, String appId, + Map context, WxSessionManager sessionManager) { + log.info("订单确认收货:{}", JsonUtils.encode(message)); + } + + @Override + public void orderSettle(OrderSettleMessage message, String content, String appId, + Map context, WxSessionManager sessionManager) { + log.info("订单结算:{}", JsonUtils.encode(message)); + } + + @Override + public void orderExtInfoUpdate(OrderExtMessage message, String content, String appId, + Map context, WxSessionManager sessionManager) { + log.info("订单其他信息更新:{}", JsonUtils.encode(message)); + } + + @Override + public void orderStatusUpdate(OrderStatusMessage message, String content, String appId, + Map context, WxSessionManager sessionManager) { + log.info("订单状态更新:{}", JsonUtils.encode(message)); + } + + @Override + public void spuAudit(SpuAuditMessage message, String content, String appId, + Map context, WxSessionManager sessionManager) { + log.info("商品审核:{}", JsonUtils.encode(message)); + } + + @Override + public void spuStatusUpdate(SpuAuditMessage message, String content, String appId, + Map context, WxSessionManager sessionManager) { + log.info("商品状态更新:{}", JsonUtils.encode(message)); + } + + @Override + public void spuUpdate(SpuAuditMessage message, String content, String appId, + Map context, WxSessionManager sessionManager) { + log.info("商品更新:{}", JsonUtils.encode(message)); + } + + @Override + public void stockNoEnough(SpuStockMessage message, String content, String appId, + Map context, WxSessionManager sessionManager) { + log.info("商品库存不足:{}", JsonUtils.encode(message)); + } + + @Override + public void categoryAudit(CategoryAuditMessage message, String content, String appId, + Map context, WxSessionManager sessionManager) { + log.info("分类审核:{}", JsonUtils.encode(message)); + } + + @Override + public void brandUpdate(BrandMessage message, String content, String appId, + Map context, WxSessionManager sessionManager) { + log.info("品牌更新:{}", JsonUtils.encode(message)); + } + + @Override + public void afterSaleStatusUpdate(AfterSaleMessage message, String content, String appId, + Map context, WxSessionManager sessionManager) { + log.info("售后状态更新:{}", JsonUtils.encode(message)); + } + + @Override + public void complaintNotify(ComplaintMessage message, String content, String appId, + Map context, WxSessionManager sessionManager) { + log.info("投诉通知:{}", JsonUtils.encode(message)); + } + + @Override + public void couponReceive(CouponReceiveMessage message, String content, String appId, + Map context, WxSessionManager sessionManager) { + log.info("优惠券领取:{}", JsonUtils.encode(message)); + } + + @Override + public void couponCreate(CouponActionMessage message, String content, String appId, + Map context, WxSessionManager sessionManager) { + log.info("优惠券创建:{}", JsonUtils.encode(message)); + } + + @Override + public void couponDelete(CouponActionMessage message, String content, String appId, + Map context, WxSessionManager sessionManager) { + log.info("优惠券删除:{}", JsonUtils.encode(message)); + } + + @Override + public void couponExpire(CouponActionMessage message, String content, String appId, + Map context, WxSessionManager sessionManager) { + log.info("优惠券过期:{}", JsonUtils.encode(message)); + } + + @Override + public void couponUpdate(CouponActionMessage message, String content, String appId, + Map context, WxSessionManager sessionManager) { + log.info("优惠券更新:{}", JsonUtils.encode(message)); + } + + @Override + public void couponInvalid(CouponActionMessage message, String content, String appId, + Map context, WxSessionManager sessionManager) { + log.info("优惠券失效:{}", JsonUtils.encode(message)); + } + + @Override + public void userCouponExpire(UserCouponExpireMessage message, String content, String appId, + Map context, WxSessionManager sessionManager) { + log.info("用户优惠券过期:{}", JsonUtils.encode(message)); + } + + @Override + public void userCouponUse(UserCouponExpireMessage message, String content, String appId, + Map context, WxSessionManager sessionManager) { + log.info("用户优惠券使用:{}", JsonUtils.encode(message)); + } + + @Override + public void userCouponUnuse(UserCouponExpireMessage message, String content, String appId, + Map context, WxSessionManager sessionManager) { + log.info("用户优惠券取消使用:{}", JsonUtils.encode(message)); + } + + @Override + public void voucherSendSucc(VoucherMessage message, String content, String appId, + Map context, WxSessionManager sessionManager) { + log.info("发放团购优惠成功:{}", JsonUtils.encode(message)); + } + + @Override + public void accountNotify(AccountNotifyMessage message, String content, String appId, + Map context, WxSessionManager sessionManager) { + log.info("账户通知:{}", JsonUtils.encode(message)); + } + + @Override + public void withdrawNotify(WithdrawNotifyMessage message, String content, String appId, + Map context, WxSessionManager sessionManager) { + log.info("提现通知:{}", JsonUtils.encode(message)); + } + + @Override + public void qrNotify(QrNotifyMessage message, String content, String appId, + Map context, WxSessionManager sessionManager) { + log.info("二维码通知:{}", JsonUtils.encode(message)); + } + + @Override + public void supplierItemUpdate(SupplierItemMessage message, String content, String appId, + Map context, WxSessionManager sessionManager) { + log.info("供应商商品更新:{}", JsonUtils.encode(message)); + } + + @Override + public Object defaultMessageHandler(WxChannelMessage message, String content, String appId, + Map context, WxSessionManager sessionManager) { + log.info("默认消息处理:{}", JsonUtils.encode(message)); + return null; + } + + @Override + public void sharerChange(WxChannelMessage message, String content, String appId, Map context, WxSessionManager sessionManager) { + log.info("分享员变更:{}", JsonUtils.encode(message)); + } + + @Override + public void vipJoin(UserInfoMessage message, String content, String appId, + Map context, WxSessionManager sessionManager) { + log.info("用户加入会员:{}", JsonUtils.encode(message)); + } + + @Override + public void vipClose(UserInfoMessage message, String content, String appId, + Map context, WxSessionManager sessionManager) { + log.info("用户注销会员:{}", JsonUtils.encode(message)); + } + + @Override + public void vipGradeUpdate(UserInfoMessage message, String content, String appId, + Map context, WxSessionManager sessionManager) { + log.info("用户等级信息更新:{}", JsonUtils.encode(message)); + } + + @Override + public void vipScoreUpdate(UserInfoMessage message, String content, String appId, + Map context, WxSessionManager sessionManager) { + log.info("用户积分更新:{}", JsonUtils.encode(message)); + } + + @Override + public void vipScoreExchange(ExchangeInfoMessage message, String content, String appId, + Map context, WxSessionManager sessionManager) { + log.info("用户积分兑换:{}", JsonUtils.encode(message)); + } + + @Override + public void closeStore(CloseStoreMessage message, String content, String appId, + Map context, WxSessionManager sessionManager) { + log.info("小店注销:{}", JsonUtils.encode(message)); + } + + @Override + public void updateNickname(NicknameUpdateMessage message, String content, String appId, + Map context, WxSessionManager sessionManager) { + log.info("小店修改名称:{}", JsonUtils.encode(message)); + } +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/BaseWxChannelServiceImpl.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/BaseWxChannelServiceImpl.java new file mode 100644 index 0000000000..1a608e1f6a --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/BaseWxChannelServiceImpl.java @@ -0,0 +1,476 @@ +package me.chanjar.weixin.channel.api.impl; + + +import com.google.gson.JsonObject; +import lombok.extern.slf4j.Slf4j; +import me.chanjar.weixin.channel.api.*; +import me.chanjar.weixin.channel.config.WxChannelConfig; +import me.chanjar.weixin.channel.util.JsonUtils; +import me.chanjar.weixin.common.api.WxConsts; +import me.chanjar.weixin.common.bean.CommonUploadParam; +import me.chanjar.weixin.common.bean.ToJson; +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.executor.CommonUploadRequestExecutor; +import me.chanjar.weixin.common.util.DataUtils; +import me.chanjar.weixin.common.util.crypto.SHA1; +import me.chanjar.weixin.common.util.http.RequestExecutor; +import me.chanjar.weixin.common.util.http.RequestHttp; +import me.chanjar.weixin.common.util.http.SimpleGetRequestExecutor; +import me.chanjar.weixin.common.util.http.SimplePostRequestExecutor; +import org.apache.commons.lang3.StringUtils; + +import java.io.IOException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.Lock; + +/** + * @author Zeyes + * @see #doGetAccessTokenRequest + */ +@Slf4j +public abstract class BaseWxChannelServiceImpl implements WxChannelService, RequestHttp { + + private final WxChannelBasicService basicService = new WxChannelBasicServiceImpl(this); + private final WxChannelCategoryService categoryService = new WxChannelCategoryServiceImpl(this); + private final WxChannelBrandService brandService = new WxChannelBrandServiceImpl(this); + private final WxChannelProductService productService = new WxChannelProductServiceImpl(this); + private final WxChannelWarehouseService warehouseService = new WxChannelWarehouseServiceImpl(this); + private final WxChannelOrderService orderService = new WxChannelOrderServiceImpl(this); + private final WxChannelAfterSaleService afterSaleService = new WxChannelAfterSaleServiceImpl(this); + private final WxChannelFreightTemplateService freightTemplateService = + new WxChannelFreightTemplateServiceImpl(this); + private final WxChannelAddressService addressService = new WxChannelAddressServiceImpl(this); + private final WxChannelCouponService couponService = new WxChannelCouponServiceImpl(this); + private final WxChannelSharerService sharerService = new WxChannelSharerServiceImpl(this); + private final WxChannelFundService fundService = new WxChannelFundServiceImpl(this); + private WxStoreHomePageService homePageService = null; + private WxStoreCooperationService cooperationService = null; + private WxChannelCompassShopService compassShopService = null; + private WxLeagueWindowService leagueWindowService = null; + private WxLeagueSupplierService leagueSupplierService = null; + private WxLeaguePromoterService leaguePromoterService = null; + private WxLeagueProductService leagueProductService = null; + private WxLeadComponentService leadComponentService = null; + private WxFinderLiveService finderLiveService = null; + private WxAssistantService assistantService = null; + private WxChannelVipService vipService = null; + private WxChannelCompassFinderService compassFinderService = null; + private WxChannelLiveDashboardService liveDashboardService = null; + + protected WxChannelConfig config; + private int retrySleepMillis = 1000; + private int maxRetryTimes = 5; + + @Override + public RequestHttp getRequestHttp() { + return this; + } + + @Override + 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()); + return false; + } + } + + @Override + public String getAccessToken() throws WxErrorException { + return getAccessToken(false); + } + + @Override + public String getAccessToken(boolean forceRefresh) throws WxErrorException { + if (!forceRefresh && !this.getConfig().isAccessTokenExpired()) { + return this.getConfig().getAccessToken(); + } + + Lock lock = this.getConfig().getAccessTokenLock(); + boolean locked = false; + try { + do { + locked = lock.tryLock(100, TimeUnit.MILLISECONDS); + if (!forceRefresh && !this.getConfig().isAccessTokenExpired()) { + return this.getConfig().getAccessToken(); + } + } while (!locked); + String response; + if (getConfig().isStableAccessToken()) { + response = doGetStableAccessTokenRequest(forceRefresh); + } else { + response = doGetAccessTokenRequest(); + } + return extractAccessToken(response); + } catch (IOException | InterruptedException e) { + throw new WxRuntimeException(e); + } finally { + if (locked) { + lock.unlock(); + } + } + } + + /** + * 通过网络请求获取AccessToken + * + * @return AccessToken + * @throws IOException IOException + */ + protected abstract String doGetAccessTokenRequest() throws IOException; + + /** + * 通过网络请求获取稳定版AccessToken + * + * @return Stable AccessToken + * @throws IOException IOException + */ + protected abstract String doGetStableAccessTokenRequest(boolean forceRefresh) throws IOException; + + @Override + public String get(String url, String queryParam) throws WxErrorException { + return execute(SimpleGetRequestExecutor.create(this), url, queryParam); + } + + @Override + public String post(String url, String postData) throws WxErrorException { + return execute(SimplePostRequestExecutor.create(this), url, postData); + } + + @Override + public String post(String url, Object obj) throws WxErrorException { + // 此处用JsonUtils.encode, 不用Gson + return this.execute(SimplePostRequestExecutor.create(this), url, JsonUtils.encode(obj)); + } + + @Override + public String post(String url, ToJson obj) throws WxErrorException { + return this.post(url, obj.toJson()); + } + + @Override + public String upload(String url, CommonUploadParam param) throws WxErrorException { + RequestExecutor executor = CommonUploadRequestExecutor.create(getRequestHttp()); + return this.execute(executor, url, param); + } + + @Override + public String post(String url, JsonObject jsonObject) throws WxErrorException { + return this.post(url, jsonObject.toString()); + } + + /** + * 向微信端发送请求,在这里执行的策略是当发生access_token过期时才去刷新,然后重新执行请求,而不是全局定时请求 + */ + @Override + public T execute(RequestExecutor executor, String uri, E data) throws WxErrorException { + return execute0(executor, uri, data, true); + } + + @Override + public T executeWithoutLog(RequestExecutor executor, String uri, E data) throws WxErrorException { + return execute0(executor, uri, data, false); + } + + protected T execute0(RequestExecutor executor, String uri, E data, boolean printResult) + throws WxErrorException { + int retryTimes = 0; + do { + try { + return this.executeInternal(executor, uri, data, false, printResult); + } catch (WxErrorException e) { + if (retryTimes + 1 > this.maxRetryTimes) { + log.warn("重试达到最大次数【{}】", maxRetryTimes); + //最后一次重试失败后,直接抛出异常,不再等待 + throw new WxErrorException(WxError.builder() + .errorCode(e.getError().getErrorCode()) + .errorMsg("微信服务端异常,超出重试次数!") + .build()); + } + + WxError error = e.getError(); + // -1 系统繁忙, 1000ms后重试 + if (error.getErrorCode() == -1) { + int sleepMillis = this.retrySleepMillis * (1 << retryTimes); + try { + log.warn("微信系统繁忙,{} ms 后重试(第{}次)", sleepMillis, retryTimes + 1); + Thread.sleep(sleepMillis); + } catch (InterruptedException e1) { + Thread.currentThread().interrupt(); + } + } else { + throw e; + } + } + } while (retryTimes++ < this.maxRetryTimes); + + log.warn("重试达到最大次数【{}】", this.maxRetryTimes); + throw new WxRuntimeException("微信服务端异常,超出重试次数"); + } + + protected T executeInternal(RequestExecutor executor, String uri, E data, boolean doNotAutoRefreshToken, + boolean printResult) throws WxErrorException { + E dataForLog = DataUtils.handleDataWithSecret(data); + + if (uri.contains("access_token=")) { + throw new IllegalArgumentException("uri参数中不允许有access_token: " + uri); + } + String accessToken = getAccessToken(false); + + WxChannelConfig config = this.getConfig(); + if (StringUtils.isNotEmpty(config.getApiHostUrl())) { + uri = uri.replace("https://api.weixin.qq.com", config.getApiHostUrl()); + } + + String uriWithAccessToken = uri + (uri.contains("?") ? "&" : "?") + "access_token=" + accessToken; + + try { + T result = executor.execute(uriWithAccessToken, data, WxType.Channel); + log.debug("\n【请求地址】: {}\n【请求参数】:{}\n【响应数据】:{}", uriWithAccessToken, dataForLog, + printResult ? result : "..."); + return result; + } catch (WxErrorException e) { + WxError error = e.getError(); + if (WxConsts.ACCESS_TOKEN_ERROR_CODES.contains(error.getErrorCode())) { + // 强制设置WxMaConfig的access token过期了,这样在下一次请求里就会刷新access token + Lock lock = config.getAccessTokenLock(); + lock.lock(); + try { + if (StringUtils.equals(config.getAccessToken(), accessToken)) { + config.expireAccessToken(); + } + } catch (Exception ex) { + config.expireAccessToken(); + } finally { + lock.unlock(); + } + if (config.autoRefreshToken() && !doNotAutoRefreshToken) { + log.warn("即将重新获取新的access_token,错误代码:{},错误信息:{}", error.getErrorCode(), error.getErrorMsg()); + //下一次不再自动重试 + //当小程序误调用第三方平台专属接口时,第三方无法使用小程序的access token,如果可以继续自动获取token会导致无限循环重试,直到栈溢出 + return this.executeInternal(executor, uri, data, true, printResult); + } + } + + if (error.getErrorCode() != 0) { + log.warn("\n【请求地址】: {}\n【请求参数】:{}\n【错误信息】:{}", uriWithAccessToken, dataForLog, error); + throw new WxErrorException(error, e); + } + return null; + } catch (IOException e) { + log.warn("\n【请求地址】: {}\n【请求参数】:{}\n【异常信息】:{}", uriWithAccessToken, dataForLog, e.getMessage()); + throw new WxRuntimeException(e); + } + } + + /** + * 设置当前的AccessToken + * + * @param resultContent 响应内容 + * @return access token + * @throws WxErrorException 异常 + */ + protected String extractAccessToken(String resultContent) throws WxErrorException { + log.debug("access-token response: {}", resultContent); + WxChannelConfig config = this.getConfig(); + WxError error = WxError.fromJson(resultContent, WxType.Channel); + if (error.getErrorCode() != 0) { + throw new WxErrorException(error); + } + WxAccessToken accessToken = WxAccessToken.fromJson(resultContent); + config.updateAccessToken(accessToken.getAccessToken(), accessToken.getExpiresIn()); + return accessToken.getAccessToken(); + } + + @Override + public WxChannelConfig getConfig() { + return config; + } + + @Override + public void setConfig(WxChannelConfig config) { + this.config = config; + initHttp(); + } + + @Override + public void setRetrySleepMillis(int retrySleepMillis) { + this.retrySleepMillis = retrySleepMillis; + } + + @Override + public void setMaxRetryTimes(int maxRetryTimes) { + this.maxRetryTimes = maxRetryTimes; + } + + @Override + public WxChannelBasicService getBasicService() { + return basicService; + } + + @Override + public WxChannelCategoryService getCategoryService() { + return categoryService; + } + + @Override + public WxChannelBrandService getBrandService() { + return brandService; + } + + @Override + public WxChannelProductService getProductService() { + return productService; + } + + @Override + public WxChannelWarehouseService getWarehouseService() { + return warehouseService; + } + + @Override + public WxChannelOrderService getOrderService() { + return orderService; + } + + @Override + public WxChannelAfterSaleService getAfterSaleService() { + return afterSaleService; + } + + @Override + public WxChannelFreightTemplateService getFreightTemplateService() { + return freightTemplateService; + } + + @Override + public WxChannelAddressService getAddressService() { + return addressService; + } + + @Override + public WxChannelCouponService getCouponService() { + return couponService; + } + + @Override + public WxChannelSharerService getSharerService() { + return sharerService; + } + + @Override + public WxChannelFundService getFundService() { + return fundService; + } + + @Override + public synchronized WxStoreHomePageService getHomePageService() { + if (homePageService == null) { + homePageService = new WxStoreHomePageServiceImpl(this); + } + return homePageService; + } + + @Override + public synchronized WxStoreCooperationService getCooperationService() { + if (cooperationService == null) { + cooperationService = new WxStoreCooperationServiceImpl(this); + } + return cooperationService; + } + + @Override + public synchronized WxChannelCompassShopService getCompassShopService() { + if (compassShopService == null) { + compassShopService = new WxChannelCompassShopServiceImpl(this); + } + return compassShopService; + } + + @Override + public synchronized WxLeagueWindowService getLeagueWindowService() { + if (leagueWindowService == null) { + leagueWindowService = new WxLeagueWindowServiceImpl(this); + } + return leagueWindowService; + } + + @Override + public synchronized WxLeagueSupplierService getLeagueSupplierService() { + if (leagueSupplierService == null) { + leagueSupplierService = new WxLeagueSupplierServiceImpl(this); + } + return leagueSupplierService; + } + + @Override + public synchronized WxLeaguePromoterService getLeaguePromoterService() { + if (leaguePromoterService == null) { + leaguePromoterService = new WxLeaguePromoterServiceImpl(this); + } + return leaguePromoterService; + } + + @Override + public synchronized WxLeagueProductService getLeagueProductService() { + if (leagueProductService == null) { + leagueProductService = new WxLeagueProductServiceImpl(this); + } + return leagueProductService; + } + + @Override + public synchronized WxLeadComponentService getLeadComponentService() { + if (leadComponentService == null) { + leadComponentService = new WxLeadComponentServiceImpl(this); + } + return leadComponentService; + } + + @Override + public synchronized WxFinderLiveService getFinderLiveService() { + if (finderLiveService == null) { + finderLiveService = new WxFinderLiveServiceImpl(this); + } + return finderLiveService; + } + + @Override + public synchronized WxAssistantService getAssistantService() { + if (assistantService == null) { + assistantService = new WxAssistantServiceImpl(this) { + }; + } + return assistantService; + } + + @Override + public synchronized WxChannelVipService getVipService() { + if (vipService == null) { + vipService = new WxChannelVipServiceImpl(this); + } + return vipService; + } + + @Override + public synchronized WxChannelCompassFinderService getCompassFinderService() { + if (compassFinderService == null) { + compassFinderService = new WxChannelCompassFinderServiceImpl(this); + } + return compassFinderService; + } + + @Override + public synchronized WxChannelLiveDashboardService getLiveDashboardService() { + if (liveDashboardService == null) { + liveDashboardService = new WxChannelLiveDashboardServiceImpl(this); + } + return liveDashboardService; + } + +} 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 new file mode 100644 index 0000000000..55be5abcca --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxAssistantServiceImpl.java @@ -0,0 +1,55 @@ +package me.chanjar.weixin.channel.api.impl; + + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import me.chanjar.weixin.channel.api.WxAssistantService; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; +import me.chanjar.weixin.channel.bean.window.request.AddWindowProductRequest; +import me.chanjar.weixin.channel.bean.window.request.GetWindowProductListRequest; +import me.chanjar.weixin.channel.bean.window.request.WindowProductRequest; +import me.chanjar.weixin.channel.bean.window.response.GetWindowProductListResponse; +import me.chanjar.weixin.channel.bean.window.response.GetWindowProductResponse; +import me.chanjar.weixin.channel.util.ResponseUtils; +import me.chanjar.weixin.common.error.WxErrorException; + +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Assistant.ADD_WINDOW_PRODUCT_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Assistant.GET_WINDOW_PRODUCT_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Assistant.LIST_WINDOW_PRODUCT_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Assistant.OFF_WINDOW_PRODUCT_URL; + +/** + * 视频号助手 橱窗管理服务 + * + * @author imyzt + */ +@RequiredArgsConstructor +@Slf4j +public class WxAssistantServiceImpl implements WxAssistantService { + + /** 微信商店服务 */ + private final BaseWxChannelServiceImpl shopService; + @Override + public WxChannelBaseResponse addWindowProduct(AddWindowProductRequest req) throws WxErrorException { + String resJson = shopService.post(ADD_WINDOW_PRODUCT_URL, "{}"); + return ResponseUtils.decode(resJson, WxChannelBaseResponse.class); + } + + @Override + public GetWindowProductResponse getWindowProduct(WindowProductRequest req) throws WxErrorException { + String resJson = shopService.post(GET_WINDOW_PRODUCT_URL, "{}"); + return ResponseUtils.decode(resJson, GetWindowProductResponse.class); + } + + @Override + public GetWindowProductListResponse getWindowProductList(GetWindowProductListRequest req) throws WxErrorException { + String resJson = shopService.post(LIST_WINDOW_PRODUCT_URL, "{}"); + return ResponseUtils.decode(resJson, GetWindowProductListResponse.class); + } + + @Override + public WxChannelBaseResponse offWindowProduct(WindowProductRequest req) throws WxErrorException { + String resJson = shopService.post(OFF_WINDOW_PRODUCT_URL, "{}"); + return ResponseUtils.decode(resJson, WxChannelBaseResponse.class); + } +} 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 new file mode 100644 index 0000000000..20cf128559 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelAddressServiceImpl.java @@ -0,0 +1,72 @@ +package me.chanjar.weixin.channel.api.impl; + + +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Address.ADD_ADDRESS_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Address.DELETE_ADDRESS_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Address.GET_ADDRESS_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Address.LIST_ADDRESS_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Address.UPDATE_ADDRESS_URL; + +import lombok.extern.slf4j.Slf4j; +import me.chanjar.weixin.channel.api.WxChannelAddressService; +import me.chanjar.weixin.channel.bean.address.AddressAddParam; +import me.chanjar.weixin.channel.bean.address.AddressDetail; +import me.chanjar.weixin.channel.bean.address.AddressIdParam; +import me.chanjar.weixin.channel.bean.address.AddressIdResponse; +import me.chanjar.weixin.channel.bean.address.AddressInfoResponse; +import me.chanjar.weixin.channel.bean.address.AddressListParam; +import me.chanjar.weixin.channel.bean.address.AddressListResponse; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; +import me.chanjar.weixin.channel.util.ResponseUtils; +import me.chanjar.weixin.common.error.WxErrorException; + +/** + * 视频号小店 地址管理服务实现 + * + * @author Zeyes + */ +@Slf4j +public class WxChannelAddressServiceImpl implements WxChannelAddressService { + + /** 微信商店服务 */ + private final BaseWxChannelServiceImpl shopService; + + public WxChannelAddressServiceImpl(BaseWxChannelServiceImpl shopService) { + this.shopService = shopService; + } + + @Override + public AddressListResponse listAddress(Integer offset, Integer limit) throws WxErrorException { + AddressListParam param = new AddressListParam(offset, limit); + String resJson = shopService.post(LIST_ADDRESS_URL, param); + return ResponseUtils.decode(resJson, AddressListResponse.class); + } + + @Override + public AddressInfoResponse getAddress(String addressId) throws WxErrorException { + AddressIdParam param = new AddressIdParam(addressId); + String resJson = shopService.post(GET_ADDRESS_URL, param); + return ResponseUtils.decode(resJson, AddressInfoResponse.class); + } + + @Override + public AddressIdResponse addAddress(AddressDetail addressDetail) throws WxErrorException { + AddressAddParam param = new AddressAddParam(addressDetail); + String resJson = shopService.post(ADD_ADDRESS_URL, param); + return ResponseUtils.decode(resJson, AddressIdResponse.class); + } + + @Override + public WxChannelBaseResponse updateAddress(AddressDetail addressDetail) throws WxErrorException { + AddressAddParam param = new AddressAddParam(addressDetail); + String resJson = shopService.post(UPDATE_ADDRESS_URL, param); + return ResponseUtils.decode(resJson, WxChannelBaseResponse.class); + } + + @Override + public WxChannelBaseResponse deleteAddress(String addressId) throws WxErrorException { + AddressIdParam param = new AddressIdParam(addressId); + String resJson = shopService.post(DELETE_ADDRESS_URL, param); + return ResponseUtils.decode(resJson, WxChannelBaseResponse.class); + } +} 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 new file mode 100644 index 0000000000..92f865444b --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelAfterSaleServiceImpl.java @@ -0,0 +1,133 @@ +package me.chanjar.weixin.channel.api.impl; + +import lombok.extern.slf4j.Slf4j; +import me.chanjar.weixin.channel.api.WxChannelAfterSaleService; +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.channel.bean.complaint.ComplaintParam; +import me.chanjar.weixin.channel.util.ResponseUtils; +import me.chanjar.weixin.common.error.WxErrorException; + +import java.util.List; + +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.AfterSale.*; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Complaint.*; + +/** + * 视频号小店 售后服务实现 + * + * @author Zeyes + */ +@Slf4j +public class WxChannelAfterSaleServiceImpl implements WxChannelAfterSaleService { + + /** + * 微信商店服务 + */ + private final 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, 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); + } + + @Override + public AfterSaleInfoResponse get(String afterSaleOrderId) throws WxErrorException { + AfterSaleIdParam param = new AfterSaleIdParam(afterSaleOrderId); + String resJson = shopService.post(AFTER_SALE_GET_URL, param); + return ResponseUtils.decode(resJson, AfterSaleInfoResponse.class); + } + + @Override + public WxChannelBaseResponse accept(String afterSaleOrderId, String addressId, Integer acceptType) throws WxErrorException { + AfterSaleAcceptParam param = new AfterSaleAcceptParam(afterSaleOrderId, addressId, acceptType); + String resJson = shopService.post(AFTER_SALE_ACCEPT_URL, param); + return ResponseUtils.decode(resJson, WxChannelBaseResponse.class); + } + + @Override + public WxChannelBaseResponse reject(String afterSaleOrderId, String rejectReason, Integer rejectReasonType) throws WxErrorException { + AfterSaleRejectParam param = new AfterSaleRejectParam(afterSaleOrderId, rejectReason, rejectReasonType); + String resJson = shopService.post(AFTER_SALE_REJECT_URL, param); + return ResponseUtils.decode(resJson, WxChannelBaseResponse.class); + } + + @Override + public WxChannelBaseResponse uploadRefundEvidence(String afterSaleOrderId, String desc, List certificates) + throws WxErrorException { + RefundEvidenceParam param = new RefundEvidenceParam(afterSaleOrderId, desc, certificates); + String resJson = shopService.post(AFTER_SALE_UPLOAD_URL, param); + return ResponseUtils.decode(resJson, WxChannelBaseResponse.class); + } + + @Override + public WxChannelBaseResponse addComplaintMaterial(String complaintId, String content, List mediaIds) + throws WxErrorException { + ComplaintParam param = new ComplaintParam(complaintId, content, mediaIds); + String resJson = shopService.post(ADD_COMPLAINT_MATERIAL_URL, param); + return ResponseUtils.decode(resJson, WxChannelBaseResponse.class); + + } + + @Override + public WxChannelBaseResponse addComplaintEvidence(String complaintId, String content, List mediaIds) + throws WxErrorException { + ComplaintParam param = new ComplaintParam(complaintId, content, mediaIds); + String resJson = shopService.post(ADD_COMPLAINT_PROOF_URL, param); + return ResponseUtils.decode(resJson, WxChannelBaseResponse.class); + } + + @Override + public ComplaintOrderResponse getComplaint(String complaintId) throws WxErrorException { + String reqJson = "{\"complaint_id\":\"" + complaintId + "\"}"; + String resJson = shopService.post(GET_COMPLAINT_ORDER_URL, reqJson); + return ResponseUtils.decode(resJson, ComplaintOrderResponse.class); + } + + @Override + public AfterSaleReasonResponse getAllReason() throws WxErrorException { + String resJson = shopService.post(AFTER_SALE_REASON_GET_URL, "{}"); + return ResponseUtils.decode(resJson, AfterSaleReasonResponse.class); + } + + @Override + 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 new file mode 100644 index 0000000000..6eb699da23 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelBasicServiceImpl.java @@ -0,0 +1,96 @@ +package me.chanjar.weixin.channel.api.impl; + +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Basics.GET_ADDRESS_CODE; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Basics.GET_IMG_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Basics.GET_SHOP_INFO; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Basics.IMG_UPLOAD_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Basics.UPLOAD_QUALIFICATION_FILE; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import lombok.extern.slf4j.Slf4j; +import me.chanjar.weixin.channel.api.WxChannelBasicService; +import me.chanjar.weixin.channel.bean.address.AddressCodeResponse; +import me.chanjar.weixin.channel.bean.image.ChannelImageInfo; +import me.chanjar.weixin.channel.bean.image.ChannelImageResponse; +import me.chanjar.weixin.channel.bean.image.QualificationFileResponse; +import me.chanjar.weixin.channel.bean.image.UploadImageResponse; +import me.chanjar.weixin.channel.bean.shop.ShopInfoResponse; +import me.chanjar.weixin.channel.executor.ChannelFileUploadRequestExecutor; +import me.chanjar.weixin.channel.executor.ChannelMediaDownloadRequestExecutor; +import me.chanjar.weixin.channel.util.ResponseUtils; +import me.chanjar.weixin.common.error.WxError; +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.common.util.http.RequestExecutor; + +/** + * @author Zeyes + */ +@Slf4j +public class WxChannelBasicServiceImpl implements WxChannelBasicService { + + /** 微信商店服务 */ + private final BaseWxChannelServiceImpl shopService; + + public WxChannelBasicServiceImpl(BaseWxChannelServiceImpl shopService) { + this.shopService = shopService; + } + + @Override + public ShopInfoResponse getShopInfo() throws WxErrorException { + String resJson = shopService.get(GET_SHOP_INFO, null); + return ResponseUtils.decode(resJson, ShopInfoResponse.class); + } + + @Override + public ChannelImageInfo uploadImg(int respType, String imgUrl) throws WxErrorException { + String url = IMG_UPLOAD_URL + "?upload_type=1&resp_type=" + respType; + String reqJson = "{\"img_url\":\"" + imgUrl + "\"}"; + String resJson = shopService.post(url, reqJson); + UploadImageResponse response = ResponseUtils.decode(resJson, UploadImageResponse.class); + return response.getImgInfo(); + } + + @Override + 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 = shopService.execute(executor, url, file); + UploadImageResponse response = ResponseUtils.decode(resJson, UploadImageResponse.class); + return response.getImgInfo(); + } + + @Override + public QualificationFileResponse uploadQualificationFile(File file) throws WxErrorException { + RequestExecutor executor = ChannelFileUploadRequestExecutor.create(shopService); + 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; + try { + String url = GET_IMG_URL + "?media_id=" + mediaId; + RequestExecutor executor = ChannelMediaDownloadRequestExecutor.create(shopService, + Files.createTempDirectory("wxjava-channel-" + appId).toFile()); + rs = shopService.execute(executor, url, null); + } catch (IOException e) { + throw new WxErrorException(WxError.builder().errorMsg(e.getMessage()).build(), e); + } + if (rs == null) { + rs = ResponseUtils.internalError(ChannelImageResponse.class); + } + return rs; + } + + @Override + public AddressCodeResponse getAddressCode(Integer code) throws WxErrorException { + String reqJson = "{\"addr_code\": " + code + "}"; + String resJson = shopService.post(GET_ADDRESS_CODE, reqJson); + return ResponseUtils.decode(resJson, AddressCodeResponse.class); + } + +} 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 new file mode 100644 index 0000000000..c6c476b116 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelBrandServiceImpl.java @@ -0,0 +1,98 @@ +package me.chanjar.weixin.channel.api.impl; + + +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Brand.ADD_BRAND_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Brand.ALL_BRAND_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Brand.CANCEL_BRAND_AUDIT_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Brand.DELETE_BRAND_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Brand.GET_BRAND_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Brand.LIST_BRAND_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Brand.LIST_BRAND_VALID_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Brand.UPDATE_BRAND_URL; + +import lombok.extern.slf4j.Slf4j; +import me.chanjar.weixin.channel.api.WxChannelBrandService; +import me.chanjar.weixin.channel.bean.audit.AuditApplyResponse; +import me.chanjar.weixin.channel.bean.base.StreamPageParam; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; +import me.chanjar.weixin.channel.bean.brand.Brand; +import me.chanjar.weixin.channel.bean.brand.BrandApplyListResponse; +import me.chanjar.weixin.channel.bean.brand.BrandInfoResponse; +import me.chanjar.weixin.channel.bean.brand.BrandListResponse; +import me.chanjar.weixin.channel.bean.brand.BrandParam; +import me.chanjar.weixin.channel.bean.brand.BrandSearchParam; +import me.chanjar.weixin.channel.util.ResponseUtils; +import me.chanjar.weixin.common.error.WxErrorException; + +/** + * 视频号小店 品牌服务实现 + * + * @author Zeyes + */ +@Slf4j +public class WxChannelBrandServiceImpl implements WxChannelBrandService { + + /** 微信商店服务 */ + private final BaseWxChannelServiceImpl shopService; + + public WxChannelBrandServiceImpl(BaseWxChannelServiceImpl shopService) { + this.shopService = shopService; + } + + @Override + public BrandListResponse listAllBrand(Integer pageSize, String nextKey) throws WxErrorException { + StreamPageParam param = new StreamPageParam(pageSize, nextKey); + String resJson = shopService.post(ALL_BRAND_URL, param); + return ResponseUtils.decode(resJson, BrandListResponse.class); + } + + @Override + public AuditApplyResponse addBrandApply(Brand brand) throws WxErrorException { + BrandParam param = new BrandParam(brand); + String resJson = shopService.post(ADD_BRAND_URL, param); + return ResponseUtils.decode(resJson, AuditApplyResponse.class); + } + + @Override + public AuditApplyResponse updateBrandApply(Brand brand) throws WxErrorException { + BrandParam param = new BrandParam(brand); + String resJson = shopService.post(UPDATE_BRAND_URL, param); + return ResponseUtils.decode(resJson, AuditApplyResponse.class); + } + + @Override + public WxChannelBaseResponse cancelBrandApply(String brandId, String auditId) throws WxErrorException { + String reqJson = "{\"brand_id\":\"" + brandId + "\",\"audit_id\":\"" + auditId + "\"}"; + String resJson = shopService.post(CANCEL_BRAND_AUDIT_URL, reqJson); + return ResponseUtils.decode(resJson, WxChannelBaseResponse.class); + } + + @Override + public WxChannelBaseResponse deleteBrandApply(String brandId) throws WxErrorException { + String reqJson = "{\"brand_id\":\"" + brandId + "\"}"; + String resJson = shopService.post(DELETE_BRAND_URL, reqJson); + return ResponseUtils.decode(resJson, WxChannelBaseResponse.class); + } + + @Override + public BrandInfoResponse getBrandApply(String brandId) throws WxErrorException { + String reqJson = "{\"brand_id\":\"" + brandId + "\"}"; + String resJson = shopService.post(GET_BRAND_URL, reqJson); + return ResponseUtils.decode(resJson, BrandInfoResponse.class); + } + + @Override + public BrandApplyListResponse listBrandApply(Integer pageSize, String nextKey, Integer status) + throws WxErrorException { + BrandSearchParam param = new BrandSearchParam(pageSize, nextKey, status); + String resJson = shopService.post(LIST_BRAND_URL, param); + return ResponseUtils.decode(resJson, BrandApplyListResponse.class); + } + + @Override + public BrandApplyListResponse listValidBrandApply(Integer pageSize, String nextKey) throws WxErrorException { + StreamPageParam param = new StreamPageParam(pageSize, nextKey); + String resJson = shopService.post(LIST_BRAND_VALID_URL, param); + return ResponseUtils.decode(resJson, BrandApplyListResponse.class); + } +} 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 new file mode 100644 index 0000000000..7070ab9298 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelCategoryServiceImpl.java @@ -0,0 +1,139 @@ +package me.chanjar.weixin.channel.api.impl; + +import java.util.Collections; +import java.util.List; +import lombok.extern.slf4j.Slf4j; +import me.chanjar.weixin.channel.api.WxChannelCategoryService; +import me.chanjar.weixin.channel.bean.audit.AuditApplyResponse; +import me.chanjar.weixin.channel.bean.audit.AuditResponse; +import me.chanjar.weixin.channel.bean.audit.CategoryAuditInfo; +import me.chanjar.weixin.channel.bean.audit.CategoryAuditRequest; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; +import me.chanjar.weixin.channel.bean.category.*; +import me.chanjar.weixin.channel.util.JsonUtils; +import me.chanjar.weixin.channel.util.ResponseUtils; +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.common.util.http.SimpleGetRequestExecutor; +import me.chanjar.weixin.common.util.http.SimplePostRequestExecutor; + +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Category.*; + +/** + * 视频号小店 商品类目相关接口 + * + * @author Zeyes + */ +@Slf4j +public class WxChannelCategoryServiceImpl implements WxChannelCategoryService { + + /** 微信商店服务 */ + private final BaseWxChannelServiceImpl shopService; + + public WxChannelCategoryServiceImpl(BaseWxChannelServiceImpl shopService) { + this.shopService = shopService; + } + + @Override + public CategoryQualificationResponse listAllCategory() throws WxErrorException { + // 数据量太大了,不记录日志 + String resJson = (String) shopService.executeWithoutLog(SimpleGetRequestExecutor.create(shopService), + LIST_ALL_CATEGORY_URL, null); + return ResponseUtils.decode(resJson, CategoryQualificationResponse.class); + } + + public List listAvailableCategory(String parentId) throws WxErrorException { + Long pid = null; + try { + pid = Long.parseLong(parentId); + } catch (Throwable e) { + log.error("parentId必须为数字, {}", parentId, e); + return Collections.emptyList(); + } + String reqJson = "{\"f_cat_id\": " + pid + "}"; + String resJson = (String) shopService.executeWithoutLog(SimplePostRequestExecutor.create(shopService), + AVAILABLE_CATEGORY_URL, reqJson); + ShopCategoryResponse response = ResponseUtils.decode(resJson, ShopCategoryResponse.class); + return response.getCategories(); + } + + @Override + public ShopCategoryResponse listAvailableCategories(String fCatId) throws WxErrorException { + String reqJson = "{\"f_cat_id\": " + fCatId + "}"; + String resJson = (String) shopService.executeWithoutLog(SimplePostRequestExecutor.create(shopService), + AVAILABLE_CATEGORY_URL, reqJson); + return ResponseUtils.decode(resJson, ShopCategoryResponse.class); + } + + @Override + public CategoryDetailResult getCategoryDetail(String id) throws WxErrorException { + Long catId = null; + try { + catId = Long.parseLong(id); + } catch (Throwable e) { + log.error("id必须为数字, {}", id, e); + return ResponseUtils.internalError(CategoryDetailResult.class); + } + String reqJson = "{\"cat_id\": " + catId + "}"; + String resJson = (String) shopService.executeWithoutLog(SimplePostRequestExecutor.create(shopService), + GET_CATEGORY_DETAIL_URL, reqJson); + return ResponseUtils.decode(resJson, CategoryDetailResult.class); + } + + @Override + public AuditApplyResponse addCategory(String level1, String level2, String level3, List certificate) + throws WxErrorException { + String reqJson = null; + try { + Long l1 = Long.parseLong(level1); + Long l2 = Long.parseLong(level2); + Long l3 = Long.parseLong(level3); + CategoryAuditInfo categoryInfo = new CategoryAuditInfo(); + categoryInfo.setLevel1(l1); + categoryInfo.setLevel2(l2); + categoryInfo.setLevel3(l3); + categoryInfo.setCertificates(certificate); + reqJson = JsonUtils.encode(new CategoryAuditRequest(categoryInfo)); + } catch (Throwable e) { + log.error("微信请求异常", e); + } + String resJson = shopService.post(ADD_CATEGORY_URL, reqJson); + return ResponseUtils.decode(resJson, AuditApplyResponse.class); + } + + @Override + public AuditApplyResponse addCategory(CategoryAuditInfo info) throws WxErrorException { + String reqJson = JsonUtils.encode(new CategoryAuditRequest(info)); + String resJson = shopService.post(ADD_CATEGORY_URL, reqJson); + return ResponseUtils.decode(resJson, AuditApplyResponse.class); + } + + @Override + public WxChannelBaseResponse cancelCategoryAudit(String auditId) throws WxErrorException { + String resJson = shopService.post(CANCEL_CATEGORY_AUDIT_URL, "{\"audit_id\": \"" + auditId + "\"}"); + return ResponseUtils.decode(resJson, WxChannelBaseResponse.class); + } + + @Override + public AuditResponse getAudit(String auditId) throws WxErrorException { + String resJson = shopService.post(GET_CATEGORY_AUDIT_URL, "{\"audit_id\": \"" + auditId + "\"}"); + return ResponseUtils.decode(resJson, AuditResponse.class); + } + + @Override + public PassCategoryResponse listPassCategory() throws WxErrorException { + String resJson = shopService.get(LIST_PASS_CATEGORY_URL, null); + return ResponseUtils.decode(resJson, PassCategoryResponse.class); + } + + @Override + public RelationCategoryResponse listRelationCategory(Boolean isFilterStatus, Integer status) throws WxErrorException { + RelationCategoryRequest request = new RelationCategoryRequest( + isFilterStatus != null ? isFilterStatus : false, + status != null ? status : 0 + ); + String reqJson = JsonUtils.encode(request); + String resJson = shopService.post(LIST_RELATION_CATEGORY_URL, reqJson); + return ResponseUtils.decode(resJson, RelationCategoryResponse.class); + } + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelCompassFinderServiceImpl.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelCompassFinderServiceImpl.java new file mode 100644 index 0000000000..c80345aef2 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelCompassFinderServiceImpl.java @@ -0,0 +1,55 @@ +package me.chanjar.weixin.channel.api.impl; + +import lombok.extern.slf4j.Slf4j; +import me.chanjar.weixin.channel.api.WxChannelCompassFinderService; +import me.chanjar.weixin.channel.bean.compass.CompassFinderBaseParam; +import me.chanjar.weixin.channel.bean.compass.finder.*; +import me.chanjar.weixin.channel.util.ResponseUtils; +import me.chanjar.weixin.common.error.WxErrorException; + +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.CompassFinder.*; + +/** + * 视频号助手 罗盘达人版服务实现 + * + * @author Winnie + */ +@Slf4j +public class WxChannelCompassFinderServiceImpl implements WxChannelCompassFinderService { + + /** + * 微信商店服务 + */ + private final BaseWxChannelServiceImpl shopService; + + public WxChannelCompassFinderServiceImpl(BaseWxChannelServiceImpl shopService) {this.shopService = shopService;} + + @Override + public OverallResponse getOverall(String ds) throws WxErrorException { + CompassFinderBaseParam param = new CompassFinderBaseParam(ds); + String resJson = shopService.post(GET_OVERALL_URL, param); + return ResponseUtils.decode(resJson, OverallResponse.class); + } + + @Override + public ProductDataResponse getProductData(String ds, String productId) throws WxErrorException { + ProductDataParam param = new ProductDataParam(ds, productId); + String resJson = shopService.post(GET_PRODUCT_DATA_URL, param); + return ResponseUtils.decode(resJson, ProductDataResponse.class); + } + + @Override + public ProductListResponse getProductList(String ds) throws WxErrorException { + CompassFinderBaseParam param = new CompassFinderBaseParam(ds); + String resJson = shopService.post(GET_PRODUCT_LIST_URL, param); + return ResponseUtils.decode(resJson, ProductListResponse.class); + } + + @Override + public SaleProfileDataResponse getSaleProfileData(String ds, Integer type) throws WxErrorException { + SaleProfileDataParam param = new SaleProfileDataParam(ds, type); + String resJson = shopService.post(GET_SALE_PROFILE_DATA_URL, param); + return ResponseUtils.decode(resJson, SaleProfileDataResponse.class); + } + +} 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 new file mode 100644 index 0000000000..3a593a691f --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelCompassShopServiceImpl.java @@ -0,0 +1,116 @@ +package me.chanjar.weixin.channel.api.impl; + +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.CompassShop.FINDER_AUTH_LIST_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.CompassShop.FINDER_LIST_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.CompassShop.GET_FINDER_OVERALL_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.CompassShop.GET_FINDER_PRODUCT_LIST_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.CompassShop.GET_FINDER_PRODUCT_OVERALL_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.CompassShop.GET_LIVE_LIST_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.CompassShop.GET_SHOP_OVERALL_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.CompassShop.GET_SHOP_PRODUCT_DATA_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.CompassShop.GET_SHOP_PRODUCT_LIST_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.CompassShop.GET_SHOP_SALE_PROFILE_DATA_URL; + +import lombok.extern.slf4j.Slf4j; +import me.chanjar.weixin.channel.api.WxChannelCompassShopService; +import me.chanjar.weixin.channel.bean.compass.CompassFinderBaseParam; +import me.chanjar.weixin.channel.bean.compass.shop.CompassFinderIdParam; +import me.chanjar.weixin.channel.bean.compass.shop.FinderAuthListResponse; +import me.chanjar.weixin.channel.bean.compass.shop.FinderListResponse; +import me.chanjar.weixin.channel.bean.compass.shop.FinderOverallResponse; +import me.chanjar.weixin.channel.bean.compass.shop.FinderProductListResponse; +import me.chanjar.weixin.channel.bean.compass.shop.FinderProductOverallResponse; +import me.chanjar.weixin.channel.bean.compass.shop.ShopLiveListResponse; +import me.chanjar.weixin.channel.bean.compass.shop.ShopOverallResponse; +import me.chanjar.weixin.channel.bean.compass.shop.ShopProductDataParam; +import me.chanjar.weixin.channel.bean.compass.shop.ShopProductDataResponse; +import me.chanjar.weixin.channel.bean.compass.shop.ShopProductListResponse; +import me.chanjar.weixin.channel.bean.compass.shop.ShopSaleProfileDataParam; +import me.chanjar.weixin.channel.bean.compass.shop.ShopSaleProfileDataResponse; +import me.chanjar.weixin.channel.util.ResponseUtils; +import me.chanjar.weixin.common.error.WxErrorException; + +/** + * 视频号/微信小店 罗盘商家版 服务实现 + * + * @author Zeyes + */ +@Slf4j +public class WxChannelCompassShopServiceImpl implements WxChannelCompassShopService { + + /** + * 微信商店服务 + */ + private final BaseWxChannelServiceImpl shopService; + + public WxChannelCompassShopServiceImpl(BaseWxChannelServiceImpl shopService) {this.shopService = shopService;} + + @Override + public ShopOverallResponse getShopOverall(String ds) throws WxErrorException { + CompassFinderBaseParam param = new CompassFinderBaseParam(ds); + String resJson = shopService.post(GET_SHOP_OVERALL_URL, param); + return ResponseUtils.decode(resJson, ShopOverallResponse.class); + } + + @Override + public FinderAuthListResponse getFinderAuthorizationList() throws WxErrorException { + String resJson = shopService.post(FINDER_AUTH_LIST_URL, "{}"); + return ResponseUtils.decode(resJson, FinderAuthListResponse.class); + } + + @Override + public FinderListResponse getFinderList(String ds) throws WxErrorException { + CompassFinderBaseParam param = new CompassFinderBaseParam(ds); + String resJson = shopService.post(FINDER_LIST_URL, param); + return ResponseUtils.decode(resJson, FinderListResponse.class); + } + + @Override + public FinderOverallResponse getFinderOverall(String ds) throws WxErrorException { + CompassFinderBaseParam param = new CompassFinderBaseParam(ds); + String resJson = shopService.post(GET_FINDER_OVERALL_URL, param); + return ResponseUtils.decode(resJson, FinderOverallResponse.class); + } + + @Override + public FinderProductListResponse getFinderProductList(String ds, String finderId) throws WxErrorException { + CompassFinderIdParam param = new CompassFinderIdParam(ds, finderId); + String resJson = shopService.post(GET_FINDER_PRODUCT_LIST_URL, param); + return ResponseUtils.decode(resJson, FinderProductListResponse.class); + } + + @Override + public FinderProductOverallResponse getFinderProductOverall(String ds, String finderId) throws WxErrorException { + CompassFinderIdParam param = new CompassFinderIdParam(ds, finderId); + String resJson = shopService.post(GET_FINDER_PRODUCT_OVERALL_URL, param); + return ResponseUtils.decode(resJson, FinderProductOverallResponse.class); + } + + @Override + public ShopLiveListResponse getShopLiveList(String ds, String finderId) throws WxErrorException { + CompassFinderIdParam param = new CompassFinderIdParam(ds, finderId); + String resJson = shopService.post(GET_LIVE_LIST_URL, param); + return ResponseUtils.decode(resJson, ShopLiveListResponse.class); + } + + @Override + public ShopProductDataResponse getShopProductData(String ds, String productId) throws WxErrorException { + ShopProductDataParam param = new ShopProductDataParam(ds, productId); + String resJson = shopService.post(GET_SHOP_PRODUCT_DATA_URL, param); + return ResponseUtils.decode(resJson, ShopProductDataResponse.class); + } + + @Override + public ShopProductListResponse getShopProductList(String ds) throws WxErrorException { + CompassFinderBaseParam param = new CompassFinderBaseParam(ds); + String resJson = shopService.post(GET_SHOP_PRODUCT_LIST_URL, param); + return ResponseUtils.decode(resJson, ShopProductListResponse.class); + } + + @Override + public ShopSaleProfileDataResponse getShopSaleProfileData(String ds, Integer type) throws WxErrorException { + ShopSaleProfileDataParam param = new ShopSaleProfileDataParam(ds, type); + String resJson = shopService.post(GET_SHOP_SALE_PROFILE_DATA_URL, param); + return ResponseUtils.decode(resJson, ShopSaleProfileDataResponse.class); + } +} 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 new file mode 100644 index 0000000000..22abf25fb0 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelCouponServiceImpl.java @@ -0,0 +1,88 @@ +package me.chanjar.weixin.channel.api.impl; + + +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Coupon.CREATE_COUPON_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Coupon.GET_COUPON_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Coupon.GET_USER_COUPON_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Coupon.LIST_COUPON_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Coupon.LIST_USER_COUPON_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Coupon.UPDATE_COUPON_STATUS_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Coupon.UPDATE_COUPON_URL; + +import lombok.extern.slf4j.Slf4j; +import me.chanjar.weixin.channel.api.WxChannelCouponService; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; +import me.chanjar.weixin.channel.bean.coupon.CouponIdInfo; +import me.chanjar.weixin.channel.bean.coupon.CouponIdResponse; +import me.chanjar.weixin.channel.bean.coupon.CouponInfoResponse; +import me.chanjar.weixin.channel.bean.coupon.CouponListParam; +import me.chanjar.weixin.channel.bean.coupon.CouponListResponse; +import me.chanjar.weixin.channel.bean.coupon.CouponParam; +import me.chanjar.weixin.channel.bean.coupon.CouponStatusParam; +import me.chanjar.weixin.channel.bean.coupon.UserCouponIdParam; +import me.chanjar.weixin.channel.bean.coupon.UserCouponListParam; +import me.chanjar.weixin.channel.bean.coupon.UserCouponListResponse; +import me.chanjar.weixin.channel.bean.coupon.UserCouponResponse; +import me.chanjar.weixin.channel.util.ResponseUtils; +import me.chanjar.weixin.common.error.WxErrorException; + +/** + * 视频号小店 优惠券服务实现 + * + * @author Zeyes + */ +@Slf4j +public class WxChannelCouponServiceImpl implements WxChannelCouponService { + + /** 微信商店服务 */ + private final BaseWxChannelServiceImpl shopService; + + public WxChannelCouponServiceImpl(BaseWxChannelServiceImpl shopService) { + this.shopService = shopService; + } + + @Override + public CouponIdResponse createCoupon(CouponParam coupon) throws WxErrorException { + String resJson = shopService.post(CREATE_COUPON_URL, coupon); + return ResponseUtils.decode(resJson, CouponIdResponse.class); + } + + @Override + public CouponIdResponse updateCoupon(CouponParam coupon) throws WxErrorException { + String resJson = shopService.post(UPDATE_COUPON_URL, coupon); + return ResponseUtils.decode(resJson, CouponIdResponse.class); + } + + @Override + public WxChannelBaseResponse updateCouponStatus(String couponId, Integer status) throws WxErrorException { + CouponStatusParam param = new CouponStatusParam(couponId, status); + String resJson = shopService.post(UPDATE_COUPON_STATUS_URL, param); + return ResponseUtils.decode(resJson, WxChannelBaseResponse.class); + } + + @Override + public CouponInfoResponse getCoupon(String couponId) throws WxErrorException { + CouponIdInfo param = new CouponIdInfo(couponId); + String resJson = shopService.post(GET_COUPON_URL, param); + return ResponseUtils.decode(resJson, CouponInfoResponse.class); + } + + @Override + public CouponListResponse getCouponList(CouponListParam param) throws WxErrorException { + String resJson = shopService.post(LIST_COUPON_URL, param); + return ResponseUtils.decode(resJson, CouponListResponse.class); + } + + @Override + public UserCouponResponse getUserCoupon(String openId, String userCouponId) throws WxErrorException { + UserCouponIdParam param = new UserCouponIdParam(openId, userCouponId); + String resJson = shopService.post(GET_USER_COUPON_URL, param); + return ResponseUtils.decode(resJson, UserCouponResponse.class); + } + + @Override + public UserCouponListResponse getUserCouponList(UserCouponListParam param) throws WxErrorException { + String resJson = shopService.post(LIST_USER_COUPON_URL, param); + return ResponseUtils.decode(resJson, UserCouponListResponse.class); + } +} 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 new file mode 100644 index 0000000000..b8f00a4f84 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelFreightTemplateServiceImpl.java @@ -0,0 +1,61 @@ +package me.chanjar.weixin.channel.api.impl; + +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.FreightTemplate.ADD_TEMPLATE_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.FreightTemplate.GET_TEMPLATE_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.FreightTemplate.LIST_TEMPLATE_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.FreightTemplate.UPDATE_TEMPLATE_URL; + +import lombok.extern.slf4j.Slf4j; +import me.chanjar.weixin.channel.api.WxChannelFreightTemplateService; +import me.chanjar.weixin.channel.bean.freight.FreightTemplate; +import me.chanjar.weixin.channel.bean.freight.TemplateAddParam; +import me.chanjar.weixin.channel.bean.freight.TemplateIdResponse; +import me.chanjar.weixin.channel.bean.freight.TemplateInfoResponse; +import me.chanjar.weixin.channel.bean.freight.TemplateListParam; +import me.chanjar.weixin.channel.bean.freight.TemplateListResponse; +import me.chanjar.weixin.channel.util.ResponseUtils; +import me.chanjar.weixin.common.error.WxErrorException; + +/** + * 视频号小店 运费模板服务实现 + * + * @author Zeyes + */ +@Slf4j +public class WxChannelFreightTemplateServiceImpl implements WxChannelFreightTemplateService { + /** 微信商店服务 */ + private final BaseWxChannelServiceImpl shopService; + + public WxChannelFreightTemplateServiceImpl(BaseWxChannelServiceImpl shopService) { + this.shopService = shopService; + } + + @Override + public TemplateListResponse listTemplate(Integer offset, Integer limit) throws WxErrorException { + TemplateListParam param = new TemplateListParam(offset, limit); + String resJson = shopService.post(LIST_TEMPLATE_URL, param); + return ResponseUtils.decode(resJson, TemplateListResponse.class); + + } + + @Override + public TemplateInfoResponse getTemplate(String templateId) throws WxErrorException { + String reqJson = "{\"template_id\": \"" + templateId + "\"}"; + String resJson = shopService.post(GET_TEMPLATE_URL, reqJson); + return ResponseUtils.decode(resJson, TemplateInfoResponse.class); + } + + @Override + public TemplateIdResponse addTemplate(FreightTemplate template) throws WxErrorException { + TemplateAddParam param = new TemplateAddParam(template); + String resJson = shopService.post(ADD_TEMPLATE_URL, param); + return ResponseUtils.decode(resJson, TemplateIdResponse.class); + } + + @Override + public TemplateIdResponse updateTemplate(FreightTemplate template) throws WxErrorException { + TemplateAddParam param = new TemplateAddParam(template); + String resJson = shopService.post(UPDATE_TEMPLATE_URL, param); + return ResponseUtils.decode(resJson, TemplateIdResponse.class); + } +} 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 new file mode 100644 index 0000000000..7cf30905ec --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelFundServiceImpl.java @@ -0,0 +1,167 @@ +package me.chanjar.weixin.channel.api.impl; + + +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Fund.CHECK_QRCODE_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Fund.GET_BALANCE_FLOW_DETAIL_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Fund.GET_BALANCE_FLOW_LIST_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Fund.GET_BALANCE_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Fund.GET_BANK_ACCOUNT_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Fund.GET_BANK_BY_NUM_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Fund.GET_BANK_LIST_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Fund.GET_CITY_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Fund.GET_PROVINCE_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Fund.GET_QRCODE_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Fund.GET_SUB_BANK_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Fund.GET_WITHDRAW_DETAIL_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Fund.GET_WITHDRAW_LIST_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Fund.SET_BANK_ACCOUNT_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Fund.WITHDRAW_URL; + +import lombok.extern.slf4j.Slf4j; +import me.chanjar.weixin.channel.api.WxChannelFundService; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; +import me.chanjar.weixin.channel.bean.fund.AccountInfo; +import me.chanjar.weixin.channel.bean.fund.AccountInfoParam; +import me.chanjar.weixin.channel.bean.fund.AccountInfoResponse; +import me.chanjar.weixin.channel.bean.fund.BalanceInfoResponse; +import me.chanjar.weixin.channel.bean.fund.FlowListResponse; +import me.chanjar.weixin.channel.bean.fund.FundsFlowResponse; +import me.chanjar.weixin.channel.bean.fund.FundsListParam; +import me.chanjar.weixin.channel.bean.fund.WithdrawDetailResponse; +import me.chanjar.weixin.channel.bean.fund.WithdrawListParam; +import me.chanjar.weixin.channel.bean.fund.WithdrawListResponse; +import me.chanjar.weixin.channel.bean.fund.WithdrawSubmitParam; +import me.chanjar.weixin.channel.bean.fund.WithdrawSubmitResponse; +import me.chanjar.weixin.channel.bean.fund.bank.BankCityResponse; +import me.chanjar.weixin.channel.bean.fund.bank.BankInfoResponse; +import me.chanjar.weixin.channel.bean.fund.bank.BankListResponse; +import me.chanjar.weixin.channel.bean.fund.bank.BankProvinceResponse; +import me.chanjar.weixin.channel.bean.fund.bank.BankSearchParam; +import me.chanjar.weixin.channel.bean.fund.bank.BranchInfoResponse; +import me.chanjar.weixin.channel.bean.fund.bank.BranchSearchParam; +import me.chanjar.weixin.channel.bean.fund.qrcode.QrCheckResponse; +import me.chanjar.weixin.channel.bean.fund.qrcode.QrCodeResponse; +import me.chanjar.weixin.channel.util.ResponseUtils; +import me.chanjar.weixin.common.error.WxErrorException; + +/** + * 视频号小店 资金服务实现 + * + * @author Zeyes + */ +@Slf4j +public class WxChannelFundServiceImpl implements WxChannelFundService { + + + /** 微信商店服务 */ + private final BaseWxChannelServiceImpl shopService; + + public WxChannelFundServiceImpl(BaseWxChannelServiceImpl shopService) { + this.shopService = shopService; + } + + @Override + public BalanceInfoResponse getBalance() throws WxErrorException { + String resJson = shopService.post(GET_BALANCE_URL, "{}"); + return ResponseUtils.decode(resJson, BalanceInfoResponse.class); + } + + @Override + public AccountInfoResponse getBankAccount() throws WxErrorException { + String resJson = shopService.post(GET_BANK_ACCOUNT_URL, "{}"); + return ResponseUtils.decode(resJson, AccountInfoResponse.class); + } + + @Override + public FundsFlowResponse getFundsFlowDetail(String flowId) throws WxErrorException { + String reqJson = "{\"flow_id\":\"" + flowId + "\"}"; + String resJson = shopService.post(GET_BALANCE_FLOW_DETAIL_URL, reqJson); + return ResponseUtils.decode(resJson, FundsFlowResponse.class); + } + + @Override + public FlowListResponse listFundsFlow(FundsListParam param) throws WxErrorException { + String resJson = shopService.post(GET_BALANCE_FLOW_LIST_URL, param); + return ResponseUtils.decode(resJson, FlowListResponse.class); + } + + @Override + public WithdrawDetailResponse getWithdrawDetail(String withdrawId) throws WxErrorException { + String reqJson = "{\"withdraw_id\":\"" + withdrawId + "\"}"; + String resJson = shopService.post(GET_WITHDRAW_DETAIL_URL, reqJson); + return ResponseUtils.decode(resJson, WithdrawDetailResponse.class); + } + + @Override + public WithdrawListResponse listWithdraw(Integer pageNum, Integer pageSize, Long startTime, Long endTime) + throws WxErrorException { + WithdrawListParam param = new WithdrawListParam(pageNum, pageSize, startTime, endTime); + String resJson = shopService.post(GET_WITHDRAW_LIST_URL, param); + return ResponseUtils.decode(resJson, WithdrawListResponse.class); + } + + @Override + public WxChannelBaseResponse setBankAccount(AccountInfo accountInfo) throws WxErrorException { + AccountInfoParam param = new AccountInfoParam(accountInfo); + String resJson = shopService.post(SET_BANK_ACCOUNT_URL, param); + return ResponseUtils.decode(resJson, WxChannelBaseResponse.class); + } + + @Override + public WithdrawSubmitResponse submitWithdraw(Integer amount, String remark, String bankMemo) + throws WxErrorException { + WithdrawSubmitParam param = new WithdrawSubmitParam(amount, remark, bankMemo); + String resJson = shopService.post(WITHDRAW_URL, param); + return ResponseUtils.decode(resJson, WithdrawSubmitResponse.class); + } + + @Override + public BankInfoResponse getBankInfoByCardNo(String accountNumber) throws WxErrorException { + String reqJson = "{\"account_number\":\"" + accountNumber + "\"}"; + String resJson = shopService.post(GET_BANK_BY_NUM_URL, reqJson); + return ResponseUtils.decode(resJson, BankInfoResponse.class); + } + + @Override + public BankListResponse searchBankList(Integer offset, Integer limit, String keywords, Integer bankType) + throws WxErrorException { + BankSearchParam param = new BankSearchParam(offset, limit, keywords, bankType); + String resJson = shopService.post(GET_BANK_LIST_URL, param); + return ResponseUtils.decode(resJson, BankListResponse.class); + } + + @Override + public BankCityResponse searchCityList(String provinceCode) throws WxErrorException { + String reqJson = "{\"province_code\":\"" + provinceCode + "\"}"; + String resJson = shopService.post(GET_CITY_URL, reqJson); + return ResponseUtils.decode(resJson, BankCityResponse.class); + } + + @Override + public BankProvinceResponse getProvinceList() throws WxErrorException { + String resJson = shopService.post(GET_PROVINCE_URL, "{}"); + return ResponseUtils.decode(resJson, BankProvinceResponse.class); + } + + @Override + public BranchInfoResponse searchBranchList(String bankCode, String cityCode, Integer offset, Integer limit) + throws WxErrorException { + BranchSearchParam param = new BranchSearchParam(bankCode, cityCode, offset, limit); + String resJson = shopService.post(GET_SUB_BANK_URL, param); + return ResponseUtils.decode(resJson, BranchInfoResponse.class); + } + + @Override + public QrCodeResponse getQrCode(String qrcodeTicket) throws WxErrorException { + String reqJson = "{\"qrcode_ticket\":\"" + qrcodeTicket + "\"}"; + String resJson = shopService.post(GET_QRCODE_URL, reqJson); + return ResponseUtils.decode(resJson, QrCodeResponse.class); + } + + @Override + public QrCheckResponse checkQrStatus(String qrcodeTicket) throws WxErrorException { + String reqJson = "{\"qrcode_ticket\":\"" + qrcodeTicket + "\"}"; + String resJson = shopService.post(CHECK_QRCODE_URL, reqJson); + return ResponseUtils.decode(resJson, QrCheckResponse.class); + } +} 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 new file mode 100644 index 0000000000..7eace4377b --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelLiveDashboardServiceImpl.java @@ -0,0 +1,81 @@ +package me.chanjar.weixin.channel.api.impl; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import lombok.extern.slf4j.Slf4j; +import me.chanjar.weixin.channel.api.WxChannelLiveDashboardService; +import me.chanjar.weixin.channel.bean.live.dashboard.LiveDataParam; +import me.chanjar.weixin.channel.bean.live.dashboard.LiveDataResponse; +import me.chanjar.weixin.channel.bean.live.dashboard.LiveListParam; +import me.chanjar.weixin.channel.bean.live.dashboard.LiveListResponse; +import me.chanjar.weixin.channel.util.ResponseUtils; +import me.chanjar.weixin.common.error.WxErrorException; +import org.apache.commons.lang3.ObjectUtils; + +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.LiveDashboard.*; + +/** + * 视频号助手 直播大屏数据服务实现 + * + * @author Winnie + */ +@Slf4j +public class WxChannelLiveDashboardServiceImpl implements WxChannelLiveDashboardService { + + /** + * 微信商店服务 + */ + private final BaseWxChannelServiceImpl shopService; + private final ObjectMapper objectMapper = new ObjectMapper(); + + public WxChannelLiveDashboardServiceImpl(BaseWxChannelServiceImpl shopService) {this.shopService = shopService;} + + @Override + public LiveListResponse getLiveList(Long ds) throws WxErrorException { + LiveListParam param = new LiveListParam(ds); + String resJson = shopService.post(GET_LIVE_LIST_URL, param); + return ResponseUtils.decode(resJson, LiveListResponse.class); + } + + @Override + public LiveDataResponse getLiveData(String exportId) throws WxErrorException { + LiveDataParam param = new LiveDataParam(exportId); + String resJson = shopService.post(GET_LIVE_DATA_URL, param); + return this.convertLiveDataResponse(resJson); + } + + /** + * 微信接口获取直播数据中存在非标准JSON,方便业务处理返回前做好解析 + * 处理参数: + * live_dashboard_data,live_comparison_index,live_ec_data_summary,live_ec_conversion_metric, + * live_ec_profile,live_distribution_channel,single_live_ec_spu_data_page_v2 + * + * @param resJson 直播数据返回JSON + * @return LiveDataResponse + * + * @throws WxErrorException 异常 + */ + private LiveDataResponse convertLiveDataResponse(String resJson) throws WxErrorException { + try { + ObjectNode rootNode = (ObjectNode) objectMapper.readTree(resJson); + String[] dataKeyArray = new String[] { + "live_dashboard_data", "live_comparison_index", "live_ec_data_summary", "live_ec_conversion_metric", + "live_ec_profile", "live_distribution_channel", "single_live_ec_spu_data_page_v2" + }; + for(String dataKey : dataKeyArray) { + JsonNode jsonNode = rootNode.get(dataKey); + if (ObjectUtils.isNotEmpty(jsonNode)) { + JsonNode dataJsonNode = objectMapper.readTree(jsonNode.asText()); + rootNode.set(dataKey, dataJsonNode); + } + } + String json = objectMapper.writeValueAsString(rootNode); + return ResponseUtils.decode(json, LiveDataResponse.class); + } catch (JsonProcessingException e) { + throw new WxErrorException(e); + } + } + +} 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 new file mode 100644 index 0000000000..fd26268333 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelOrderServiceImpl.java @@ -0,0 +1,181 @@ +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.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.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; +import me.chanjar.weixin.channel.bean.order.OrderPriceParam; +import me.chanjar.weixin.channel.bean.order.OrderRemarkParam; +import me.chanjar.weixin.channel.bean.order.OrderSearchParam; +import me.chanjar.weixin.channel.bean.order.VirtualTelNumberResponse; +import me.chanjar.weixin.channel.util.ResponseUtils; +import me.chanjar.weixin.common.error.WxErrorException; + + +/** + * 视频号小店订单服务 + * + * @author Zeyes + */ +@Slf4j +public class WxChannelOrderServiceImpl implements WxChannelOrderService { + + /** 微信商店服务 */ + private final BaseWxChannelServiceImpl shopService; + + public WxChannelOrderServiceImpl(BaseWxChannelServiceImpl shopService) { + this.shopService = shopService; + } + + @Override + public OrderInfoResponse getOrder(String orderId) throws WxErrorException { + 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); + } + + @Override + public OrderListResponse getOrders(OrderListParam param) throws WxErrorException { + String resJson = shopService.post(ORDER_LIST_URL, param); + return ResponseUtils.decode(resJson, OrderListResponse.class); + } + + @Override + public OrderListResponse searchOrder(OrderSearchParam param) throws WxErrorException { + String resJson = shopService.post(ORDER_SEARCH_URL, param); + return ResponseUtils.decode(resJson, OrderListResponse.class); + } + + @Override + public WxChannelBaseResponse updatePrice(String orderId, Integer expressFee, List changeOrderInfos) + throws WxErrorException { + OrderPriceParam param = new OrderPriceParam(orderId, expressFee, changeOrderInfos); + String resJson = shopService.post(UPDATE_PRICE_URL, param); + ; + return ResponseUtils.decode(resJson, WxChannelBaseResponse.class); + } + + @Override + public WxChannelBaseResponse updateRemark(String orderId, String merchantNotes) throws WxErrorException { + OrderRemarkParam param = new OrderRemarkParam(orderId, merchantNotes); + String resJson = shopService.post(UPDATE_REMARK_URL, param); + return ResponseUtils.decode(resJson, WxChannelBaseResponse.class); + } + + @Override + public WxChannelBaseResponse updateAddress(String orderId, AddressInfo userAddress) throws WxErrorException { + OrderAddressParam param = new OrderAddressParam(orderId, userAddress); + String resJson = shopService.post(UPDATE_ADDRESS_URL, param); + return ResponseUtils.decode(resJson, WxChannelBaseResponse.class); + } + + @Override + public WxChannelBaseResponse updateDelivery(DeliveryUpdateParam param) throws WxErrorException { + String resJson = shopService.post(UPDATE_EXPRESS_URL, param); + return ResponseUtils.decode(resJson, WxChannelBaseResponse.class); + } + + @Override + public WxChannelBaseResponse acceptAddressModify(String orderId) throws WxErrorException { + OrderIdParam param = new OrderIdParam(orderId); + String resJson = shopService.post(ACCEPT_ADDRESS_MODIFY_URL, param); + return ResponseUtils.decode(resJson, WxChannelBaseResponse.class); + } + + @Override + public WxChannelBaseResponse rejectAddressModify(String orderId) throws WxErrorException { + OrderIdParam param = new OrderIdParam(orderId); + String resJson = shopService.post(REJECT_ADDRESS_MODIFY_URL, param); + return ResponseUtils.decode(resJson, WxChannelBaseResponse.class); + } + + @Override + public WxChannelBaseResponse closeOrder(String orderId) { + // 暂不支持 + return ResponseUtils.internalError(WxChannelBaseResponse.class); + } + + @Override + public DeliveryCompanyResponse listDeliveryCompany() throws WxErrorException { + String resJson = shopService.post(GET_DELIVERY_COMPANY_URL, "{}"); + 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 { + DeliverySendParam param = new DeliverySendParam(orderId, deliveryList); + String resJson = shopService.post(DELIVERY_SEND_URL, param); + return ResponseUtils.decode(resJson, WxChannelBaseResponse.class); + } + + @Override + public WxChannelBaseResponse uploadFreshInspect(String orderId, List items) + throws WxErrorException { + FreshInspectParam param = new FreshInspectParam(orderId, items); + String resJson = shopService.post(UPLOAD_FRESH_INSPECT_URL, param); + return ResponseUtils.decode(resJson, WxChannelBaseResponse.class); + } + + @Override + public VirtualTelNumberResponse getVirtualTelNumber(String orderId) throws WxErrorException { + String reqJson = "{\"order_id\":\"" + orderId + "\"}"; + String resJson = shopService.post(VIRTUAL_TEL_NUMBER_URL, reqJson); + return ResponseUtils.decode(resJson, VirtualTelNumberResponse.class); + } + + @Override + public DecodeSensitiveInfoResponse decodeSensitiveInfo(String orderId) throws WxErrorException { + String reqJson = "{\"order_id\":\"" + orderId + "\"}"; + String resJson = shopService.post(DECODE_SENSITIVE_INFO_URL, reqJson); + return ResponseUtils.decode(resJson, DecodeSensitiveInfoResponse.class); + } +} 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 new file mode 100644 index 0000000000..08c9638f0c --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelProductServiceImpl.java @@ -0,0 +1,243 @@ +package me.chanjar.weixin.channel.api.impl; + + +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.ADD_LIMIT_TASK_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.CANCEL_AUDIT_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.DELETE_LIMIT_TASK_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.LIST_LIMIT_TASK_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.SPU_ADD_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.SPU_AUDIT_FREE_UPDATE_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.SPU_DELISTING_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.SPU_DEL_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.SPU_GET_STOCK_BATCH_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.SPU_GET_STOCK_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.SPU_GET_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.SPU_H5URL_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.SPU_LISTING_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.SPU_LIST_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.SPU_QRCODE_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.SPU_TAGLINK_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.SPU_UPDATE_STOCK_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.SPU_UPDATE_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.STOP_LIMIT_TASK_URL; + +import java.util.List; +import lombok.extern.slf4j.Slf4j; +import me.chanjar.weixin.channel.api.WxChannelProductService; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; +import me.chanjar.weixin.channel.bean.limit.LimitTaskAddResponse; +import me.chanjar.weixin.channel.bean.limit.LimitTaskListParam; +import me.chanjar.weixin.channel.bean.limit.LimitTaskListResponse; +import me.chanjar.weixin.channel.bean.limit.LimitTaskParam; +import me.chanjar.weixin.channel.bean.product.SkuStockBatchParam; +import me.chanjar.weixin.channel.bean.product.SkuStockBatchResponse; +import me.chanjar.weixin.channel.bean.product.SkuStockParam; +import me.chanjar.weixin.channel.bean.product.SkuStockResponse; +import me.chanjar.weixin.channel.bean.product.SpuFastInfo; +import me.chanjar.weixin.channel.bean.product.SpuGetResponse; +import me.chanjar.weixin.channel.bean.product.SpuInfo; +import me.chanjar.weixin.channel.bean.product.SpuListParam; +import me.chanjar.weixin.channel.bean.product.SpuListResponse; +import me.chanjar.weixin.channel.bean.product.SpuUpdateInfo; +import me.chanjar.weixin.channel.bean.product.SpuUpdateResponse; +import me.chanjar.weixin.channel.bean.product.link.ProductH5UrlResponse; +import me.chanjar.weixin.channel.bean.product.link.ProductQrCodeResponse; +import me.chanjar.weixin.channel.bean.product.link.ProductTagLinkResponse; +import me.chanjar.weixin.channel.util.JsonUtils; +import me.chanjar.weixin.channel.util.ResponseUtils; +import me.chanjar.weixin.common.error.WxErrorException; + +/** + * 视频号小店商品服务 + * + * @author Zeyes + */ +@Slf4j +public class WxChannelProductServiceImpl implements WxChannelProductService { + + /** 微信商店服务 */ + private final BaseWxChannelServiceImpl shopService; + + public WxChannelProductServiceImpl(BaseWxChannelServiceImpl shopService) { + this.shopService = shopService; + } + + @Override + public SpuUpdateResponse addProduct(SpuUpdateInfo info) throws WxErrorException { + String reqJson = JsonUtils.encode(info); + String resJson = shopService.post(SPU_ADD_URL, reqJson); + return ResponseUtils.decode(resJson, SpuUpdateResponse.class); + } + + @Override + public SpuUpdateResponse updateProduct(SpuUpdateInfo info) throws WxErrorException { + String reqJson = JsonUtils.encode(info); + String resJson = shopService.post(SPU_UPDATE_URL, reqJson); + return ResponseUtils.decode(resJson, SpuUpdateResponse.class); + } + + @Override + public SpuUpdateResponse addProduct(SpuInfo info) throws WxErrorException { + String reqJson = JsonUtils.encode(info); + String resJson = shopService.post(SPU_ADD_URL, reqJson); + return ResponseUtils.decode(resJson, SpuUpdateResponse.class); + } + + @Override + public SpuUpdateResponse updateProduct(SpuInfo info) throws WxErrorException { + String reqJson = JsonUtils.encode(info); + String resJson = shopService.post(SPU_UPDATE_URL, reqJson); + return ResponseUtils.decode(resJson, SpuUpdateResponse.class); + } + + @Override + public WxChannelBaseResponse updateProductAuditFree(SpuFastInfo info) throws WxErrorException { + String reqJson = JsonUtils.encode(info); + String resJson = shopService.post(SPU_AUDIT_FREE_UPDATE_URL, reqJson); + return ResponseUtils.decode(resJson, SpuUpdateResponse.class); + } + + @Override + public WxChannelBaseResponse updateStock(String productId, String skuId, Integer diffType, Integer num) + throws WxErrorException { + SkuStockParam param = new SkuStockParam(productId, skuId, diffType, num); + String reqJson = JsonUtils.encode(param); + String resJson = shopService.post(SPU_UPDATE_STOCK_URL, reqJson); + return ResponseUtils.decode(resJson, WxChannelBaseResponse.class); + } + + /** + * 生成商品id Json + * + * @param productId 商品ID + * @param dataType 默认取1。1:获取线上数据, 2:获取草稿数据, 3:同时获取线上和草稿数据(注意:需成功上架后才有线上数据) + * @return json + */ + protected String generateProductIdJson(String productId, Integer dataType) { + StringBuilder sb = new StringBuilder(); + sb.append('{'); + if (productId != null) { + sb.append("\"product_id\":").append(productId); + } + + if (dataType != null) { + sb.append(",").append("\"data_type\":").append(dataType); + } + sb.append('}'); + return sb.toString(); + } + + /** + * 简单的商品请求 参数是商品id 只返回基本结果 + * + * @param url 资源路径 + * @param productId 商品ID + * @return 是否成功 + */ + protected WxChannelBaseResponse simpleProductRequest(String url, String productId) throws WxErrorException { + String reqJson = this.generateProductIdJson(productId, null); + String resJson = shopService.post(url, reqJson); + return ResponseUtils.decode(resJson, WxChannelBaseResponse.class); + } + + @Override + public WxChannelBaseResponse deleteProduct(String productId) throws WxErrorException { + return simpleProductRequest(SPU_DEL_URL, productId); + } + + @Override + public WxChannelBaseResponse cancelProductAudit(String productId) throws WxErrorException { + return simpleProductRequest(CANCEL_AUDIT_URL, productId); + } + + @Override + public SpuGetResponse getProduct(String productId, Integer dataType) throws WxErrorException { + String reqJson = this.generateProductIdJson(productId, dataType); + String resJson = shopService.post(SPU_GET_URL, reqJson); + return ResponseUtils.decode(resJson, SpuGetResponse.class); + } + + @Override + public SpuListResponse listProduct(Integer pageSize, String nextKey, Integer status) throws WxErrorException { + SpuListParam param = new SpuListParam(pageSize, nextKey, status); + String reqJson = JsonUtils.encode(param); + String resJson = shopService.post(SPU_LIST_URL, reqJson); + return ResponseUtils.decode(resJson, SpuListResponse.class); + } + + @Override + public WxChannelBaseResponse upProduct(String productId) throws WxErrorException { + return simpleProductRequest(SPU_LISTING_URL, productId); + } + + @Override + public WxChannelBaseResponse downProduct(String productId) throws WxErrorException { + return simpleProductRequest(SPU_DELISTING_URL, productId); + } + + @Override + public SkuStockResponse getSkuStock(String productId, String skuId) throws WxErrorException { + String reqJson = "{\"product_id\":\"" + productId + "\",\"sku_id\":\"" + skuId + "\"}"; + String resJson = shopService.post(SPU_GET_STOCK_URL, reqJson); + return ResponseUtils.decode(resJson, SkuStockResponse.class); + } + + @Override + public SkuStockBatchResponse getSkuStockBatch(List productIds) throws WxErrorException { + SkuStockBatchParam param = new SkuStockBatchParam(productIds); + String reqJson = JsonUtils.encode(param); + String resJson = shopService.post(SPU_GET_STOCK_BATCH_URL, reqJson); + return ResponseUtils.decode(resJson, SkuStockBatchResponse.class); + } + + @Override + public ProductH5UrlResponse getProductH5Url(String productId) throws WxErrorException { + String reqJson = "{\"product_id\":\"" + productId + "\"}"; + String resJson = shopService.post(SPU_H5URL_URL, reqJson); + return ResponseUtils.decode(resJson, ProductH5UrlResponse.class); + } + + @Override + public ProductQrCodeResponse getProductQrCode(String productId) throws WxErrorException { + String reqJson = "{\"product_id\":\"" + productId + "\"}"; + String resJson = shopService.post(SPU_QRCODE_URL, reqJson); + return ResponseUtils.decode(resJson, ProductQrCodeResponse.class); + } + + @Override + public ProductTagLinkResponse getProductTagLink(String productId) throws WxErrorException { + String reqJson = "{\"product_id\":\"" + productId + "\"}"; + String resJson = shopService.post(SPU_TAGLINK_URL, reqJson); + return ResponseUtils.decode(resJson, ProductTagLinkResponse.class); + } + + @Override + public LimitTaskAddResponse addLimitTask(LimitTaskParam param) throws WxErrorException { + String reqJson = JsonUtils.encode(param); + String resJson = shopService.post(ADD_LIMIT_TASK_URL, reqJson); + return ResponseUtils.decode(resJson, LimitTaskAddResponse.class); + } + + @Override + public LimitTaskListResponse listLimitTask(Integer pageSize, String nextKey, Integer status) + throws WxErrorException { + LimitTaskListParam param = new LimitTaskListParam(pageSize, nextKey, status); + String reqJson = JsonUtils.encode(param); + String resJson = shopService.post(LIST_LIMIT_TASK_URL, reqJson); + return ResponseUtils.decode(resJson, LimitTaskListResponse.class); + } + + @Override + public WxChannelBaseResponse stopLimitTask(String taskId) throws WxErrorException { + String reqJson = "{\"task_id\": \"" + taskId + "\"}"; + String resJson = shopService.post(STOP_LIMIT_TASK_URL, reqJson); + return ResponseUtils.decode(resJson, WxChannelBaseResponse.class); + } + + @Override + public WxChannelBaseResponse deleteLimitTask(String taskId) throws WxErrorException { + String reqJson = "{\"task_id\": \"" + taskId + "\"}"; + String resJson = shopService.post(DELETE_LIMIT_TASK_URL, reqJson); + return ResponseUtils.decode(resJson, WxChannelBaseResponse.class); + } +} 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 new file mode 100644 index 0000000000..6f380f80fb --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelServiceHttpClientImpl.java @@ -0,0 +1,115 @@ +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.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.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.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 + */ +@Slf4j +public class WxChannelServiceHttpClientImpl extends BaseWxChannelServiceImpl { + + private CloseableHttpClient httpClient; + private HttpHost httpProxy; + + @Override + public void initHttp() { + WxChannelConfig config = this.getConfig(); + ApacheHttpClientBuilder apacheHttpClientBuilder = config.getApacheHttpClientBuilder(); + if (null == apacheHttpClientBuilder) { + apacheHttpClientBuilder = DefaultApacheHttpClientBuilder.get(); + } + + apacheHttpClientBuilder.httpProxyHost(config.getHttpProxyHost()) + .httpProxyPort(config.getHttpProxyPort()) + .httpProxyUsername(config.getHttpProxyUsername()) + .httpProxyPassword(config.getHttpProxyPassword()); + + 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.APACHE_HTTP; + } + + @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, ApacheBasicResponseHandler.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, 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..f4cbb04755 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelServiceHttpComponentsImpl.java @@ -0,0 +1,112 @@ +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.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() == null ? null : 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/WxChannelServiceImpl.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelServiceImpl.java new file mode 100644 index 0000000000..ccd4eafc79 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelServiceImpl.java @@ -0,0 +1,26 @@ +package me.chanjar.weixin.channel.api.impl; + +import lombok.extern.slf4j.Slf4j; + +/** + * 视频号小店服务实现 + * + * @author Zeyes + */ +@Slf4j +public class WxChannelServiceImpl extends WxChannelServiceHttpComponentsImpl { + + public WxChannelServiceImpl() { + } + +// /** +// * 设置获取access_token接口参数. +// * +// * @param stabled false 表示调用普通模式AccessToken接口, true调用稳定模式接口 +// * @param forceRefresh stabled=true使用, true表示强制刷新模式 +// * @deprecated 请使用 {@link BaseWxChannelServiceImpl#setConfig(WxChannelConfig) } 替代 +// */ +// @Deprecated +// public WxChannelServiceImpl(Boolean stabled, Boolean forceRefresh) { +// } +} 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 new file mode 100644 index 0000000000..f7b0ccc65b --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelServiceOkHttpImpl.java @@ -0,0 +1,109 @@ +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 java.util.Objects; +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.okhttp.DefaultOkHttpClientBuilder; +import me.chanjar.weixin.common.util.http.okhttp.OkHttpProxyInfo; +import okhttp3.Authenticator; +import okhttp3.Credentials; +import okhttp3.MediaType; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.RequestBody; +import okhttp3.Response; +import okhttp3.Route; +import org.apache.commons.lang3.StringUtils; + +/** + * @author : zhenyun.su + * @since : 2024/2/27 + */ +@Slf4j +public class WxChannelServiceOkHttpImpl extends BaseWxChannelServiceImpl { + private OkHttpClient httpClient; + private OkHttpProxyInfo httpProxy; + + public WxChannelServiceOkHttpImpl() { + } + + @Override + public void initHttp() { + log.debug("WxChannelServiceOkHttpImpl initHttp"); + if (this.config.getHttpProxyHost() != null && this.config.getHttpProxyPort() > 0) { + 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.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("Proxy-Authorization", credential).build(); + } + }); + this.httpClient = clientBuilder.build(); + } else { + this.httpClient = DefaultOkHttpClientBuilder.get().build(); + } + } + + @Override + public OkHttpClient getRequestHttpClient() { + return this.httpClient; + } + + @Override + public OkHttpProxyInfo getRequestHttpProxy() { + return this.httpProxy; + } + + @Override + public HttpClientType getRequestType() { + return HttpClientType.OK_HTTP; + } + + @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()); + + Request request = new Request.Builder().url(url).get().build(); + try (Response response = getRequestHttpClient().newCall(request).execute()) { + return Objects.requireNonNull(response.body()).string(); + } + } + + @Override + protected String doGetStableAccessTokenRequest(boolean forceRefresh) throws IOException { + WxChannelConfig config = this.getConfig(); + String url = StringUtils.isNotEmpty(config.getAccessTokenUrl()) ? + config.getAccessTokenUrl() : StringUtils.isNotEmpty(config.getApiHostUrl()) ? + GET_STABLE_ACCESS_TOKEN_URL.replace("https://api.weixin.qq.com", config.getApiHostUrl()) : + GET_STABLE_ACCESS_TOKEN_URL; + + 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; + + RequestBody body = RequestBody.Companion.create(requestJson, MediaType.parse("application/json; charset=utf-8")); + Request request = new Request.Builder().url(url).post(body).build(); + try (Response response = getRequestHttpClient().newCall(request).execute()) { + return Objects.requireNonNull(response.body()).string(); + } + } +} 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 new file mode 100644 index 0000000000..3e27b124c7 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelSharerServiceImpl.java @@ -0,0 +1,76 @@ +package me.chanjar.weixin.channel.api.impl; + + +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Share.BIND_SHARER_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Share.LIST_SHARER_ORDER_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Share.LIST_SHARER_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Share.SEARCH_SHARER_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Share.UNBIND_SHARER_URL; + +import com.google.gson.JsonObject; +import java.util.List; +import lombok.extern.slf4j.Slf4j; +import me.chanjar.weixin.channel.api.WxChannelSharerService; +import me.chanjar.weixin.channel.bean.sharer.SharerBindResponse; +import me.chanjar.weixin.channel.bean.sharer.SharerInfoResponse; +import me.chanjar.weixin.channel.bean.sharer.SharerListParam; +import me.chanjar.weixin.channel.bean.sharer.SharerOrderParam; +import me.chanjar.weixin.channel.bean.sharer.SharerOrderResponse; +import me.chanjar.weixin.channel.bean.sharer.SharerSearchParam; +import me.chanjar.weixin.channel.bean.sharer.SharerSearchResponse; +import me.chanjar.weixin.channel.bean.sharer.SharerUnbindParam; +import me.chanjar.weixin.channel.bean.sharer.SharerUnbindResponse; +import me.chanjar.weixin.channel.util.ResponseUtils; +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.common.util.json.GsonHelper; + +/** + * 视频号小店 分享员服务实现 + * + * @author Zeyes + */ +@Slf4j +public class WxChannelSharerServiceImpl implements WxChannelSharerService { + + /** 微信商店服务 */ + private final BaseWxChannelServiceImpl shopService; + + public WxChannelSharerServiceImpl(BaseWxChannelServiceImpl shopService) { + this.shopService = shopService; + } + + @Override + public SharerBindResponse bindSharer(String username) throws WxErrorException { + JsonObject jsonObject = GsonHelper.buildJsonObject("username", username); + + String resJson = shopService.post(BIND_SHARER_URL, jsonObject); + return ResponseUtils.decode(resJson, SharerBindResponse.class); + } + + @Override + public SharerSearchResponse searchSharer(String openid, String username) throws WxErrorException { + SharerSearchParam param = new SharerSearchParam(openid, username); + String resJson = shopService.post(SEARCH_SHARER_URL, param); + return ResponseUtils.decode(resJson, SharerSearchResponse.class); + } + + @Override + public SharerInfoResponse listSharer(Integer page, Integer pageSize, Integer sharerType) throws WxErrorException { + SharerListParam param = new SharerListParam(page, pageSize, sharerType); + String resJson = shopService.post(LIST_SHARER_URL, param); + return ResponseUtils.decode(resJson, SharerInfoResponse.class); + } + + @Override + public SharerOrderResponse listSharerOrder(SharerOrderParam param) throws WxErrorException { + String resJson = shopService.post(LIST_SHARER_ORDER_URL, param); + return ResponseUtils.decode(resJson, SharerOrderResponse.class); + } + + @Override + public SharerUnbindResponse unbindSharer(List openIds) throws WxErrorException { + SharerUnbindParam param = new SharerUnbindParam(openIds); + String resJson = shopService.post(UNBIND_SHARER_URL, param); + return ResponseUtils.decode(resJson, SharerUnbindResponse.class); + } +} 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 new file mode 100644 index 0000000000..4644989d60 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelVipServiceImpl.java @@ -0,0 +1,68 @@ +package me.chanjar.weixin.channel.api.impl; + +import lombok.extern.slf4j.Slf4j; +import me.chanjar.weixin.channel.api.WxChannelVipService; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; +import me.chanjar.weixin.channel.bean.vip.*; +import me.chanjar.weixin.channel.util.ResponseUtils; +import me.chanjar.weixin.common.error.WxErrorException; + +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Vip.*; + +/** + * 视频号小店 会员功能接口 + * + * @author aushiye + * @link 会员功能接口文档 + */ + +@Slf4j +public class WxChannelVipServiceImpl implements WxChannelVipService { + private final BaseWxChannelServiceImpl shopService; + + public WxChannelVipServiceImpl(BaseWxChannelServiceImpl shopService) { + this.shopService = shopService; + } + + @Override + public VipInfoResponse getVipInfo(String openId, Boolean needPhoneNumber) throws WxErrorException { + VipInfoParam param = new VipInfoParam(openId, needPhoneNumber); + String respJson = shopService.post(VIP_USER_INFO_URL, param); + return ResponseUtils.decode(respJson, VipInfoResponse.class); + } + + @Override + public VipListResponse getVipList(Boolean needPhoneNumber, Integer pageNum, Integer pageSize) throws WxErrorException { + VipListParam param = new VipListParam(needPhoneNumber, pageNum, pageSize); + String respJson = shopService.post(VIP_USER_LIST_URL, param); + return ResponseUtils.decode(respJson, VipListResponse.class); + } + + @Override + public VipScoreResponse getVipScore(String openId) throws WxErrorException { + VipOpenIdParam param = new VipOpenIdParam(openId); + String respJson = shopService.post(VIP_SCORE_URL, param); + return ResponseUtils.decode(respJson, VipScoreResponse.class); + } + + @Override + public WxChannelBaseResponse increaseVipScore(String openId, String score, String remark, String requestId) throws WxErrorException { + VipScoreParam param = new VipScoreParam(openId, score, remark, requestId); + String respJson = shopService.post(SCORE_INCREASE_URL, param); + return ResponseUtils.decode(respJson, WxChannelBaseResponse.class); + } + + @Override + public WxChannelBaseResponse decreaseVipScore(String openId, String score, String remark, String requestId) throws WxErrorException { + VipScoreParam param = new VipScoreParam(openId, score, remark, requestId); + String respJson = shopService.post(SCORE_DECREASE_URL, param); + return ResponseUtils.decode(respJson, WxChannelBaseResponse.class); + } + + @Override + public WxChannelBaseResponse updateVipGrade(String openId, Integer score) throws WxErrorException { + VipGradeParam param = new VipGradeParam(openId, score); + String respJson = shopService.post(GRADE_UPDATE_URL, param); + return ResponseUtils.decode(respJson, WxChannelBaseResponse.class); + } +} 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 new file mode 100644 index 0000000000..6805f26a4f --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelWarehouseServiceImpl.java @@ -0,0 +1,123 @@ +package me.chanjar.weixin.channel.api.impl; + + +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Warehouse.ADD_COVER_AREA_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Warehouse.ADD_WAREHOUSE_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Warehouse.DELETE_COVER_AREA_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Warehouse.GET_WAREHOUSE_PRIORITY_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Warehouse.GET_WAREHOUSE_STOCK_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Warehouse.GET_WAREHOUSE_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Warehouse.LIST_WAREHOUSE_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Warehouse.SET_WAREHOUSE_PRIORITY_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Warehouse.UPDATE_WAREHOUSE_STOCK_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Warehouse.UPDATE_WAREHOUSE_URL; + +import java.util.List; +import lombok.extern.slf4j.Slf4j; +import me.chanjar.weixin.channel.api.WxChannelWarehouseService; +import me.chanjar.weixin.channel.bean.base.StreamPageParam; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; +import me.chanjar.weixin.channel.bean.warehouse.LocationPriorityResponse; +import me.chanjar.weixin.channel.bean.warehouse.PriorityLocationParam; +import me.chanjar.weixin.channel.bean.warehouse.StockGetParam; +import me.chanjar.weixin.channel.bean.warehouse.UpdateLocationParam; +import me.chanjar.weixin.channel.bean.warehouse.WarehouseIdsResponse; +import me.chanjar.weixin.channel.bean.warehouse.WarehouseLocation; +import me.chanjar.weixin.channel.bean.warehouse.WarehouseLocationParam; +import me.chanjar.weixin.channel.bean.warehouse.WarehouseParam; +import me.chanjar.weixin.channel.bean.warehouse.WarehouseResponse; +import me.chanjar.weixin.channel.bean.warehouse.WarehouseStockParam; +import me.chanjar.weixin.channel.bean.warehouse.WarehouseStockResponse; +import me.chanjar.weixin.channel.util.ResponseUtils; +import me.chanjar.weixin.common.error.WxErrorException; + +/** + * 视频号小店 区域仓库服务实现 + * + * @author Zeyes + */ +@Slf4j +public class WxChannelWarehouseServiceImpl implements WxChannelWarehouseService { + + /** 微信商店服务 */ + private final BaseWxChannelServiceImpl shopService; + + public WxChannelWarehouseServiceImpl(BaseWxChannelServiceImpl shopService) { + this.shopService = shopService; + } + + @Override + public WxChannelBaseResponse createWarehouse(WarehouseParam param) throws WxErrorException { + String resJson = shopService.post(ADD_WAREHOUSE_URL, param); + return ResponseUtils.decode(resJson, WxChannelBaseResponse.class); + } + + @Override + public WarehouseIdsResponse listWarehouse(Integer pageSize, String nextKey) throws WxErrorException { + StreamPageParam param = new StreamPageParam(pageSize, nextKey); + String resJson = shopService.post(LIST_WAREHOUSE_URL, param); + return ResponseUtils.decode(resJson, WarehouseIdsResponse.class); + } + + @Override + public WarehouseResponse getWarehouse(String outWarehouseId) throws WxErrorException { + String reqJson = "{\"out_warehouse_id\":\"" + outWarehouseId + "\"}"; + String resJson = shopService.post(GET_WAREHOUSE_URL, reqJson); + return ResponseUtils.decode(resJson, WarehouseResponse.class); + } + + @Override + public WxChannelBaseResponse updateWarehouse(String outWarehouseId, String name, String intro) + throws WxErrorException { + String reqJson = "{\"out_warehouse_id\":\"" + outWarehouseId + + "\",\"name\":\"" + name + "\",\"intro\":\"" + intro + "\"}"; + String resJson = shopService.post(UPDATE_WAREHOUSE_URL, reqJson); + return ResponseUtils.decode(resJson, WxChannelBaseResponse.class); + } + + @Override + public WxChannelBaseResponse addWarehouseArea(String outWarehouseId, List coverLocations) + throws WxErrorException { + UpdateLocationParam param = new UpdateLocationParam(outWarehouseId, coverLocations); + String resJson = shopService.post(ADD_COVER_AREA_URL, param); + return ResponseUtils.decode(resJson, WxChannelBaseResponse.class); + } + + @Override + public WxChannelBaseResponse deleteWarehouseArea(String outWarehouseId, List coverLocations) + throws WxErrorException { + UpdateLocationParam param = new UpdateLocationParam(outWarehouseId, coverLocations); + String resJson = shopService.post(DELETE_COVER_AREA_URL, param); + return ResponseUtils.decode(resJson, WxChannelBaseResponse.class); + + } + + @Override + public WxChannelBaseResponse setWarehousePriority(PriorityLocationParam param) throws WxErrorException { + String resJson = shopService.post(SET_WAREHOUSE_PRIORITY_URL, param); + return ResponseUtils.decode(resJson, WxChannelBaseResponse.class); + + } + + @Override + public LocationPriorityResponse getWarehousePriority(Integer addressId1, Integer addressId2, Integer addressId3, + Integer addressId4) throws WxErrorException { + WarehouseLocationParam param = new WarehouseLocationParam(addressId1, addressId2, addressId3, addressId4); + String resJson = shopService.post(GET_WAREHOUSE_PRIORITY_URL, param); + return ResponseUtils.decode(resJson, LocationPriorityResponse.class); + } + + @Override + public WxChannelBaseResponse updateWarehouseStock(WarehouseStockParam param) throws WxErrorException { + String resJson = shopService.post(UPDATE_WAREHOUSE_STOCK_URL, param); + return ResponseUtils.decode(resJson, WxChannelBaseResponse.class); + } + + @Override + public WarehouseStockResponse getWarehouseStock(String productId, String skuId, String outWarehouseId) + throws WxErrorException { + StockGetParam param = new StockGetParam(productId, skuId, outWarehouseId); + String resJson = shopService.post(GET_WAREHOUSE_STOCK_URL, param); + return ResponseUtils.decode(resJson, WarehouseStockResponse.class); + } +} 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 new file mode 100644 index 0000000000..51623609cf --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxFinderLiveServiceImpl.java @@ -0,0 +1,47 @@ +package me.chanjar.weixin.channel.api.impl; + + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import me.chanjar.weixin.channel.api.WxFinderLiveService; +import me.chanjar.weixin.channel.bean.lead.component.request.GetFinderLiveDataListRequest; +import me.chanjar.weixin.channel.bean.lead.component.request.GetFinderLiveLeadsDataRequest; +import me.chanjar.weixin.channel.bean.lead.component.response.FinderAttrResponse; +import me.chanjar.weixin.channel.bean.lead.component.response.GetFinderLiveDataListResponse; +import me.chanjar.weixin.channel.bean.lead.component.response.GetFinderLiveLeadsDataResponse; +import me.chanjar.weixin.channel.util.ResponseUtils; +import me.chanjar.weixin.common.error.WxErrorException; + +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.FinderLive.GET_FINDER_ATTR_BY_APPID; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.FinderLive.GET_FINDER_LIVE_DATA_LIST; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.FinderLive.GET_FINDER_LIVE_LEADS_DATA; + +/** + * 视频号助手 留资服务的直播数据服务 + * @author imyzt + * @date 2024/01/27 + */ +@RequiredArgsConstructor +@Slf4j +public class WxFinderLiveServiceImpl implements WxFinderLiveService { + + /** 微信商店服务 */ + private final BaseWxChannelServiceImpl shopService; + @Override + public FinderAttrResponse getFinderAttrByAppid() throws WxErrorException { + String resJson = shopService.post(GET_FINDER_ATTR_BY_APPID, "{}"); + return ResponseUtils.decode(resJson, FinderAttrResponse.class); + } + + @Override + public GetFinderLiveDataListResponse getFinderLiveDataList(GetFinderLiveDataListRequest req) throws WxErrorException { + String resJson = shopService.post(GET_FINDER_LIVE_DATA_LIST, req); + return ResponseUtils.decode(resJson, GetFinderLiveDataListResponse.class); + } + + @Override + public GetFinderLiveLeadsDataResponse getFinderLiveLeadsData(GetFinderLiveLeadsDataRequest req) throws WxErrorException { + String resJson = shopService.post(GET_FINDER_LIVE_LEADS_DATA, req); + return ResponseUtils.decode(resJson, GetFinderLiveLeadsDataResponse.class); + } +} 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 new file mode 100644 index 0000000000..eb1bcee28c --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxLeadComponentServiceImpl.java @@ -0,0 +1,96 @@ +package me.chanjar.weixin.channel.api.impl; + + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import me.chanjar.weixin.channel.api.WxLeadComponentService; +import me.chanjar.weixin.channel.bean.lead.component.request.GetLeadInfoByComponentRequest; +import me.chanjar.weixin.channel.bean.lead.component.request.GetLeadsComponentIdRequest; +import me.chanjar.weixin.channel.bean.lead.component.request.GetLeadsComponentPromoteRecordRequest; +import me.chanjar.weixin.channel.bean.lead.component.request.GetLeadsInfoByRequestIdRequest; +import me.chanjar.weixin.channel.bean.lead.component.request.GetLeadsRequestIdRequest; +import me.chanjar.weixin.channel.bean.lead.component.response.GetLeadsComponentIdResponse; +import me.chanjar.weixin.channel.bean.lead.component.response.GetLeadsComponentPromoteRecordResponse; +import me.chanjar.weixin.channel.bean.lead.component.response.GetLeadsRequestIdResponse; +import me.chanjar.weixin.channel.bean.lead.component.response.LeadInfoResponse; +import me.chanjar.weixin.channel.util.ResponseUtils; +import me.chanjar.weixin.common.error.WxErrorException; +import org.apache.commons.lang3.ObjectUtils; + +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.LeadComponent.GET_LEADS_COMPONENT_ID; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.LeadComponent.GET_LEADS_COMPONENT_PROMOTE_RECORD; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.LeadComponent.GET_LEADS_INFO_BY_COMPONENT_ID; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.LeadComponent.GET_LEADS_INFO_BY_REQUEST_ID; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.LeadComponent.GET_LEADS_REQUEST_ID; + +/** + * 视频号助手 留资组件管理服务 + * @author imyzt + * @date 2024/01/27 + */ +@RequiredArgsConstructor +@Slf4j +public class WxLeadComponentServiceImpl implements WxLeadComponentService { + + /** 微信商店服务 */ + private final BaseWxChannelServiceImpl shopService; + private final ObjectMapper objectMapper = new ObjectMapper(); + @Override + public LeadInfoResponse getLeadsInfoByComponentId(GetLeadInfoByComponentRequest req) throws WxErrorException { + req.setVersion(ObjectUtils.defaultIfNull(req.getVersion(), 1)); + String resJson = shopService.post(GET_LEADS_INFO_BY_COMPONENT_ID, req); + return this.convertLeadInfoResponse(resJson); + } + + @Override + public LeadInfoResponse getLeadsInfoByRequestId(GetLeadsInfoByRequestIdRequest req) throws WxErrorException { + req.setVersion(ObjectUtils.defaultIfNull(req.getVersion(), 1)); + String resJson = shopService.post(GET_LEADS_INFO_BY_REQUEST_ID, req); + return this.convertLeadInfoResponse(resJson); + } + + @Override + public GetLeadsRequestIdResponse getLeadsRequestId(GetLeadsRequestIdRequest req) throws WxErrorException { + String resJson = shopService.post(GET_LEADS_REQUEST_ID, req); + return ResponseUtils.decode(resJson, GetLeadsRequestIdResponse.class); + } + + @Override + public GetLeadsComponentPromoteRecordResponse getLeadsComponentPromoteRecord(GetLeadsComponentPromoteRecordRequest req) throws WxErrorException { + String resJson = shopService.post(GET_LEADS_COMPONENT_PROMOTE_RECORD, req); + return ResponseUtils.decode(resJson, GetLeadsComponentPromoteRecordResponse.class); + } + + @Override + public GetLeadsComponentIdResponse getLeadsComponentId(GetLeadsComponentIdRequest req) throws WxErrorException { + String resJson = shopService.post(GET_LEADS_COMPONENT_ID, req); + return ResponseUtils.decode(resJson, GetLeadsComponentIdResponse.class); + } + + /** + * 微信返回的数据中, user_data和leads_data均为字符串包裹的非标准JSON结构, 为方便业务使用避免踩坑此处做好解析 + */ + private LeadInfoResponse convertLeadInfoResponse(String resJson) throws WxErrorException { + try { + ObjectNode rootNode = (ObjectNode) objectMapper.readTree(resJson); + ArrayNode convertedUserDataArray = objectMapper.createArrayNode(); + for (JsonNode userDataEle : rootNode.get("user_data")) { + ObjectNode userDataJsonNode = (ObjectNode) objectMapper.readTree(userDataEle.asText()); + ArrayNode leadsDataArray = (ArrayNode) objectMapper.readTree(userDataJsonNode.get("leads_data").asText()); + userDataJsonNode.set("leads_data", leadsDataArray); + convertedUserDataArray.add(userDataJsonNode); + } + rootNode.set("user_data", convertedUserDataArray); + String json = objectMapper.writeValueAsString(rootNode); + return ResponseUtils.decode(json, LeadInfoResponse.class); + } catch (JsonProcessingException e) { + throw new WxErrorException(e); + } + } + +} 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 new file mode 100644 index 0000000000..fc8d2fbadc --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxLeagueProductServiceImpl.java @@ -0,0 +1,71 @@ +package me.chanjar.weixin.channel.api.impl; + + +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.League.BATCH_ADD_LEAGUE_ITEM_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.League.DELETE_LEAGUE_ITEM_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.League.GET_LEAGUE_ITEM_LIST_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.League.GET_LEAGUE_ITEM_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.League.UPDATE_LEAGUE_ITEM_URL; + +import lombok.extern.slf4j.Slf4j; +import me.chanjar.weixin.channel.api.WxLeagueProductService; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; +import me.chanjar.weixin.channel.bean.league.product.BatchAddParam; +import me.chanjar.weixin.channel.bean.league.product.BatchAddResponse; +import me.chanjar.weixin.channel.bean.league.product.ProductDeleteParam; +import me.chanjar.weixin.channel.bean.league.product.ProductDetailParam; +import me.chanjar.weixin.channel.bean.league.product.ProductDetailResponse; +import me.chanjar.weixin.channel.bean.league.product.ProductListParam; +import me.chanjar.weixin.channel.bean.league.product.ProductListResponse; +import me.chanjar.weixin.channel.bean.league.product.ProductUpdateParam; +import me.chanjar.weixin.channel.bean.league.product.ProductUpdateResponse; +import me.chanjar.weixin.channel.util.ResponseUtils; +import me.chanjar.weixin.common.error.WxErrorException; + + +/** + * 视频号小店 商品服务 + * + * @author Zeyes + */ +@Slf4j +public class WxLeagueProductServiceImpl implements WxLeagueProductService { + + /** 微信商店服务 */ + private final BaseWxChannelServiceImpl shopService; + + public WxLeagueProductServiceImpl(BaseWxChannelServiceImplshopService) { + this.shopService = shopService; + } + + @Override + public BatchAddResponse batchAddProduct(BatchAddParam param) throws WxErrorException { + String resJson = shopService.post(BATCH_ADD_LEAGUE_ITEM_URL, param); + return ResponseUtils.decode(resJson, BatchAddResponse.class); + } + + @Override + public ProductUpdateResponse updateProduct(ProductUpdateParam param) throws WxErrorException { + String resJson = shopService.post(UPDATE_LEAGUE_ITEM_URL, param); + return ResponseUtils.decode(resJson, ProductUpdateResponse.class); + } + + @Override + public WxChannelBaseResponse deleteProduct(Integer type, String productId, String infoId) throws WxErrorException { + ProductDeleteParam param = new ProductDeleteParam(type, productId, infoId); + String resJson = shopService.post(DELETE_LEAGUE_ITEM_URL, param); + return ResponseUtils.decode(resJson, WxChannelBaseResponse.class); + } + + @Override + public ProductDetailResponse getProductDetail(ProductDetailParam param) throws WxErrorException { + String resJson = shopService.post(GET_LEAGUE_ITEM_URL, param); + return ResponseUtils.decode(resJson, ProductDetailResponse.class); + } + + @Override + public ProductListResponse listProduct(ProductListParam param) throws WxErrorException { + String resJson = shopService.post(GET_LEAGUE_ITEM_LIST_URL, param); + return ResponseUtils.decode(resJson, ProductListResponse.class); + } +} 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 new file mode 100644 index 0000000000..c81df29533 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxLeaguePromoterServiceImpl.java @@ -0,0 +1,97 @@ +package me.chanjar.weixin.channel.api.impl; + +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.League.ADD_PROMOTER_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.League.DELETE_PROMOTER_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.League.EDIT_PROMOTER_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.League.GET_PROMOTER_LIST_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.League.GET_PROMOTER_URL; + +import lombok.extern.slf4j.Slf4j; +import me.chanjar.weixin.channel.api.WxLeaguePromoterService; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; +import me.chanjar.weixin.channel.bean.league.promoter.PromoterInfoResponse; +import me.chanjar.weixin.channel.bean.league.promoter.PromoterListParam; +import me.chanjar.weixin.channel.bean.league.promoter.PromoterListResponse; +import me.chanjar.weixin.channel.util.ResponseUtils; +import me.chanjar.weixin.common.error.WxErrorException; + +/** + * 视频号小店 达人服务 + * + * @author Zeyes + */ +@Slf4j +public class WxLeaguePromoterServiceImpl implements WxLeaguePromoterService { + + /** 微信商店服务 */ + private final BaseWxChannelServiceImpl shopService; + + public WxLeaguePromoterServiceImpl(BaseWxChannelServiceImpl shopService) { + this.shopService = shopService; + } + + @Override + public WxChannelBaseResponse addPromoter(String finderId) throws WxErrorException { + String reqJson = "{\"finder_id\":\"" + finderId + "\"}"; + String resJson = shopService.post(ADD_PROMOTER_URL, reqJson); + return ResponseUtils.decode(resJson, WxChannelBaseResponse.class); + } + + @Override + public WxChannelBaseResponse updatePromoter(String finderId, int type) throws WxErrorException { + String reqJson = "{\"finder_id\":\"" + finderId + "\",\"type\":" + type + "}"; + String resJson = shopService.post(EDIT_PROMOTER_URL, reqJson); + return ResponseUtils.decode(resJson, WxChannelBaseResponse.class); + } + + @Override + public WxChannelBaseResponse deletePromoter(String finderId) throws WxErrorException { + String reqJson = "{\"finder_id\":\"" + finderId + "\"}"; + String resJson = shopService.post(DELETE_PROMOTER_URL, reqJson); + return ResponseUtils.decode(resJson, WxChannelBaseResponse.class); + } + + @Override + public PromoterInfoResponse getPromoterInfo(String finderId) throws WxErrorException { + String reqJson = "{\"finder_id\":\"" + finderId + "\"}"; + String resJson = shopService.post(GET_PROMOTER_URL, reqJson); + 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 { + PromoterListParam param = new PromoterListParam(pageIndex, pageSize, status); + String resJson = shopService.post(GET_PROMOTER_LIST_URL, param); + return ResponseUtils.decode(resJson, PromoterListResponse.class); + + } +} 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 new file mode 100644 index 0000000000..2b280a2f6d --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxLeagueSupplierServiceImpl.java @@ -0,0 +1,107 @@ +package me.chanjar.weixin.channel.api.impl; + +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.League.GET_SUPPLIER_BALANCE_FLOW_DETAIL_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.League.GET_SUPPLIER_BALANCE_FLOW_LIST_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.League.GET_SUPPLIER_BALANCE_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.League.GET_SUPPLIER_ITEM_LIST_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.League.GET_SUPPLIER_ITEM_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.League.GET_SUPPLIER_ORDER_LIST_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.League.GET_SUPPLIER_ORDER_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.League.GET_SUPPLIER_SHOP_LIST_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.League.GET_SUPPLIER_SHOP_URL; + +import lombok.extern.slf4j.Slf4j; +import me.chanjar.weixin.channel.api.WxLeagueSupplierService; +import me.chanjar.weixin.channel.bean.base.StreamPageParam; +import me.chanjar.weixin.channel.bean.league.supplier.CommissionOrderListParam; +import me.chanjar.weixin.channel.bean.league.supplier.CommissionOrderListResponse; +import me.chanjar.weixin.channel.bean.league.supplier.CommissionOrderResponse; +import me.chanjar.weixin.channel.bean.league.supplier.CoopProductDetailParam; +import me.chanjar.weixin.channel.bean.league.supplier.CoopProductListParam; +import me.chanjar.weixin.channel.bean.league.supplier.CoopProductListResponse; +import me.chanjar.weixin.channel.bean.league.supplier.CoopProductResponse; +import me.chanjar.weixin.channel.bean.league.supplier.FlowListParam; +import me.chanjar.weixin.channel.bean.league.supplier.ShopDetailResponse; +import me.chanjar.weixin.channel.bean.league.supplier.ShopListResponse; +import me.chanjar.weixin.channel.bean.league.supplier.SupplierBalanceResponse; +import me.chanjar.weixin.channel.bean.league.supplier.SupplierFlowDetailResponse; +import me.chanjar.weixin.channel.bean.league.supplier.SupplierFlowListResponse; +import me.chanjar.weixin.channel.util.ResponseUtils; +import me.chanjar.weixin.common.error.WxErrorException; + +/** + * 视频号小店 优选联盟 团长数据服务 + * + * @author Zeyes + */ +@Slf4j +public class WxLeagueSupplierServiceImpl implements WxLeagueSupplierService { + + /** 微信商店服务 */ + private final BaseWxChannelServiceImpl shopService; + + public WxLeagueSupplierServiceImpl(BaseWxChannelServiceImpl shopService) { + this.shopService = shopService; + } + + @Override + public SupplierBalanceResponse getBalanceInfo() throws WxErrorException { + String resJson = shopService.post(GET_SUPPLIER_BALANCE_URL, "{}"); + return ResponseUtils.decode(resJson, SupplierBalanceResponse.class); + } + + @Override + public SupplierFlowDetailResponse getFlowDetail(String flowId) throws WxErrorException { + String reqJson = "{\"flow_id\":\"" + flowId + "\"}"; + String resJson = shopService.post(GET_SUPPLIER_BALANCE_FLOW_DETAIL_URL, reqJson); + return ResponseUtils.decode(resJson, SupplierFlowDetailResponse.class); + } + + @Override + public SupplierFlowListResponse getFlowList(FlowListParam param) throws WxErrorException { + String resJson = shopService.post(GET_SUPPLIER_BALANCE_FLOW_LIST_URL, param); + return ResponseUtils.decode(resJson, SupplierFlowListResponse.class); + } + + @Override + public CoopProductResponse getProductDetail(String productId, String appId) throws WxErrorException { + CoopProductDetailParam param = new CoopProductDetailParam(productId, appId); + String resJson = shopService.post(GET_SUPPLIER_ITEM_URL, param); + return ResponseUtils.decode(resJson, CoopProductResponse.class); + } + + @Override + public CoopProductListResponse getProductList(String appid, Integer pageSize, String nextKey) + throws WxErrorException { + CoopProductListParam param = new CoopProductListParam(appid, pageSize, nextKey); + String resJson = shopService.post(GET_SUPPLIER_ITEM_LIST_URL, param); + return ResponseUtils.decode(resJson, CoopProductListResponse.class); + } + + @Override + public CommissionOrderResponse getCommissionOrder(String orderId, String skuId) throws WxErrorException { + String reqJson = "{\"order_id\":\"" + orderId + "\",\"sku_id\":\"" + skuId + "\"}"; + String resJson = shopService.post(GET_SUPPLIER_ORDER_URL, reqJson); + return ResponseUtils.decode(resJson, CommissionOrderResponse.class); + } + + @Override + public CommissionOrderListResponse getCommissionOrderList(CommissionOrderListParam param) throws WxErrorException { + String resJson = shopService.post(GET_SUPPLIER_ORDER_LIST_URL, param); + return ResponseUtils.decode(resJson, CommissionOrderListResponse.class); + } + + @Override + public ShopDetailResponse getShopDetail(String appid) throws WxErrorException { + String reqJson = "{\"appid\":\"" + appid + "\"}"; + String resJson = shopService.post(GET_SUPPLIER_SHOP_URL, reqJson); + return ResponseUtils.decode(resJson, ShopDetailResponse.class); + } + + @Override + public ShopListResponse getShopList(Integer pageSize, String nextKey) throws WxErrorException { + StreamPageParam param = new StreamPageParam(pageSize, nextKey); + String resJson = shopService.post(GET_SUPPLIER_SHOP_LIST_URL, param); + return ResponseUtils.decode(resJson, ShopListResponse.class); + } +} 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 new file mode 100644 index 0000000000..a0c21ab4ef --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxLeagueWindowServiceImpl.java @@ -0,0 +1,81 @@ +package me.chanjar.weixin.channel.api.impl; + +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.League.ADD_SUPPLIER_GOODS_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.League.GET_SUPPLIER_AUTH_STATUS_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.League.GET_SUPPLIER_AUTH_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.League.GET_SUPPLIER_GOODS_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.League.LIST_SUPPLIER_GOODS_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.League.REMOVE_SUPPLIER_GOODS_URL; + +import lombok.extern.slf4j.Slf4j; +import me.chanjar.weixin.channel.api.WxLeagueWindowService; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; +import me.chanjar.weixin.channel.bean.league.window.AuthInfoResponse; +import me.chanjar.weixin.channel.bean.league.window.AuthStatusResponse; +import me.chanjar.weixin.channel.bean.league.window.ProductSearchParam; +import me.chanjar.weixin.channel.bean.league.window.WindowProductListResponse; +import me.chanjar.weixin.channel.bean.league.window.WindowProductParam; +import me.chanjar.weixin.channel.bean.league.window.WindowProductResponse; +import me.chanjar.weixin.channel.util.ResponseUtils; +import me.chanjar.weixin.common.error.WxErrorException; + + +/** + * 视频号小店 优选联盟 团长合作达人管理服务 + * + * @author Zeyes + */ +@Slf4j +public class WxLeagueWindowServiceImpl implements WxLeagueWindowService { + + /** 微信商店服务 */ + private final BaseWxChannelServiceImpl shopService; + + public WxLeagueWindowServiceImpl(BaseWxChannelServiceImpl shopService) { + this.shopService = shopService; + } + + @Override + public WxChannelBaseResponse addProduct(String appid, String openfinderid, String productId) + throws WxErrorException { + WindowProductParam param = new WindowProductParam(appid, openfinderid, productId); + String resJson = shopService.post(ADD_SUPPLIER_GOODS_URL, param); + return ResponseUtils.decode(resJson, WxChannelBaseResponse.class); + } + + @Override + public WindowProductListResponse listProduct(ProductSearchParam param) throws WxErrorException { + String resJson = shopService.post(LIST_SUPPLIER_GOODS_URL, param); + return ResponseUtils.decode(resJson, WindowProductListResponse.class); + } + + @Override + public WxChannelBaseResponse removeProduct(String appid, String openfinderid, String productId) + throws WxErrorException { + WindowProductParam param = new WindowProductParam(appid, openfinderid, productId); + String resJson = shopService.post(REMOVE_SUPPLIER_GOODS_URL, param); + return ResponseUtils.decode(resJson, WxChannelBaseResponse.class); + } + + @Override + public WindowProductResponse getProductDetail(String appid, String openfinderid, String productId) + throws WxErrorException { + WindowProductParam param = new WindowProductParam(appid, openfinderid, productId); + String resJson = shopService.post(GET_SUPPLIER_GOODS_URL, param); + return ResponseUtils.decode(resJson, WindowProductResponse.class); + } + + @Override + public AuthInfoResponse getWindowAuthInfo(String finderId) throws WxErrorException { + String reqJson = "{\"finder_id\":\"" + finderId + "\"}"; + String resJson = shopService.post(GET_SUPPLIER_AUTH_URL, reqJson); + return ResponseUtils.decode(resJson, AuthInfoResponse.class); + } + + @Override + public AuthStatusResponse getWindowAuthStatus(String finderId) throws WxErrorException { + String reqJson = "{\"finder_id\":\"" + finderId + "\"}"; + String resJson = shopService.post(GET_SUPPLIER_AUTH_STATUS_URL, reqJson); + return ResponseUtils.decode(resJson, AuthStatusResponse.class); + } +} 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 new file mode 100644 index 0000000000..56dc78e09e --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxStoreCooperationServiceImpl.java @@ -0,0 +1,68 @@ +package me.chanjar.weixin.channel.api.impl; + +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Cooperation.CANCEL_COOPERATION_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Cooperation.GENERATE_QRCODE_COOPERATION_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Cooperation.GET_COOPERATION_STATUS_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Cooperation.LIST_COOPERATION_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Cooperation.UNBIND_COOPERATION_URL; + +import lombok.extern.slf4j.Slf4j; +import me.chanjar.weixin.channel.api.WxStoreCooperationService; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; +import me.chanjar.weixin.channel.bean.cooperation.CooperationListResponse; +import me.chanjar.weixin.channel.bean.cooperation.CooperationQrCodeResponse; +import me.chanjar.weixin.channel.bean.cooperation.CooperationSharerParam; +import me.chanjar.weixin.channel.bean.cooperation.CooperationStatusResponse; +import me.chanjar.weixin.channel.util.ResponseUtils; +import me.chanjar.weixin.common.error.WxErrorException; + +/** + * 微信小店 合作账号相关接口 + * + * @author Zeyes + */ +@Slf4j +public class WxStoreCooperationServiceImpl implements WxStoreCooperationService { + + /** 微信小店服务 */ + private final BaseWxChannelServiceImpl storeService; + + public WxStoreCooperationServiceImpl(BaseWxChannelServiceImpl storeService) { + this.storeService = storeService; + } + + @Override + public CooperationListResponse listCooperation(Integer sharerType) throws WxErrorException { + String paramJson = "{\"sharer_type\":" + sharerType + "}"; + String resJson = storeService.post(LIST_COOPERATION_URL, paramJson); + return ResponseUtils.decode(resJson, CooperationListResponse.class); + } + + @Override + public CooperationStatusResponse getCooperationStatus(String sharerId, Integer sharerType) throws WxErrorException { + CooperationSharerParam param = new CooperationSharerParam(sharerId, sharerType); + String resJson = storeService.post(GET_COOPERATION_STATUS_URL, param); + return ResponseUtils.decode(resJson, CooperationStatusResponse.class); + } + + @Override + public CooperationQrCodeResponse generateQrCode(String sharerId, Integer sharerType) throws WxErrorException { + CooperationSharerParam param = new CooperationSharerParam(sharerId, sharerType); + String resJson = storeService.post(GENERATE_QRCODE_COOPERATION_URL, param); + return ResponseUtils.decode(resJson, CooperationQrCodeResponse.class); + } + + @Override + public WxChannelBaseResponse cancelInvitation(String sharerId, Integer sharerType) throws WxErrorException { + CooperationSharerParam param = new CooperationSharerParam(sharerId, sharerType); + String resJson = storeService.post(CANCEL_COOPERATION_URL, param); + return ResponseUtils.decode(resJson, WxChannelBaseResponse.class); + } + + @Override + public WxChannelBaseResponse unbind(String sharerId, Integer sharerType) throws WxErrorException { + CooperationSharerParam param = new CooperationSharerParam(sharerId, sharerType); + String resJson = storeService.post(UNBIND_COOPERATION_URL, param); + return ResponseUtils.decode(resJson, WxChannelBaseResponse.class); + } +} 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 new file mode 100644 index 0000000000..e3e9f06deb --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxStoreHomePageServiceImpl.java @@ -0,0 +1,164 @@ +package me.chanjar.weixin.channel.api.impl; + +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.HomePage.*; + + +import lombok.extern.slf4j.Slf4j; +import me.chanjar.weixin.channel.api.WxStoreHomePageService; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; +import me.chanjar.weixin.channel.bean.home.background.BackgroundApplyResponse; +import me.chanjar.weixin.channel.bean.home.background.BackgroundGetResponse; +import me.chanjar.weixin.channel.bean.home.banner.BannerApplyParam; +import me.chanjar.weixin.channel.bean.home.banner.BannerApplyResponse; +import me.chanjar.weixin.channel.bean.home.banner.BannerGetResponse; +import me.chanjar.weixin.channel.bean.home.banner.BannerInfo; +import me.chanjar.weixin.channel.bean.home.tree.TreeProductEditInfo; +import me.chanjar.weixin.channel.bean.home.tree.TreeProductEditParam; +import me.chanjar.weixin.channel.bean.home.tree.TreeProductListInfo; +import me.chanjar.weixin.channel.bean.home.tree.TreeProductListParam; +import me.chanjar.weixin.channel.bean.home.tree.TreeProductListResponse; +import me.chanjar.weixin.channel.bean.home.tree.TreeShowGetResponse; +import me.chanjar.weixin.channel.bean.home.tree.TreeShowInfo; +import me.chanjar.weixin.channel.bean.home.tree.TreeShowParam; +import me.chanjar.weixin.channel.bean.home.tree.TreeShowSetResponse; +import me.chanjar.weixin.channel.bean.home.window.WindowProductIndexParam; +import me.chanjar.weixin.channel.bean.home.window.WindowProductListParam; +import me.chanjar.weixin.channel.bean.home.window.WindowProductSetting; +import me.chanjar.weixin.channel.bean.home.window.WindowProductSettingResponse; +import me.chanjar.weixin.channel.util.ResponseUtils; +import me.chanjar.weixin.common.error.WxErrorException; + +/** + * 微信小店 主页管理相关接口 + * + * @author Zeyes + */ +@Slf4j +public class WxStoreHomePageServiceImpl implements WxStoreHomePageService { + + /** 微信小店服务 */ + private final BaseWxChannelServiceImpl storeService; + + public WxStoreHomePageServiceImpl(BaseWxChannelServiceImpl storeService) { + this.storeService = storeService; + } + + + @Override + public WxChannelBaseResponse addTreeProduct(TreeProductEditInfo info) throws WxErrorException { + TreeProductEditParam param = new TreeProductEditParam(info); + String resJson = storeService.post(ADD_TREE_PRODUCT_URL, param); + return ResponseUtils.decode(resJson, WxChannelBaseResponse.class); + } + + @Override + public WxChannelBaseResponse delTreeProduct(TreeProductEditInfo info) throws WxErrorException { + TreeProductEditParam param = new TreeProductEditParam(info); + String resJson = storeService.post(DEL_TREE_PRODUCT_URL, param); + return ResponseUtils.decode(resJson, WxChannelBaseResponse.class); + } + + @Override + public TreeProductListResponse getTreeProductList(TreeProductListInfo info) throws WxErrorException { + TreeProductListParam param = new TreeProductListParam(info); + String resJson = storeService.post(LIST_TREE_PRODUCT_URL, param); + return ResponseUtils.decode(resJson, TreeProductListResponse.class); + } + + @Override + public TreeShowSetResponse setShowTree(TreeShowInfo info) throws WxErrorException { + TreeShowParam param = new TreeShowParam(info); + String resJson = storeService.post(SET_SHOW_TREE_URL, param); + return ResponseUtils.decode(resJson, TreeShowSetResponse.class); + } + + @Override + public TreeShowGetResponse getShowTree() throws WxErrorException { + String resJson = storeService.post(GET_SHOW_TREE_URL, ""); + return ResponseUtils.decode(resJson, TreeShowGetResponse.class); + } + + @Override + public WindowProductSettingResponse listWindowProduct(Integer pageSize, String nextKey) throws WxErrorException { + WindowProductListParam param = new WindowProductListParam(pageSize, nextKey); + String resJson = storeService.post(LIST_WINDOW_PRODUCT_URL, param); + return ResponseUtils.decode(resJson, WindowProductSettingResponse.class); + } + + @Override + public WxChannelBaseResponse reorderWindowProduct(String productId, Integer indexNum) throws WxErrorException { + WindowProductIndexParam param = new WindowProductIndexParam(productId, indexNum); + String resJson = storeService.post(REORDER_WINDOW_PRODUCT_URL, param); + return ResponseUtils.decode(resJson, WxChannelBaseResponse.class); + } + + @Override + public WxChannelBaseResponse hideWindowProduct(String productId, Integer setHide) throws WxErrorException { + WindowProductSetting param = new WindowProductSetting(); + param.setProductId(productId); + param.setSetHide(setHide); + String resJson = storeService.post(HIDE_WINDOW_PRODUCT_URL, param); + return ResponseUtils.decode(resJson, WxChannelBaseResponse.class); + } + + @Override + public WxChannelBaseResponse topWindowProduct(String productId, Integer setTop) throws WxErrorException { + WindowProductSetting param = new WindowProductSetting(); + param.setProductId(productId); + param.setSetTop(setTop); + String resJson = storeService.post(TOP_WINDOW_PRODUCT_URL, param); + return ResponseUtils.decode(resJson, WxChannelBaseResponse.class); + } + + @Override + public BackgroundApplyResponse applyBackground(String imgUrl) throws WxErrorException { + String paramJson = "{\"img_url\":\"" + imgUrl + "\"}"; + String resJson = storeService.post(APPLY_BACKGROUND_URL, paramJson); + return ResponseUtils.decode(resJson, BackgroundApplyResponse.class); + } + + @Override + public BackgroundGetResponse getBackground() throws WxErrorException { + String resJson = storeService.post(GET_BACKGROUND_URL, ""); + return ResponseUtils.decode(resJson, BackgroundGetResponse.class); + } + + @Override + public WxChannelBaseResponse cancelBackground(Integer applyId) throws WxErrorException { + String paramJson = "{\"apply_id\":" + applyId + "}"; + String resJson = storeService.post(CANCEL_BACKGROUND_URL, paramJson); + return ResponseUtils.decode(resJson, WxChannelBaseResponse.class); + } + + @Override + public WxChannelBaseResponse removeBackground() throws WxErrorException { + String resJson = storeService.post(REMOVE_BACKGROUND_URL, ""); + return ResponseUtils.decode(resJson, WxChannelBaseResponse.class); + } + + @Override + public BannerApplyResponse applyBanner(BannerInfo info) throws WxErrorException { + BannerApplyParam param = new BannerApplyParam(info); + String resJson = storeService.post(APPLY_BANNER_URL, param); + return ResponseUtils.decode(resJson, BannerApplyResponse.class); + } + + @Override + public BannerGetResponse getBanner() throws WxErrorException { + String resJson = storeService.post(GET_BANNER_URL, ""); + return ResponseUtils.decode(resJson, BannerGetResponse.class); + } + + @Override + public WxChannelBaseResponse cancelBanner(Integer applyId) throws WxErrorException { + String paramJson = "{\"apply_id\":" + applyId + "}"; + String resJson = storeService.post(CANCEL_BANNER_URL, paramJson); + return ResponseUtils.decode(resJson, WxChannelBaseResponse.class); + } + + @Override + public WxChannelBaseResponse removeBanner() throws WxErrorException { + String resJson = storeService.post(REMOVE_BANNER_URL, ""); + return ResponseUtils.decode(resJson, WxChannelBaseResponse.class); + } +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/address/AddressAddParam.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/address/AddressAddParam.java new file mode 100644 index 0000000000..a831de6655 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/address/AddressAddParam.java @@ -0,0 +1,27 @@ +package me.chanjar.weixin.channel.bean.address; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 地址 请求参数 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@JsonInclude(Include.NON_NULL) +public class AddressAddParam implements Serializable { + + private static final long serialVersionUID = 6778585213498438738L; + + /** 地址id */ + @JsonProperty("address_detail") + private AddressDetail addressDetail; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/address/AddressCode.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/address/AddressCode.java new file mode 100644 index 0000000000..c7c885f0ab --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/address/AddressCode.java @@ -0,0 +1,30 @@ +package me.chanjar.weixin.channel.bean.address; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 地址编码 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +public class AddressCode implements Serializable { + + private static final long serialVersionUID = -6782328785056142627L; + + /** 地址名称 */ + @JsonProperty("name") + private String name; + + /** 地址行政编码 */ + @JsonProperty("code") + private Integer code; + + /** 地址级别 1-省级 2-市级 3-区县级 4-街道 */ + @JsonProperty("level") + private Integer level; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/address/AddressCodeResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/address/AddressCodeResponse.java new file mode 100644 index 0000000000..09ede50c38 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/address/AddressCodeResponse.java @@ -0,0 +1,29 @@ +package me.chanjar.weixin.channel.bean.address; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.List; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; + +/** + * 地址编码 响应 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class AddressCodeResponse extends WxChannelBaseResponse { + + private static final long serialVersionUID = -8994407971295563982L; + + /** 本行政编码地址信息 */ + @JsonProperty("addrs_msg") + private AddressCode current; + + /** 下一级所有地址信息 */ + @JsonProperty("next_level_addrs") + private List list; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/address/AddressDetail.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/address/AddressDetail.java new file mode 100644 index 0000000000..88f4945e20 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/address/AddressDetail.java @@ -0,0 +1,66 @@ +package me.chanjar.weixin.channel.bean.address; + +import com.fasterxml.jackson.annotation.JsonInclude; +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 +@JsonInclude(JsonInclude.Include.NON_NULL) +public class AddressDetail implements Serializable { + + private static final long serialVersionUID = -7839578838482198641L; + + /** 地址id */ + @JsonProperty("address_id") + private String addressId; + + /** 联系人姓名 */ + @JsonProperty("name") + private String name; + + /** 地区信息 */ + @JsonProperty("address_info") + private AddressInfo addressInfo; + + /** 座机 */ + @JsonProperty("landline") + private String landline; + + /** 是否为发货地址 */ + @JsonProperty("send_addr") + private Boolean sendAddr; + + /** 是否为收货地址 */ + @JsonProperty("recv_addr") + private Boolean recvAddr; + + /** 是否为默认发货地址 */ + @JsonProperty("default_send") + private Boolean defaultSend; + + /** 是否为默认收货地址 */ + @JsonProperty("default_recv") + private Boolean defaultRecv; + + /** 创建时间戳(秒) */ + @JsonProperty("create_time") + private Long createTime; + + /** 更新时间戳(秒) */ + @JsonProperty("update_time") + private Long updateTime; + + /** 线下配送地址类型 */ + @JsonProperty("address_type") + private OfflineAddressType addressType; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/address/AddressIdParam.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/address/AddressIdParam.java new file mode 100644 index 0000000000..d1eb7e0b46 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/address/AddressIdParam.java @@ -0,0 +1,27 @@ +package me.chanjar.weixin.channel.bean.address; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 地址id 请求参数 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@JsonInclude(Include.NON_NULL) +public class AddressIdParam implements Serializable { + + private static final long serialVersionUID = -7001183932180608746L; + + /** 地址id */ + @JsonProperty("address_id") + private String addressId; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/address/AddressIdResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/address/AddressIdResponse.java new file mode 100644 index 0000000000..f6505efa15 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/address/AddressIdResponse.java @@ -0,0 +1,26 @@ +package me.chanjar.weixin.channel.bean.address; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; + +/** + * 地址id 响应 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class AddressIdResponse extends WxChannelBaseResponse { + + private static final long serialVersionUID = -9218327846685744008L; + + /** 地址id */ + @JsonProperty("address_id") + private String addressId; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/address/AddressInfoResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/address/AddressInfoResponse.java new file mode 100644 index 0000000000..957d0162a8 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/address/AddressInfoResponse.java @@ -0,0 +1,24 @@ +package me.chanjar.weixin.channel.bean.address; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; + +/** + * 地址id 响应 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class AddressInfoResponse extends WxChannelBaseResponse { + + private static final long serialVersionUID = 8203853673226715673L; + + /** 地址详情 */ + @JsonProperty("address_detail") + private AddressDetail addressDetail; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/address/AddressListParam.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/address/AddressListParam.java new file mode 100644 index 0000000000..c62cf39fb8 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/address/AddressListParam.java @@ -0,0 +1,26 @@ +package me.chanjar.weixin.channel.bean.address; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.bean.base.OffsetParam; + +/** + * 用户地址 列表 请求参数 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +@JsonInclude(Include.NON_NULL) +public class AddressListParam extends OffsetParam { + + private static final long serialVersionUID = -4434287264623932176L; + + public AddressListParam(Integer offset, Integer limit) { + super(offset, limit); + } +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/address/AddressListResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/address/AddressListResponse.java new file mode 100644 index 0000000000..b8846f9aa3 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/address/AddressListResponse.java @@ -0,0 +1,26 @@ +package me.chanjar.weixin.channel.bean.address; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.List; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; + +/** + * 地址列表 响应 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class AddressListResponse extends WxChannelBaseResponse { + + private static final long serialVersionUID = -3997164605170764105L; + + /** 地址详情 */ + @JsonProperty("address_id_list") + private List ids; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/address/OfflineAddressType.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/address/OfflineAddressType.java new file mode 100644 index 0000000000..81dd169399 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/address/OfflineAddressType.java @@ -0,0 +1,28 @@ +package me.chanjar.weixin.channel.bean.address; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 线下配送地址类型 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class OfflineAddressType implements Serializable { + + private static final long serialVersionUID = 636850757572901377L; + + /** 1表示同城配送 */ + @JsonProperty("same_city") + private Integer sameCity; + + /** 1表示用户自提 */ + @JsonProperty("pickup") + private Integer pickup; +} 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/AfterSaleAcceptParam.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/after/AfterSaleAcceptParam.java new file mode 100644 index 0000000000..32ad9154ee --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/after/AfterSaleAcceptParam.java @@ -0,0 +1,39 @@ +package me.chanjar.weixin.channel.bean.after; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +/** + * 售后单同意信息 + * + * @author Zeyes + */ +@Data +@JsonInclude(Include.NON_NULL) +public class AfterSaleAcceptParam extends AfterSaleIdParam { + + private static final long serialVersionUID = -4352801757159074950L; + /** 同意退货时传入地址id */ + @JsonProperty("address_id") + private String addressId; + + /** 针对退货退款同意售后的阶段: 1. 同意退货退款,并通知用户退货; 2. 确认收到货并退款给用户。 如果不填则将根据当前的售后单状态自动选择相应操作。对于仅退款的情况,由于只存在一种同意的场景,无需填写此字段。*/ + @JsonProperty("accept_type") + private Integer acceptType; + + public AfterSaleAcceptParam() { + } + + public AfterSaleAcceptParam(String afterSaleOrderId, String addressId) { + super(afterSaleOrderId); + this.addressId = addressId; + } + + public AfterSaleAcceptParam(String afterSaleOrderId, String addressId, Integer acceptType) { + super(afterSaleOrderId); + this.addressId = addressId; + this.acceptType = acceptType; + } +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/after/AfterSaleDetail.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/after/AfterSaleDetail.java new file mode 100644 index 0000000000..aa1e7b400f --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/after/AfterSaleDetail.java @@ -0,0 +1,42 @@ +package me.chanjar.weixin.channel.bean.after; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import java.util.List; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 售后详情 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +public class AfterSaleDetail implements Serializable { + + private static final long serialVersionUID = -8130659179770831047L; + /** 售后描述 */ + @JsonProperty("desc") + private String desc; + + /** 是否已经收到货 */ + @JsonProperty("receive_product") + private Boolean receiveProduct; + + /** 是否已经收到货 */ + @JsonProperty("cancel_time") + private Long cancelTime; + + /** 举证图片media_id列表,根据mediaid获取文件内容接口 */ + @JsonProperty("prove_imgs") + private List proveImgs; + + /** 联系电话 */ + @JsonProperty("tel_number") + private String telNumber; + + /** 举证图片media_id列表,根据mediaid获取文件内容接口 */ + @JsonProperty("media_id_list") + private List mediaIdList; +} 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/AfterSaleIdParam.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/after/AfterSaleIdParam.java new file mode 100644 index 0000000000..1e16a72395 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/after/AfterSaleIdParam.java @@ -0,0 +1,26 @@ +package me.chanjar.weixin.channel.bean.after; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 售后单id信息 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@JsonInclude(Include.NON_NULL) +public class AfterSaleIdParam implements Serializable { + + private static final long serialVersionUID = 4974332291476116540L; + /** 售后单号 */ + @JsonProperty("after_sale_order_id") + private String afterSaleOrderId; +} 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 new file mode 100644 index 0000000000..d465766d75 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/after/AfterSaleInfo.java @@ -0,0 +1,101 @@ +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 AfterSaleInfo implements Serializable { + + private static final long serialVersionUID = 6595670817781635247L; + /** 售后单号 */ + @JsonProperty("after_sale_order_id") + private String afterSaleOrderId; + + /** 售后状态 {@link me.chanjar.weixin.channel.enums.AfterSaleStatus} */ + @JsonProperty("status") + private String status; + + /** 订单id */ + @JsonProperty("order_id") + private String orderId; + + /** 买家身份标识 */ + @JsonProperty("openid") + private String openid; + + /** 买家在开放平台的唯一标识符,若当前视频号小店已绑定到微信开放平台帐号下会返回 */ + @JsonProperty("unionid") + private String unionid; + + /** 售后相关商品信息 */ + @JsonProperty("product_info") + private AfterSaleProductInfo productInfo; + + /** 售后详情 */ + @JsonProperty("details") + private AfterSaleDetail details; + + /** 退款详情 */ + @JsonProperty("refund_info") + private RefundInfo refundInfo; + + /** 用户退货信息 */ + @JsonProperty("return_info") + private ReturnInfo returnInfo; + + /** 商家上传的信息 */ + @JsonProperty("merchant_upload_info") + private MerchantUploadInfo merchantUploadInfo; + + /** 创建时间 时间戳 秒 */ + @JsonProperty("create_time") + private Long createTime; + + /** 更新时间 时间戳 秒 */ + @JsonProperty("update_time") + private Long updateTime; + + /** 退款原因(后续新增的原因将不再有字面含义,请参考reason_text) */ + @JsonProperty("reason") + private String reason; + + /** 退款原因解释 */ + @JsonProperty("reason_text") + private String reasonText; + + /** 退款结果 */ + @JsonProperty("refund_resp") + private RefundResp refundResp; + + /** 售后类型。REFUND:退款;RETURN:退货退款 */ + @JsonProperty("type") + private String type; + + /** 纠纷id,该字段可用于获取纠纷信息 */ + @JsonProperty("complaint_id") + private String complaintId; + + /** 仅在待商家审核退款退货申请或收货期间返回,表示操作剩余时间(秒数)*/ + @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/AfterSaleInfoResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/after/AfterSaleInfoResponse.java new file mode 100644 index 0000000000..adedf72f03 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/after/AfterSaleInfoResponse.java @@ -0,0 +1,23 @@ +package me.chanjar.weixin.channel.bean.after; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; + +/** + * 售后单 响应 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class AfterSaleInfoResponse extends WxChannelBaseResponse { + + private static final long serialVersionUID = -752661975153491902L; + /** 售后单 */ + @JsonProperty("after_sale_order") + private AfterSaleInfo info; +} 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 new file mode 100644 index 0000000000..a477a2c581 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/after/AfterSaleListParam.java @@ -0,0 +1,42 @@ +package me.chanjar.weixin.channel.bean.after; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 售后单列表 请求参数 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@JsonInclude(Include.NON_NULL) +public class AfterSaleListParam implements Serializable { + + private static final long serialVersionUID = -103549981452112069L; + /** 订单创建启始时间 unix时间戳 */ + @JsonProperty("begin_create_time") + private Long beginCreateTime; + + /** 订单创建结束时间,end_create_time减去begin_create_time不得大于24小时 unix时间戳 */ + @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/AfterSaleListResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/after/AfterSaleListResponse.java new file mode 100644 index 0000000000..dde39238a7 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/after/AfterSaleListResponse.java @@ -0,0 +1,32 @@ +package me.chanjar.weixin.channel.bean.after; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.List; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; + +/** + * 售后单列表 响应 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class AfterSaleListResponse extends WxChannelBaseResponse { + + private static final long serialVersionUID = 5033313416948732123L; + /** 售后单号列表 */ + @JsonProperty("after_sale_order_id_list") + private List ids; + + /** 翻页参数 */ + @JsonProperty("next_key") + private String nextKey; + + /** 是否还有数据 */ + @JsonProperty("has_more") + private Boolean hasMore; +} 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/AfterSaleProductInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/after/AfterSaleProductInfo.java new file mode 100644 index 0000000000..ffcaf320ca --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/after/AfterSaleProductInfo.java @@ -0,0 +1,29 @@ +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 AfterSaleProductInfo implements Serializable { + + private static final long serialVersionUID = 4205179093262757775L; + /** 商品spu id */ + @JsonProperty("product_id") + private String productId; + + /** 商品sku id */ + @JsonProperty("sku_id") + private String skuId; + + /** 售后数量 */ + @JsonProperty("count") + private Integer count; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/after/AfterSaleReason.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/after/AfterSaleReason.java new file mode 100644 index 0000000000..7c66eff18f --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/after/AfterSaleReason.java @@ -0,0 +1,33 @@ +package me.chanjar.weixin.channel.bean.after; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * 全量售后原因 + * + * @author lizhengwu + * @date 2024/7/24 + */ +@Data +@NoArgsConstructor +public class AfterSaleReason implements Serializable { + + private static final long serialVersionUID = -3674527884494606230L; + + /** + * 售后原因枚举 + */ + @JsonProperty("reason") + private Integer reason; + + /** + * 售后原因说明 + */ + @JsonProperty("reason_text") + private String reasonText; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/after/AfterSaleReasonResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/after/AfterSaleReasonResponse.java new file mode 100644 index 0000000000..7372dea1f1 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/after/AfterSaleReasonResponse.java @@ -0,0 +1,28 @@ +package me.chanjar.weixin.channel.bean.after; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; + +import java.util.List; + +/** + * 售后原因 + * + * + * @author lizhengwu + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode +public class AfterSaleReasonResponse extends WxChannelBaseResponse { + + + private static final long serialVersionUID = -580378623915041396L; + + @JsonProperty("reason_list") + private List reasonList; + +} 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/AfterSaleRejectParam.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/after/AfterSaleRejectParam.java new file mode 100644 index 0000000000..cbde459fea --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/after/AfterSaleRejectParam.java @@ -0,0 +1,43 @@ +package me.chanjar.weixin.channel.bean.after; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +/** + * 售后单拒绝信息 + * + * @author Zeyes + */ +@Data +@JsonInclude(Include.NON_NULL) +public class AfterSaleRejectParam extends AfterSaleIdParam { + + private static final long serialVersionUID = -7507483859864253314L; + /** + * 拒绝原因 + */ + @JsonProperty("reject_reason") + private String rejectReason; + + /** + * 拒绝原因枚举值 + */ + @JsonProperty("reject_reason_type") + private Integer rejectReasonType; + + public AfterSaleRejectParam() { + } + + public AfterSaleRejectParam(String afterSaleOrderId, String rejectReason) { + super(afterSaleOrderId); + this.rejectReason = rejectReason; + } + + public AfterSaleRejectParam(String afterSaleOrderId, String rejectReason, Integer rejectReasonType) { + super(afterSaleOrderId); + this.rejectReason = rejectReason; + this.rejectReasonType = rejectReasonType; + } +} 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 new file mode 100644 index 0000000000..7987153ec0 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/after/AfterSaleRejectReason.java @@ -0,0 +1,44 @@ +package me.chanjar.weixin.channel.bean.after; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * 拒绝售后原因 + * + * @author lizhengwu + * @date 2024/7/24 + */ +@Data +@NoArgsConstructor +public class AfterSaleRejectReason implements Serializable { + + private static final long serialVersionUID = -3672834150982780L; + + /** + * 售后拒绝原因枚举 + */ + @JsonProperty("reject_reason_type") + private Integer rejectReasonType; + + /** + * 售后拒绝原因说明 + */ + @JsonProperty("reject_reason_type_text") + private String rejectReasonTypeText; + + /** + * 售后拒绝原因默认描述 + */ + @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/AfterSaleRejectReasonResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/after/AfterSaleRejectReasonResponse.java new file mode 100644 index 0000000000..7b50691d00 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/after/AfterSaleRejectReasonResponse.java @@ -0,0 +1,29 @@ +package me.chanjar.weixin.channel.bean.after; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; + +import java.util.List; + +/** + * 售后原因 + * + * @author lizhengwu + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode +public class AfterSaleRejectReasonResponse extends WxChannelBaseResponse { + + private static final long serialVersionUID = -7946679037747710613L; + + /** + * 售后原因列表 + */ + @JsonProperty("reject_reason_list") + private List rejectReasonList; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/after/AfterSaleReturnParam.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/after/AfterSaleReturnParam.java new file mode 100644 index 0000000000..47e815c8dd --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/after/AfterSaleReturnParam.java @@ -0,0 +1,36 @@ +package me.chanjar.weixin.channel.bean.after; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.Data; +import me.chanjar.weixin.channel.bean.base.AddressInfo; + +/** + * 退货信息 + * + * @author Zeyes + */ +@Data +public class AfterSaleReturnParam implements Serializable { + + private static final long serialVersionUID = -1101993925465293521L; + /** 微信侧售后单号 */ + @JsonProperty("aftersale_id") + private Long afterSaleId; + + /** 外部售后单号,和aftersale_id二选一 */ + @JsonProperty("out_aftersale_id") + private String outAfterSaleId; + + /** 商家收货地址 */ + @JsonProperty("address_info") + private AddressInfo addressInfo; + + public AfterSaleReturnParam() { + } + + public AfterSaleReturnParam(Long afterSaleId, String outAfterSaleId) { + this.outAfterSaleId = outAfterSaleId; + this.afterSaleId = afterSaleId; + } +} 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/after/MerchantUploadInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/after/MerchantUploadInfo.java new file mode 100644 index 0000000000..805c3a3f6e --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/after/MerchantUploadInfo.java @@ -0,0 +1,27 @@ +package me.chanjar.weixin.channel.bean.after; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import java.util.List; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 商家上传的信息 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +public class MerchantUploadInfo implements Serializable { + + private static final long serialVersionUID = 373513419356603563L; + /** 拒绝原因 */ + @JsonProperty("reject_reason") + private String rejectReason; + + /** 退款凭证 */ + @JsonProperty("refund_certificates") + private List refundCertificates; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/after/RefundEvidenceParam.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/after/RefundEvidenceParam.java new file mode 100644 index 0000000000..c81ae042d4 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/after/RefundEvidenceParam.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 java.io.Serializable; +import java.util.List; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 退款凭证信息 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@JsonInclude(JsonInclude.Include.NON_NULL) +public class RefundEvidenceParam implements Serializable { + + private static final long serialVersionUID = 2117305897849528009L; + /** 售后单号 */ + @JsonProperty("after_sale_order_id") + private String afterSaleOrderId; + + /** 描述 */ + @JsonProperty("desc") + private String desc; + + /** 凭证图片列表 */ + @JsonProperty("refund_certificates") + private List certificates; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/after/RefundInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/after/RefundInfo.java new file mode 100644 index 0000000000..73aedf99cf --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/after/RefundInfo.java @@ -0,0 +1,25 @@ +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 RefundInfo implements Serializable { + + private static final long serialVersionUID = -6994243947898889309L; + /** 退款金额(分) */ + @JsonProperty("amount") + private Integer amount; + + /** 标明售后单退款直接原因, 枚举值详情请参考 {@link me.chanjar.weixin.channel.enums.RefundReason} */ + @JsonProperty("refund_reason") + private Integer refundReason; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/after/RefundResp.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/after/RefundResp.java new file mode 100644 index 0000000000..83b7039a77 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/after/RefundResp.java @@ -0,0 +1,30 @@ +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 RefundResp implements Serializable { + + private static final long serialVersionUID = 6549707043779644156L; + /** code */ + @JsonProperty("code") + private String code; + + /** ret */ + @JsonProperty("ret") + private Integer ret; + + /** message */ + @JsonProperty("message") + private String message; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/after/ReturnInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/after/ReturnInfo.java new file mode 100644 index 0000000000..08238d5484 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/after/ReturnInfo.java @@ -0,0 +1,29 @@ +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 ReturnInfo implements Serializable { + + private static final long serialVersionUID = 1643844664701376892L; + /** 快递单号 */ + @JsonProperty("waybill_id") + private String waybillId; + + /** 物流公司id */ + @JsonProperty("delivery_id") + private String deliveryId; + + /** 物流公司名称 */ + @JsonProperty("delivery_name") + private String deliveryName; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/audit/AuditApplyResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/audit/AuditApplyResponse.java new file mode 100644 index 0000000000..547207c82b --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/audit/AuditApplyResponse.java @@ -0,0 +1,24 @@ +package me.chanjar.weixin.channel.bean.audit; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; + +/** + * 审核提交结果响应 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class AuditApplyResponse extends WxChannelBaseResponse { + + private static final long serialVersionUID = -3950614749162384497L; + + /** 类目列表 */ + @JsonProperty("audit_id") + private String auditId; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/audit/AuditResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/audit/AuditResponse.java new file mode 100644 index 0000000000..3ef07387d1 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/audit/AuditResponse.java @@ -0,0 +1,24 @@ +package me.chanjar.weixin.channel.bean.audit; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; + +/** + * 审核结果响应 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class AuditResponse extends WxChannelBaseResponse { + + private static final long serialVersionUID = 9218713381520774914L; + + /** 审核结果 1:审核中,3:审核成功,2:审核拒绝,12:主动取消申请单 */ + @JsonProperty("data") + private AuditResult data; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/audit/AuditResult.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/audit/AuditResult.java new file mode 100644 index 0000000000..89aaa8a267 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/audit/AuditResult.java @@ -0,0 +1,26 @@ +package me.chanjar.weixin.channel.bean.audit; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 审核结果 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +public class AuditResult implements Serializable { + + private static final long serialVersionUID = 1846416634865665240L; + + /** 审核状态, 0:审核中,1:审核成功,9:审核拒绝, 12:主动取消 */ + @JsonProperty("status") + private Integer status; + + /** 如果审核拒绝,返回拒绝原因 */ + @JsonProperty("reject_reason") + private String rejectReason; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/audit/CategoryAuditInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/audit/CategoryAuditInfo.java new file mode 100644 index 0000000000..485092704d --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/audit/CategoryAuditInfo.java @@ -0,0 +1,79 @@ +package me.chanjar.weixin.channel.bean.audit; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import java.util.List; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 类目审核信息 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@JsonInclude(JsonInclude.Include.NON_NULL) +public class CategoryAuditInfo implements Serializable { + + private static final long serialVersionUID = -8792967130645424788L; + + /** 一级类目,字符类型,最长不超过10 */ + @JsonProperty("level1") + private Long level1; + + /** 二级类目,字符类型,最长不超过10 */ + @JsonProperty("level2") + private Long level2; + + /** 三级类目,字符类型,最长不超过10 */ + @JsonProperty("level3") + private Long level3; + + /** 新类目树类目ID */ + @JsonProperty("cats_v2") + private List catsV2; + + /** 资质材料,图片fileid,图片类型,最多不超过10张 */ + @JsonProperty("certificate") + private List certificates; + + /** 报备函,图片fileid,图片类型,最多不超过10张 */ + @JsonProperty("baobeihan") + private List baobeihan; + + /** 经营证明,图片fileid,图片类型,最多不超过10张 */ + @JsonProperty("jingyingzhengming") + private List jingyingzhengming; + + /** 带货口碑,图片fileid,图片类型,最多不超过10张 */ + @JsonProperty("daihuokoubei") + private List daihuokoubei; + + /** 入住资质,图片fileid,图片类型,最多不超过10张 */ + @JsonProperty("ruzhuzhizhi") + private List ruzhuzhizhi; + + /** 经营流水,图片fileid,图片类型,最多不超过10张 */ + @JsonProperty("jingyingliushui") + private List jingyingliushui; + + /** 补充材料,图片fileid,图片类型,最多不超过10张 */ + @JsonProperty("buchongcailiao") + private List buchongcailiao; + + /** 经营平台,仅支持taobao,jd,douyin,kuaishou,pdd,other这些取值 */ + @JsonProperty("jingyingpingtai") + private String jingyingpingtai; + + /** 账号名称 */ + @JsonProperty("zhanghaomingcheng") + private String zhanghaomingcheng; + + /** 品牌列表,获取类目信息中的attr.is_limit_brand为true时必传 */ + @JsonProperty("brand_list") + private List brandList; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/audit/CategoryAuditRequest.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/audit/CategoryAuditRequest.java new file mode 100644 index 0000000000..a311bf0d2f --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/audit/CategoryAuditRequest.java @@ -0,0 +1,23 @@ +package me.chanjar.weixin.channel.bean.audit; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 类目审核信息 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class CategoryAuditRequest implements Serializable { + + private static final long serialVersionUID = -1151634735247657643L; + + @JsonProperty("category_info") + private CategoryAuditInfo categoryInfo; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/audit/CategoryBrand.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/audit/CategoryBrand.java new file mode 100644 index 0000000000..632096e4d2 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/audit/CategoryBrand.java @@ -0,0 +1,23 @@ +package me.chanjar.weixin.channel.bean.audit; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 分类中的品牌 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class CategoryBrand implements Serializable { + private static final long serialVersionUID = -5437441266080209907L; + + /** 品牌ID,是店铺申请且已审核通过的品牌ID */ + @JsonProperty("brand_id") + private String brand_id; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/audit/CatsV2.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/audit/CatsV2.java new file mode 100644 index 0000000000..b7cc6f39bc --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/audit/CatsV2.java @@ -0,0 +1,22 @@ +package me.chanjar.weixin.channel.bean.audit; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 新类目树类目ID + * @author Zeyes + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class CatsV2 implements Serializable { + private static final long serialVersionUID = -2484092110142035589L; + + /** 新类目树类目ID */ + @JsonProperty("cat_id") + private String catId; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/audit/ProductAuditInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/audit/ProductAuditInfo.java new file mode 100644 index 0000000000..7693f23ed3 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/audit/ProductAuditInfo.java @@ -0,0 +1,37 @@ +package me.chanjar.weixin.channel.bean.audit; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 商品审核信息 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +public class ProductAuditInfo implements Serializable { + + private static final long serialVersionUID = -5264206679057480206L; + + /** 审核单id */ + @JsonProperty("audit_id") + private String auditId; + + /** 上一次提交时间, yyyy-MM-dd HH:mm:ss */ + @JsonProperty("submit_time") + private String submitTime; + + /** 上一次审核时间, yyyy-MM-dd HH:mm:ss */ + @JsonProperty("audit_time") + private String auditTime; + + /** 拒绝理由,只有edit_status为3时出现 */ + @JsonProperty("reject_reason") + private String rejectReason; + + @JsonProperty("func_type") + private Integer funcType; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/base/AddressInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/base/AddressInfo.java new file mode 100644 index 0000000000..3c713840a4 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/base/AddressInfo.java @@ -0,0 +1,70 @@ +package me.chanjar.weixin.channel.bean.base; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.Accessors; + +/** + * 地址信息 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@Accessors(chain = true) +public class AddressInfo implements Serializable { + + private static final long serialVersionUID = 6928300709804576100L; + + /** 收件人姓名 */ + @JsonProperty("user_name") + private String userName; + + /** 收件人手机号码 */ + @JsonProperty("tel_number") + private String telNumber; + + /** 邮编 */ + @JsonProperty("postal_code") + private String postalCode; + + /** 省份 */ + @JsonProperty("province_name") + private String provinceName; + + /** 城市 */ + @JsonProperty("city_name") + private String cityName; + + /** 区 */ + @JsonProperty("county_name") + private String countyName; + + /** 详细地址 */ + @JsonProperty("detail_info") + private String detailInfo; + + /** 国家码 */ + @JsonProperty("national_code") + private String nationalCode; + + /** 门牌号码 */ + @JsonProperty("house_number") + private String houseNumber; + + /** 纬度 */ + @JsonProperty("lat") + private Double lat; + + /** 经度 */ + @JsonProperty("lng") + private Double lng; + + public AddressInfo(String provinceName, String cityName, String countyName) { + this.provinceName = provinceName; + this.cityName = cityName; + this.countyName = countyName; + } +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/base/AttrInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/base/AttrInfo.java new file mode 100644 index 0000000000..ca6ce7a750 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/base/AttrInfo.java @@ -0,0 +1,28 @@ +package me.chanjar.weixin.channel.bean.base; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 属性 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class AttrInfo implements Serializable { + + private static final long serialVersionUID = -790859309885311785L; + + /** 销售属性key(自定义),字符类型,最长不超过40 */ + @JsonProperty("attr_key") + private String key; + + /** 销售属性value(自定义),字符类型,最长不超过40,相同key下不能超过100个不同value */ + @JsonProperty("attr_value") + private String value; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/base/OffsetParam.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/base/OffsetParam.java new file mode 100644 index 0000000000..ebfad1bf21 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/base/OffsetParam.java @@ -0,0 +1,30 @@ +package me.chanjar.weixin.channel.bean.base; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 偏移参数 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@JsonInclude(Include.NON_NULL) +public class OffsetParam implements Serializable { + + private static final long serialVersionUID = -1268796871980541662L; + + /** 起始位置 */ + @JsonProperty("offset") + private Integer offset; + /** 拉取个数 */ + @JsonProperty("limit") + private Integer limit; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/base/PageParam.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/base/PageParam.java new file mode 100644 index 0000000000..d76e48d3b6 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/base/PageParam.java @@ -0,0 +1,28 @@ +package me.chanjar.weixin.channel.bean.base; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 分页参数 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class PageParam implements Serializable { + + private static final long serialVersionUID = -2606033044242617845L; + + /** 页码 */ + @JsonProperty("page") + protected Integer page; + + /** 每页订单数,上限100 */ + @JsonProperty("page_size") + protected Integer pageSize; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/base/StreamPageParam.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/base/StreamPageParam.java new file mode 100644 index 0000000000..6f3fb76d71 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/base/StreamPageParam.java @@ -0,0 +1,28 @@ +package me.chanjar.weixin.channel.bean.base; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 流式分页参数 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class StreamPageParam implements Serializable { + + private static final long serialVersionUID = -4098060161712929196L; + + /** 每页订单数,上限100 */ + @JsonProperty("page_size") + protected Integer pageSize; + + /** 分页参数,上一页请求返回 */ + @JsonProperty("next_key") + protected String nextKey; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/base/TimeRange.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/base/TimeRange.java new file mode 100644 index 0000000000..f681794835 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/base/TimeRange.java @@ -0,0 +1,26 @@ +package me.chanjar.weixin.channel.bean.base; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 时间范围 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +public class TimeRange implements Serializable { + + private static final long serialVersionUID = -8149679871789511479L; + + /** 开始时间 秒级时间戳 */ + @JsonProperty("start_time") + private Long startTime; + + /** 结束时间 秒级时间戳 */ + @JsonProperty("end_time") + private Long endTime; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/base/WxChannelBaseResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/base/WxChannelBaseResponse.java new file mode 100644 index 0000000000..b20d7f4b33 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/base/WxChannelBaseResponse.java @@ -0,0 +1,68 @@ +package me.chanjar.weixin.channel.bean.base; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import java.util.StringJoiner; + +/** + * 视频号小店 基础响应 + * + * @author Zeyes + */ +public class WxChannelBaseResponse implements Serializable { + + private static final long serialVersionUID = 3141420881984171781L; + + /** 请求成功状态码 */ + public static final int SUCCESS_CODE = 0; + public static final int INTERNAL_ERROR_CODE = -99; + + /** + * 错误码 + */ + @JsonProperty("errcode") + protected int errCode; + + /** + * 错误消息 + */ + @JsonProperty("errmsg") + protected String errMsg; + + /** + * 错误代码 + 错误消息 + * + * @return String + */ + public String errorMessage() { + return "errcode: " + errCode + ", errmsg: " + errMsg; + } + + public boolean isSuccess() { + return errCode == SUCCESS_CODE; + } + + public int getErrCode() { + return errCode; + } + + public void setErrCode(int errCode) { + this.errCode = errCode; + } + + public String getErrMsg() { + return errMsg; + } + + public void setErrMsg(String errMsg) { + this.errMsg = errMsg; + } + + @Override + public String toString() { + return new StringJoiner(", ", WxChannelBaseResponse.class.getSimpleName() + "[", "]") + .add("errCode=" + errCode) + .add("errMsg='" + errMsg + "'") + .toString(); + } +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/brand/BasicBrand.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/brand/BasicBrand.java new file mode 100644 index 0000000000..714740f843 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/brand/BasicBrand.java @@ -0,0 +1,30 @@ +package me.chanjar.weixin.channel.bean.brand; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 基础品牌信息 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +public class BasicBrand implements Serializable { + + private static final long serialVersionUID = -1991771439710177859L; + + /** 品牌库中的品牌编号(Long) */ + @JsonProperty("brand_id") + private String brandId; + + /** 品牌商标中文名 */ + @JsonProperty("ch_name") + private String chName; + + /** 品牌商标英文名 */ + @JsonProperty("en_name") + private String enName; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/brand/Brand.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/brand/Brand.java new file mode 100644 index 0000000000..92f4f41acc --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/brand/Brand.java @@ -0,0 +1,45 @@ +package me.chanjar.weixin.channel.bean.brand; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +/** + * 品牌信息 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +public class Brand extends BasicBrand { + + private static final long serialVersionUID = 4648597514861057019L; + + /** 商标分类号, 取值范围1-45 */ + @JsonProperty("classification_no") + private String classificationNo; + + /** 商标类型, 取值1:R标; 2: TM标 */ + @JsonProperty("trade_mark_symbol") + private Integer tradeMarkSymbol; + + /** 商标注册信息 */ + @JsonProperty("register_details") + private BrandRegisterDetail registerDetail; + + /** 商标申请信息 */ + @JsonProperty("application_details") + private BrandApplicationDetail applicationDetail; + + /** 商标授权信息, 取值1:自有品牌; 2: 授权品牌 */ + @JsonProperty("grant_type") + private Integer grantType; + + /** 授权品牌信息 */ + @JsonProperty("grant_details") + private BrandGrantDetail grantDetail; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/brand/BrandApplicationDetail.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/brand/BrandApplicationDetail.java new file mode 100644 index 0000000000..48575f27cd --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/brand/BrandApplicationDetail.java @@ -0,0 +1,31 @@ +package me.chanjar.weixin.channel.bean.brand; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import java.util.List; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 商标申请信息 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +public class BrandApplicationDetail implements Serializable { + + private static final long serialVersionUID = 2145344855482129473L; + + /** 商标申请受理时间, TM标时必填 */ + @JsonProperty("acceptance_time") + private Long acceptanceTime; + + /** 商标注册申请受理书file_id, TM标时必填, 限制最多传1张, 需要先调用“资质上传”接口上传资质图片 */ + @JsonProperty("acceptance_certification") + private List acceptanceCertification; + + /** 商标申请号, TM标时必填 */ + @JsonProperty("acceptance_no") + private String acceptanceNo; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/brand/BrandApplyListResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/brand/BrandApplyListResponse.java new file mode 100644 index 0000000000..16e7f3ae82 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/brand/BrandApplyListResponse.java @@ -0,0 +1,33 @@ +package me.chanjar.weixin.channel.bean.brand; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.List; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; + +/** + * 品牌申请列表响应 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class BrandApplyListResponse extends WxChannelBaseResponse { + + private static final long serialVersionUID = 243021267020609148L; + + /** 品牌资质申请信息 */ + @JsonProperty("brands") + private List brands; + + /** 本次翻页的上下文,用于请求下一页 */ + @JsonProperty("next_key") + private String nextKey; + + /** 品牌资质总数 */ + @JsonProperty("total_num") + private Integer totalNum; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/brand/BrandGrantDetail.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/brand/BrandGrantDetail.java new file mode 100644 index 0000000000..6b4826fcd4 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/brand/BrandGrantDetail.java @@ -0,0 +1,44 @@ +package me.chanjar.weixin.channel.bean.brand; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import java.util.List; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 商标授权信息 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +public class BrandGrantDetail implements Serializable { + + private static final long serialVersionUID = 3537812707384823606L; + + /** 品牌销售授权书的file_id, 授权品牌必填, 限制最多传9张, 需要先调用“资质上传”接口上传资质图片 */ + @JsonProperty("grant_certifications") + private List grantCertifications; + + /** 授权级数, 授权品牌必填, 取值1-3 */ + @JsonProperty("grant_level") + private Integer grantLevel; + + /** 授权有效期, 开始时间, 长期有效可不填 */ + @JsonProperty("start_time") + private Long startTime; + + /** 授权有效期, 结束时间, 长期有效可不填 */ + @JsonProperty("end_time") + private Long endTime; + + /** 是否长期有效 */ + @JsonProperty("is_permanent") + private boolean permanent; + + /** 品牌权利人证件照的file_id, 限制最多传2张, 需要先调用“资质上传”接口上传资质图片 */ + @JsonProperty("brand_owner_id_photos") + private List brandOwnerIdPhotos; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/brand/BrandInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/brand/BrandInfo.java new file mode 100644 index 0000000000..799002369d --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/brand/BrandInfo.java @@ -0,0 +1,52 @@ +package me.chanjar.weixin.channel.bean.brand; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +/** + * 品牌信息 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class BrandInfo extends Brand { + + private static final long serialVersionUID = 5464505958132626159L; + + /** 申请单状态 1审核中 2审核失败 3已生效 4已撤回 5即将过期(不影响商品售卖) 6已过期 */ + @JsonProperty("status") + private Integer status; + + /** 创建时间 */ + @JsonProperty("create_time") + private Long createTime; + + /** 更新时间 */ + @JsonProperty("update_time") + private Long updateTime; + + /** 审核结果 */ + @JsonProperty("audit_result") + private AuditResult auditResult; + + /** 审核结果 */ + @Data + @NoArgsConstructor + public static class AuditResult implements Serializable { + + private static final long serialVersionUID = 3936802571381636820L; + /** 提审的审核单ID */ + @JsonProperty("audit_id") + private String auditId; + + /** 审核不通过的原因, 审核成功不返回 */ + @JsonProperty("reject_reason") + private String rejectReason; + } + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/brand/BrandInfoResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/brand/BrandInfoResponse.java new file mode 100644 index 0000000000..20536b5a07 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/brand/BrandInfoResponse.java @@ -0,0 +1,24 @@ +package me.chanjar.weixin.channel.bean.brand; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; + +/** + * 品牌响应 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class BrandInfoResponse extends WxChannelBaseResponse { + + private static final long serialVersionUID = 2105745692451683517L; + + /** 品牌信息 */ + @JsonProperty("brand") + private BrandInfo brand; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/brand/BrandListResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/brand/BrandListResponse.java new file mode 100644 index 0000000000..c6cff6f317 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/brand/BrandListResponse.java @@ -0,0 +1,33 @@ +package me.chanjar.weixin.channel.bean.brand; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.List; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; + +/** + * 品牌列表响应 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class BrandListResponse extends WxChannelBaseResponse { + + private static final long serialVersionUID = -5335449078706304920L; + + /** 品牌库中的品牌信息 */ + @JsonProperty("brands") + private List brands; + + /** 本次翻页的上下文,用于请求下一页 */ + @JsonProperty("next_key") + private String nextKey; + + /** 是否还有下一页内容 */ + @JsonProperty("continue_flag") + private boolean continueFlag; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/brand/BrandParam.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/brand/BrandParam.java new file mode 100644 index 0000000000..05f8d89b42 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/brand/BrandParam.java @@ -0,0 +1,26 @@ +package me.chanjar.weixin.channel.bean.brand; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 品牌参数 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@JsonInclude(JsonInclude.Include.NON_NULL) +public class BrandParam implements Serializable { + + private static final long serialVersionUID = -4894709391464428613L; + + /** 品牌信息 */ + @JsonProperty("brand") + private Brand brand; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/brand/BrandRegisterDetail.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/brand/BrandRegisterDetail.java new file mode 100644 index 0000000000..28b417f38c --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/brand/BrandRegisterDetail.java @@ -0,0 +1,48 @@ +package me.chanjar.weixin.channel.bean.brand; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import java.util.List; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 品牌注册信息 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +public class BrandRegisterDetail implements Serializable { + + private static final long serialVersionUID = 1169957179510362405L; + + /** 商标注册人, R标时必填 */ + @JsonProperty("registrant") + private String registrant; + + /** 商标注册号, R标时必填 */ + @JsonProperty("register_no") + private String registerNo; + + /** 商标注册有效期(时间戳秒), 开始时间, 长期有效可不填 */ + @JsonProperty("start_time") + private Long startTime; + + /** 商标注册有效期(时间戳秒), 结束时间, 长期有效可不填 */ + @JsonProperty("end_time") + private Long endTime; + + /** 是否长期有效 */ + @JsonProperty("is_permanent") + private boolean permanent; + + /** 商标注册证的file_id, R标时必填, 限制最多传1张, 需要先调用“资质上传”接口上传资质图片 */ + @JsonProperty("register_certifications") + private List registerCertifications; + + /** 变更/续展证明的file_id, 限制最多传5张, 需要先调用“资质上传”接口上传资质图片 */ + @JsonProperty("renew_certifications") + private List renewCertifications; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/brand/BrandSearchParam.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/brand/BrandSearchParam.java new file mode 100644 index 0000000000..e73ed4f54e --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/brand/BrandSearchParam.java @@ -0,0 +1,31 @@ +package me.chanjar.weixin.channel.bean.brand; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import me.chanjar.weixin.channel.bean.base.StreamPageParam; + +/** + * 品牌搜索参数 + * + * @author Zeyes + */ +@Data +@EqualsAndHashCode(callSuper = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +public class BrandSearchParam extends StreamPageParam { + + private static final long serialVersionUID = 5961201403338269712L; + /** 审核单状态, 不填默认拉全部商品 */ + @JsonProperty("status") + private Integer status; + + public BrandSearchParam() { + } + + public BrandSearchParam(Integer pageSize, String nextKey, Integer status) { + super(pageSize, nextKey); + this.status = status; + } +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/category/AccountCategoryResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/category/AccountCategoryResponse.java new file mode 100644 index 0000000000..3db7c74cec --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/category/AccountCategoryResponse.java @@ -0,0 +1,25 @@ +package me.chanjar.weixin.channel.bean.category; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.List; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; + +/** + * 分类响应 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class AccountCategoryResponse extends WxChannelBaseResponse { + + private static final long serialVersionUID = 3486089711447908477L; + + /** 类目列表 */ + @JsonProperty("data") + private List categories; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/category/CategoryAndQualificationList.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/category/CategoryAndQualificationList.java new file mode 100644 index 0000000000..c9e973c8b8 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/category/CategoryAndQualificationList.java @@ -0,0 +1,23 @@ +package me.chanjar.weixin.channel.bean.category; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import java.util.List; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 分类资质响应 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +public class CategoryAndQualificationList implements Serializable { + + private static final long serialVersionUID = 4245906598437404655L; + + /** 分类列表 */ + @JsonProperty("cat_and_qua") + private List list; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/category/CategoryDetailResult.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/category/CategoryDetailResult.java new file mode 100644 index 0000000000..3188bd3820 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/category/CategoryDetailResult.java @@ -0,0 +1,259 @@ +package me.chanjar.weixin.channel.bean.category; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import java.util.List; +import lombok.Data; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; + +/** + * @author Zeyes + */ +@Data +@NoArgsConstructor +public class CategoryDetailResult extends WxChannelBaseResponse { + + private static final long serialVersionUID = 4657778764371047619L; + + @JsonProperty("info") + private Info info; + + @JsonProperty("attr") + private Attr attr; + + @JsonProperty("product_qua_list") + private List productQuaList; + + + @Data + @NoArgsConstructor + public static class Info implements Serializable { + + /** 类目ID */ + @JsonProperty("cat_id") + private String id; + /** 类目名称 */ + @JsonProperty("name") + private String name; + } + + @Data + @NoArgsConstructor + public static class Attr implements Serializable { + + /** 是否支持虚拟发货 */ + @JsonProperty("shop_no_shipment") + private Boolean shopNoShipment; + + /** 是否定向准入 */ + @JsonProperty("access_permit_required") + private Boolean accessPermitRequired; + + /** 是否支持预售 */ + @JsonProperty("pre_sale") + private Boolean preSale; + + /** 是否必须支持7天无理由退货 */ + @JsonProperty("seven_day_return") + private Boolean sevenDayReturn; + + /** 定准类目的品牌ID */ + @JsonProperty("brand_list") + private List brands; + + /** 类目关联的保证金,单位分 */ + @JsonProperty("deposit") + private Long deposit; + + /** 产品属性 */ + @JsonProperty("product_attr_list") + private List productAttrs; + + /** 销售属性 */ + @JsonProperty("sale_attr_list") + private List saleAttrs; + + /** 佣金信息 */ + @JsonProperty("transactionfee_info") + private FeeInfo feeInfo; + + /** 折扣规则 */ + @JsonProperty("coupon_rule") + private CouponRule couponRule; + + /** 价格下限,单位分,商品售价不可低于此价格 */ + @JsonProperty("floor_price") + private Long floorPrice; + + /** 收货时间选项 */ + @JsonProperty("confirm_receipt_days") + private List confirmReceiptDays; + + /** 是否品牌定向准入,即该类目一定要有品牌 */ + @JsonProperty("is_limit_brand") + private Boolean limitBrand; + + /** 商品编辑要求 */ + @JsonProperty("product_requirement") + private ProductRequirement productRequirement; + + /** 尺码表 */ + @JsonProperty("size_chart") + private SizeChart sizeChart; + + /** 放心买必须打开坏损包赔 */ + @JsonProperty("is_confidence_require_bad_must_pay") + private Boolean confidenceRequireBadMustPay; + + /** 资质信息 */ + @JsonProperty("product_qua_list") + private List productQuaList; + } + + @Data + @NoArgsConstructor + public static class BrandInfo implements Serializable { + + /** 定准类目的品牌ID */ + @JsonProperty("brand_id") + private String id; + } + + @Data + @NoArgsConstructor + public static class ProductAttr implements Serializable { + + /** 类目必填项名称 */ + @JsonProperty("name") + private String name; + + /** 属性类型,string为自定义,select_one为多选一,该参数短期保留,用于兼容。将来废弃,使用type_v2替代 */ + @JsonProperty("type") + private String type; + + /** + * 属性类型v2,共7种类型 + * string:文本 + * select_one:单选,选项列表在value中 + * select_many:多选,选项列表在value中 + * integer:整数,数字必须为整数 + * decimal4:小数(4 位精度),小数部分最多 4 位 + * integer_unit:整数 + 单位,单位的选项列表在value中 + * decimal4_unit:小数(4 位精度) + 单位,单位的选项列表在value中 + */ + @JsonProperty("type_v2") + private String typeV2; + + /** + * 可选项列表,当type为:select_one/select_many时,为选项列表 + * 当type为:integer_unit/decimal4_unit时,为单位的列表 + */ + @JsonProperty("value") + private String value; + + /** 是否类目必填项 */ + @JsonProperty("is_required") + private Boolean required; + + /** 输入提示,请填写提示语 */ + @JsonProperty("hint") + private String hint; + + /** 允许添加选项,当type为select_one/select_many时,标识是否允许添加新选项(value中不存在的选项) */ + @JsonProperty("append_allowed") + private Boolean appendAllowed; + } + + @Data + @NoArgsConstructor + public static class FeeInfo implements Serializable { + + /** 类目实收的交易佣金比例,单位万分比 */ + @JsonProperty("basis_point") + private Integer basisPoint; + + /** 类目原始佣金比例,单位万分比 */ + @JsonProperty("original_basis_point") + private Integer originalBasisPoint; + + /** 佣金激励类型,0:无激励措施,1:新店佣金减免 */ + @JsonProperty("incentive_type") + private Integer incentiveType; + } + + @Data + @NoArgsConstructor + public static class CouponRule implements Serializable { + + /** 最高的折扣比例,百分比, 0表示无限制 */ + @JsonProperty("discount_ratio_limit") + private Integer supportCoupon; + + /** 最高的折扣金额,单位分,0表示无限制 */ + @JsonProperty("discount_limit") + private Integer couponType; + } + + @Data + @NoArgsConstructor + public static class ProductRequirement implements Serializable { + /** 商品标题的编辑要求 */ + @JsonProperty("product_title_requirement") + private String productTitleRequirement; + + /** 商品主图的编辑要求 */ + @JsonProperty("product_img_requirement") + private String productImgRequirement; + + /** 商品描述的编辑要求 */ + @JsonProperty("product_desc_requirement") + private String productDescRequirement; + } + + @Data + @NoArgsConstructor + public static class SizeChart implements Serializable { + + /** 是否支持尺码表 */ + @JsonProperty("is_support") + private Boolean support; + + /** 尺码配置要求列表 */ + @JsonProperty("item_list") + private List itemList; + } + + @Data + @NoArgsConstructor + public static class SizeChartItem implements Serializable { + /** 尺码属性名称 */ + @JsonProperty("name") + private String name; + + /** 尺码属性值的单位 */ + @JsonProperty("unit") + private String unit; + + /** 尺码属性值的类型,1:字符型,2:整数型,3:小数型 */ + @JsonProperty("type") + private String type; + + /** 尺码属性值的填写格式,1:单值填写,2:区间值填写,3:支持单值或区间值 */ + @JsonProperty("format") + private String format; + + /** 尺码属性值的限制 */ + @JsonProperty("limit") + private String limit; + + /** 是否必填 */ + @JsonProperty("is_required") + private Boolean required; + } + +} + + + + diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/category/CategoryQualification.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/category/CategoryQualification.java new file mode 100644 index 0000000000..9cac327d6c --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/category/CategoryQualification.java @@ -0,0 +1,46 @@ +package me.chanjar.weixin.channel.bean.category; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import java.util.List; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 分类资质信息 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +public class CategoryQualification implements Serializable { + + private static final long serialVersionUID = 6495550078851408381L; + + /** 类目 */ + @JsonProperty("cat") + private ShopCategory category; + + /** 资质信息 */ + @JsonProperty("qua") + private QualificationInfo info; + + /** 商品资质信息,将废弃,使用product_qua_list代替 */ + @JsonProperty("product_qua") + @Deprecated + private QualificationInfo productInfo; + + /** 品牌资质信息 */ + @JsonProperty("brand_qua") + @Deprecated + private QualificationInfo brandQua; + + /** 商品资质列表,替代product_qua */ + @JsonProperty("product_qua_list") + private List productQuaList; + + /** 放心买必须打开坏损包赔 */ + @JsonProperty("is_confidence_require_bad_must_pay") + private Boolean confidenceRequireBadMustPay; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/category/CategoryQualificationResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/category/CategoryQualificationResponse.java new file mode 100644 index 0000000000..cbd588ebf9 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/category/CategoryQualificationResponse.java @@ -0,0 +1,28 @@ +package me.chanjar.weixin.channel.bean.category; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.List; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; + +/** + * 分类资质响应 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class CategoryQualificationResponse extends WxChannelBaseResponse { + + private static final long serialVersionUID = -7869091908852685830L; + + @JsonProperty("cats") + private List list; + + @JsonProperty("cats_v2") + private List catsV2; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/category/PassCategoryInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/category/PassCategoryInfo.java new file mode 100644 index 0000000000..82b16c0188 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/category/PassCategoryInfo.java @@ -0,0 +1,26 @@ +package me.chanjar.weixin.channel.bean.category; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 审核通过的分类和资质信息 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +public class PassCategoryInfo implements Serializable { + + private static final long serialVersionUID = 1152077957498898216L; + + /** 类目ID */ + @JsonProperty("cat_id") + private String catId; + + /** 资质ID */ + @JsonProperty("qua_id") + private String quaId; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/category/PassCategoryResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/category/PassCategoryResponse.java new file mode 100644 index 0000000000..6509321b88 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/category/PassCategoryResponse.java @@ -0,0 +1,25 @@ +package me.chanjar.weixin.channel.bean.category; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.List; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; + +/** + * 审核通过的分类和资质信息 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class PassCategoryResponse extends WxChannelBaseResponse { + + private static final long serialVersionUID = -3674591447273025743L; + + /** 类目和资质信息列表 */ + @JsonProperty("list") + private List list; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/category/QualificationInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/category/QualificationInfo.java new file mode 100644 index 0000000000..efb7249fe3 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/category/QualificationInfo.java @@ -0,0 +1,36 @@ +package me.chanjar.weixin.channel.bean.category; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 资质信息 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +public class QualificationInfo implements Serializable { + + /** 资质ID */ + @JsonProperty("qua_id") + private String id; + + /** 是否需要申请 */ + @JsonProperty("need_to_apply") + private Boolean needToApply; + + /** 资质信息 */ + @JsonProperty("tips") + private String tips; + + /** 该类目申请的时候是否一定要提交资质 */ + @JsonProperty("mandatory") + private Boolean mandatory; + + /** 资质名称 */ + @JsonProperty("name") + private String name; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/category/RelationCategoryItem.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/category/RelationCategoryItem.java new file mode 100644 index 0000000000..8e0bd1b0b5 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/category/RelationCategoryItem.java @@ -0,0 +1,41 @@ +package me.chanjar.weixin.channel.bean.category; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * 店铺类目权限列表项 + * + * @author chucheng + */ +@Data +@NoArgsConstructor +public class RelationCategoryItem implements Serializable { + + /** 类目id */ + @JsonProperty("id") + private Long id; + + /** 类目状态, 1生效中,2已失效 */ + @JsonProperty("status") + private Integer status; + + /** 失效原因 */ + @JsonProperty("uneffective_reason") + private String uneffectiveReason; + + /** 生效时间 */ + @JsonProperty("effective_time") + private Long effectiveTime; + + /** 失效时间 */ + @JsonProperty("uneffective_time") + private Long uneffectiveTime; + + /** 类目资质id */ + @JsonProperty("qua_id") + private Long quaId; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/category/RelationCategoryRequest.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/category/RelationCategoryRequest.java new file mode 100644 index 0000000000..c514e7d9ca --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/category/RelationCategoryRequest.java @@ -0,0 +1,28 @@ +package me.chanjar.weixin.channel.bean.category; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 类目权限列表请求参数 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class RelationCategoryRequest implements Serializable { + + private static final long serialVersionUID = -8765432109876543210L; + + /** 是否按状态筛选 */ + @JsonProperty("is_filter_status") + private Boolean isFilterStatus; + + /** 类目状态(当 isFilterStatus 为 true 时有效) */ + @JsonProperty("status") + private Integer status; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/category/RelationCategoryResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/category/RelationCategoryResponse.java new file mode 100644 index 0000000000..4bd1ea96d4 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/category/RelationCategoryResponse.java @@ -0,0 +1,25 @@ +package me.chanjar.weixin.channel.bean.category; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.List; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; + +/** + * 店铺的类目权限列表响应 + * + * @author chucheng + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class RelationCategoryResponse extends WxChannelBaseResponse { + + private static final long serialVersionUID = -8473920857463918245L; + + @JsonProperty("list") + private List list; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/category/ShopCategory.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/category/ShopCategory.java new file mode 100644 index 0000000000..5dd04582f3 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/category/ShopCategory.java @@ -0,0 +1,36 @@ +package me.chanjar.weixin.channel.bean.category; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 商品类目 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +public class ShopCategory implements Serializable { + + /** 类目ID */ + @JsonProperty("cat_id") + private String id; + + /** 类目父ID */ + @JsonProperty("f_cat_id") + private String parentId; + + /** 类目名称 */ + @JsonProperty("name") + private String name; + + /** 层级 */ + @JsonProperty("level") + private Integer level; + + /** 是否为叶子类目(品类) */ + @JsonProperty("leaf") + private Boolean leaf; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/category/ShopCategoryResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/category/ShopCategoryResponse.java new file mode 100644 index 0000000000..fff7362a7a --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/category/ShopCategoryResponse.java @@ -0,0 +1,29 @@ +package me.chanjar.weixin.channel.bean.category; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.List; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; + +/** + * 分类响应 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class ShopCategoryResponse extends WxChannelBaseResponse { + + private static final long serialVersionUID = 3871098948660947422L; + + /** 类目列表 */ + @JsonProperty("cat_list") + private List categories; + + /** 类目列表 */ + @JsonProperty("cat_list_v2") + private List catListV2; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/compass/CompassFinderBaseParam.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/compass/CompassFinderBaseParam.java new file mode 100644 index 0000000000..a1d5e277cc --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/compass/CompassFinderBaseParam.java @@ -0,0 +1,30 @@ +package me.chanjar.weixin.channel.bean.compass; + +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; + +/** + * 获取达人罗盘数据通用请求参数 + * + * @author Winnie + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +@JsonInclude(JsonInclude.Include.NON_NULL) +public class CompassFinderBaseParam implements Serializable { + + private static final long serialVersionUID = - 4900361041041434435L; + + /** + * 日期,格式 yyyyMMdd + */ + @JsonProperty("ds") + private String ds; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/compass/finder/Field.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/compass/finder/Field.java new file mode 100644 index 0000000000..a23cde1878 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/compass/finder/Field.java @@ -0,0 +1,33 @@ +package me.chanjar.weixin.channel.bean.compass.finder; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; +import java.util.List; + +/** + * 维度数据 + * + * @author Winnie + */ +@Data +@NoArgsConstructor +public class Field implements Serializable { + + private static final long serialVersionUID = - 4243469984232948689L; + + /** + * 维度类别名 + */ + @JsonProperty("field_name") + private String fieldName; + + /** + * 维度指标数据列表 + */ + @JsonProperty("data_list") + private List dataList; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/compass/finder/FieldData.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/compass/finder/FieldData.java new file mode 100644 index 0000000000..a8b82c8326 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/compass/finder/FieldData.java @@ -0,0 +1,32 @@ +package me.chanjar.weixin.channel.bean.compass.finder; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * 维度指标数据 + * + * @author Winnie + */ +@Data +@NoArgsConstructor +public class FieldData implements Serializable { + + private static final long serialVersionUID = - 4022953139259283599L; + + /** + * 维度指标名 + */ + @JsonProperty("dim_key") + private String dimKey; + + /** + * 维度指标值 + */ + @JsonProperty("dim_value") + private String dimValue; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/compass/finder/Overall.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/compass/finder/Overall.java new file mode 100644 index 0000000000..ab77df0f97 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/compass/finder/Overall.java @@ -0,0 +1,56 @@ +package me.chanjar.weixin.channel.bean.compass.finder; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * 电商概览数据 + * + * @author Winnie + */ +@Data +@NoArgsConstructor +public class Overall implements Serializable { + + private static final long serialVersionUID = 2456038666608345011L; + + /** + * 成交金额,单位分 + */ + @JsonProperty("pay_gmv") + private String payGmv; + + /** + * 直播成交金额,单位分 + */ + @JsonProperty("live_pay_gmv") + private String livePayGmv; + + /** + * 短视频成交金额,单位分 + */ + @JsonProperty("feed_pay_gmv") + private String feedPayGmv; + + /** + * 橱窗成交金额,单位分 + */ + @JsonProperty("window_pay_gmv") + private String windowPayGmv; + + /** + * 商品分享支付金额,单位分 + */ + @JsonProperty("product_pay_gmv") + private String productPayGmv; + + /** + * 其他渠道成交金额,单位分 + */ + @JsonProperty("other_pay_gmv") + private String otherPayGmv; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/compass/finder/OverallResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/compass/finder/OverallResponse.java new file mode 100644 index 0000000000..8331726c13 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/compass/finder/OverallResponse.java @@ -0,0 +1,27 @@ +package me.chanjar.weixin.channel.bean.compass.finder; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; + +/** + * 获取电商概览数据响应 + * + * @author Winnie + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class OverallResponse extends WxChannelBaseResponse { + + private static final long serialVersionUID = 6350218415876820956L; + + /** + * 电商概览数据 + */ + @JsonProperty("data") + private Overall data; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/compass/finder/ProductCompassData.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/compass/finder/ProductCompassData.java new file mode 100644 index 0000000000..d84c8d367b --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/compass/finder/ProductCompassData.java @@ -0,0 +1,170 @@ +package me.chanjar.weixin.channel.bean.compass.finder; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * 商品罗盘数据 + * + * @author Winnie + */ +@Data +@NoArgsConstructor +public class ProductCompassData implements Serializable { + + private static final long serialVersionUID = - 1009289493985863096L; + + /** + * 成交金额 + */ + @JsonProperty("pay_gmv") + private String payGmv; + + /** + * 下单金额(单位:分) + */ + @JsonProperty("create_gmv") + private String createGmv; + + /** + * 下单订单数 + */ + @JsonProperty("create_cnt") + private String createCnt; + + /** + * 下单人数 + */ + @JsonProperty("create_uv") + private String createUv; + + /** + * 下单件数 + */ + @JsonProperty("create_product_cnt") + private String createProductCnt; + + /** + * 成交订单数 + */ + @JsonProperty("pay_cnt") + private String payCnt; + + /** + * 成交人数 + */ + @JsonProperty("pay_uv") + private String payUv; + + /** + * 成交件数 + */ + @JsonProperty("pay_product_cnt") + private String payProductCnt; + + /** + * 成交金额(剔除退款)(单位:分) + */ + @JsonProperty("pure_pay_gmv") + private String purePayGmv; + + /** + * 成交客单价(单位:分) + */ + @JsonProperty("pay_gmv_per_uv") + private String payGmvPerUv; + + /** + * 实际结算金额(单位:分) + */ + @JsonProperty("actual_commission") + private String actualCommission; + + /** + * 预估佣金金额(单位:分) + */ + @JsonProperty("predict_commission") + private String predictCommission; + + /** + * 商品点击人数 + */ + @JsonProperty("product_click_uv") + private String productClickUv; + + /** + * 商品点击次数 + */ + @JsonProperty("product_click_cnt") + private String productClickCnt; + + /** + * 成交退款金额 + */ + @JsonProperty("pay_refund_gmv") + private String payRefundGmv; + + /** + * 成交退款人数 + */ + @JsonProperty("pay_refund_uv") + private String payRefundUv; + + /** + * 成交退款率 + */ + @JsonProperty("pay_refund_ratio") + private Double payRefundRatio; + + /** + * 发货后成交退款率 + */ + @JsonProperty("pay_refund_after_send_ratio") + private Double payRefundAfterSendRatio; + + /** + * 成交退款订单数 + */ + @JsonProperty("pay_refund_cnt") + private String payRefundCnt; + + /** + * 成交退款件数 + */ + @JsonProperty("pay_refund_product_cnt") + private String payRefundProductCnt; + + /** + * 发货前成交退款率 + */ + @JsonProperty("pay_refund_before_send_ratio") + private Double payRefundBeforeSendRatio; + + /** + * 退款金额(单位:分) + */ + @JsonProperty("refund_gmv") + private String refundGmv; + + /** + * 退款件数 + */ + @JsonProperty("refund_product_cnt") + private String refundProductCnt; + + /** + * 退款订单数 + */ + @JsonProperty("refund_cnt") + private String refundCnt; + + /** + * 退款人数 + */ + @JsonProperty("refund_uv") + private String refundUv; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/compass/finder/ProductDataParam.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/compass/finder/ProductDataParam.java new file mode 100644 index 0000000000..57a26a9794 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/compass/finder/ProductDataParam.java @@ -0,0 +1,34 @@ +package me.chanjar.weixin.channel.bean.compass.finder; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.bean.compass.CompassFinderBaseParam; + +/** + * 获取带货商品数据请求参数 + * + * @author Winnie + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +public class ProductDataParam extends CompassFinderBaseParam { + + private static final long serialVersionUID = - 5016298274452168329L; + + /** + * 商品id + */ + @JsonProperty("product_id") + private String productId; + + public ProductDataParam(String ds, String productId) { + super(ds); + this.productId = productId; + } + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/compass/finder/ProductDataResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/compass/finder/ProductDataResponse.java new file mode 100644 index 0000000000..628e0cc221 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/compass/finder/ProductDataResponse.java @@ -0,0 +1,27 @@ +package me.chanjar.weixin.channel.bean.compass.finder; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; + +/** + * 获取带货商品数据响应 + * + * @author Winnie + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class ProductDataResponse extends WxChannelBaseResponse { + + private static final long serialVersionUID = 7264776818163943719L; + + /** + * 带货商品数据 + */ + @JsonProperty("product_info") + private ProductInfo productInfo; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/compass/finder/ProductInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/compass/finder/ProductInfo.java new file mode 100644 index 0000000000..3d1071b261 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/compass/finder/ProductInfo.java @@ -0,0 +1,68 @@ +package me.chanjar.weixin.channel.bean.compass.finder; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * 带货商品数据 + * + * @author Winnie + */ +@Data +@NoArgsConstructor +public class ProductInfo implements Serializable { + + private static final long serialVersionUID = - 3347940276601700091L; + + /** + * 商品id + */ + @JsonProperty("product_id") + private String productId; + + /** + * 商品头图 + */ + @JsonProperty("head_img_url") + private String headImgUrl; + + /** + * 商品标题 + */ + @JsonProperty("title") + private String title; + + /** + * 商品价格 + */ + @JsonProperty("price") + private String price; + + /** + * 1级类目 + */ + @JsonProperty("first_category_id") + private String firstCategoryId; + + /** + * 2级类目 + */ + @JsonProperty("second_category_id") + private String secondCategoryId; + + /** + * 3级类目 + */ + @JsonProperty("third_category_id") + private String thirdCategoryId; + + /** + * 商品罗盘数据 + */ + @JsonProperty("data") + private ProductCompassData data; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/compass/finder/ProductListResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/compass/finder/ProductListResponse.java new file mode 100644 index 0000000000..e327531305 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/compass/finder/ProductListResponse.java @@ -0,0 +1,29 @@ +package me.chanjar.weixin.channel.bean.compass.finder; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; + +import java.util.List; + +/** + * 获取带货商品列表响应 + * + * @author Winnie + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class ProductListResponse extends WxChannelBaseResponse { + + private static final long serialVersionUID = 7903039293558611066L; + + /** + * 带货商品列表 + */ + @JsonProperty("product_list") + private List productList; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/compass/finder/SaleProfileData.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/compass/finder/SaleProfileData.java new file mode 100644 index 0000000000..379943903e --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/compass/finder/SaleProfileData.java @@ -0,0 +1,27 @@ +package me.chanjar.weixin.channel.bean.compass.finder; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; +import java.util.List; + +/** + * 带货人群数据 + * + * @author Winnie + */ +@Data +@NoArgsConstructor +public class SaleProfileData implements Serializable { + + private static final long serialVersionUID = - 5542602540358792014L; + + /** + * 维度数据列表 + */ + @JsonProperty("field_list") + private List fieldList; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/compass/finder/SaleProfileDataParam.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/compass/finder/SaleProfileDataParam.java new file mode 100644 index 0000000000..abe4610785 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/compass/finder/SaleProfileDataParam.java @@ -0,0 +1,34 @@ +package me.chanjar.weixin.channel.bean.compass.finder; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.bean.compass.CompassFinderBaseParam; + +/** + * 获取带货人群数据请求参数 + * + * @author Winnie + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +public class SaleProfileDataParam extends CompassFinderBaseParam { + + private static final long serialVersionUID = 4037843292285732855L; + + /** + * 用户类型 {@link me.chanjar.weixin.channel.enums.SaleProfileUserType} + */ + @JsonProperty("type") + private Integer type; + + public SaleProfileDataParam(String ds, Integer type) { + super(ds); + this.type = type; + } + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/compass/finder/SaleProfileDataResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/compass/finder/SaleProfileDataResponse.java new file mode 100644 index 0000000000..a976671ba0 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/compass/finder/SaleProfileDataResponse.java @@ -0,0 +1,27 @@ +package me.chanjar.weixin.channel.bean.compass.finder; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; + +/** + * 获取带货人群数据响应 + * + * @author Winnie + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class SaleProfileDataResponse extends WxChannelBaseResponse { + + private static final long serialVersionUID = - 6409722880191468272L; + + /** + * 带货人群数据 + */ + @JsonProperty("data") + private SaleProfileData data; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/compass/shop/CompassFinderIdParam.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/compass/shop/CompassFinderIdParam.java new file mode 100644 index 0000000000..9383d2de2f --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/compass/shop/CompassFinderIdParam.java @@ -0,0 +1,32 @@ +package me.chanjar.weixin.channel.bean.compass.shop; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.bean.compass.CompassFinderBaseParam; + +/** + * 带货达人 请求参数 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +public class CompassFinderIdParam extends CompassFinderBaseParam { + + private static final long serialVersionUID = 9214560943091074780L; + + /** 视频号ID */ + @JsonProperty("finder_id") + private String finderId; + + public CompassFinderIdParam(String ds, String finderId) { + super(ds); + this.finderId = finderId; + } + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/compass/shop/FinderAuthListResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/compass/shop/FinderAuthListResponse.java new file mode 100644 index 0000000000..0f0351e975 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/compass/shop/FinderAuthListResponse.java @@ -0,0 +1,30 @@ +package me.chanjar.weixin.channel.bean.compass.shop; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.List; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; + +/** + * 获取授权视频号列表 响应 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class FinderAuthListResponse extends WxChannelBaseResponse { + + private static final long serialVersionUID = -3215073536002857589L; + + /** 主营视频号id */ + @JsonProperty("main_finder_id") + private String mainFinderId; + + /** 授权视频号id列表 */ + @JsonProperty("authorized_finder_id_list") + private List authorizedFinderIdList; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/compass/shop/FinderGmvData.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/compass/shop/FinderGmvData.java new file mode 100644 index 0000000000..822f93c4f0 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/compass/shop/FinderGmvData.java @@ -0,0 +1,39 @@ +package me.chanjar.weixin.channel.bean.compass.shop; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 带货达人数据 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +public class FinderGmvData implements Serializable { + + private static final long serialVersionUID = -7463331971169286175L; + + /** 成交金额,单位分 */ + @JsonProperty("pay_gmv") + private String payGmv; + + /** 动销商品数 */ + @JsonProperty("pay_product_id_cnt") + private String payProductIdCnt; + + /** 成交人数 */ + @JsonProperty("pay_uv") + private String payUv; + + /** 退款金额,单位分 */ + @JsonProperty("refund_gmv") + private String refundGmv; + + /** 成交退款金额,单位分 */ + @JsonProperty("pay_refund_gmv") + private String payRefundGmv; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/compass/shop/FinderGmvItem.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/compass/shop/FinderGmvItem.java new file mode 100644 index 0000000000..a102732c8a --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/compass/shop/FinderGmvItem.java @@ -0,0 +1,30 @@ +package me.chanjar.weixin.channel.bean.compass.shop; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 带货达人列表数据 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +public class FinderGmvItem implements Serializable { + + private static final long serialVersionUID = -3740996985044711599L; + + /** 视频号id */ + @JsonProperty("finder_id") + private String finderId; + + /** 视频号昵称 */ + @JsonProperty("finder_nickname") + private String finderNickname; + + /** 带货达人数据 */ + @JsonProperty("data") + private FinderGmvData data; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/compass/shop/FinderListResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/compass/shop/FinderListResponse.java new file mode 100644 index 0000000000..a5a37d9a2f --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/compass/shop/FinderListResponse.java @@ -0,0 +1,26 @@ +package me.chanjar.weixin.channel.bean.compass.shop; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.List; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; + +/** + * 带货达人列表 响应 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class FinderListResponse extends WxChannelBaseResponse { + + private static final long serialVersionUID = 6358992001065379269L; + + /** 授权视频号id列表 */ + @JsonProperty("finder_list") + private List finderList; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/compass/shop/FinderOverallData.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/compass/shop/FinderOverallData.java new file mode 100644 index 0000000000..6303202709 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/compass/shop/FinderOverallData.java @@ -0,0 +1,35 @@ +package me.chanjar.weixin.channel.bean.compass.shop; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 带货数据概览 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +public class FinderOverallData implements Serializable { + + private static final long serialVersionUID = -994852668593815907L; + + /** 成交金额,单位分 */ + @JsonProperty("pay_gmv") + private String payGmv; + + /** 动销达人数 */ + @JsonProperty("pay_sales_finder_cnt") + private String paySalesFinderCnt; + + /** 动销商品数 */ + @JsonProperty("pay_product_id_cnt") + private String payProductIdCnt; + + /** 点击-成交转化率 */ + @JsonProperty("click_to_pay_uv_ratio") + private Double clickToPayUvRatio; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/compass/shop/FinderOverallResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/compass/shop/FinderOverallResponse.java new file mode 100644 index 0000000000..fdc83fcce8 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/compass/shop/FinderOverallResponse.java @@ -0,0 +1,26 @@ +package me.chanjar.weixin.channel.bean.compass.shop; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; + +/** + * 带货数据概览 响应 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class FinderOverallResponse extends WxChannelBaseResponse { + + private static final long serialVersionUID = -4935555091396799318L; + + /** + * 电商概览数据 + */ + @JsonProperty("data") + private FinderOverallData data; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/compass/shop/FinderProductListItem.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/compass/shop/FinderProductListItem.java new file mode 100644 index 0000000000..7f6ad34445 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/compass/shop/FinderProductListItem.java @@ -0,0 +1,66 @@ +package me.chanjar.weixin.channel.bean.compass.shop; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 带货达人商品列表 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +public class FinderProductListItem implements Serializable { + + private static final long serialVersionUID = 1646092488200992026L; + + /** 商品id */ + @JsonProperty("product_id") + private String productId; + + /** 商品头图 */ + @JsonProperty("head_img_url") + private String headImgUrl; + + /** 商品标题 */ + @JsonProperty("title") + private String title; + + /** 商品价格 */ + @JsonProperty("price") + private String price; + + /** 商品1级类目 */ + @JsonProperty("first_category_id") + private String firstCategoryId; + + /** 商品2级类目 */ + @JsonProperty("second_category_id") + private String secondCategoryId; + + /** 商品3级类目 */ + @JsonProperty("third_category_id") + private String thirdCategoryId; + + /** gmv */ + @JsonProperty("data") + private GmvData data; + + + @Data + @NoArgsConstructor + public static class GmvData implements Serializable { + private static final long serialVersionUID = 1840494188469233735L; + + /** 佣金率 */ + @JsonProperty("commission_ratio") + private Double commissionRatio; + + /** 成交金额,单位分 */ + @JsonProperty("pay_gmv") + private String payGmv; + } + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/compass/shop/FinderProductListResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/compass/shop/FinderProductListResponse.java new file mode 100644 index 0000000000..bcdb1932d4 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/compass/shop/FinderProductListResponse.java @@ -0,0 +1,28 @@ +package me.chanjar.weixin.channel.bean.compass.shop; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.List; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; + +/** + * 带货达人商品列表 响应 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class FinderProductListResponse extends WxChannelBaseResponse { + + private static final long serialVersionUID = 5883861777181983173L; + + /** + * 带货达人商品列表 + */ + @JsonProperty("product_list") + private List productList; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/compass/shop/FinderProductOverallResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/compass/shop/FinderProductOverallResponse.java new file mode 100644 index 0000000000..e47223a4d8 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/compass/shop/FinderProductOverallResponse.java @@ -0,0 +1,25 @@ +package me.chanjar.weixin.channel.bean.compass.shop; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; + +/** + * 带货达人详情 响应 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class FinderProductOverallResponse extends WxChannelBaseResponse { + + private static final long serialVersionUID = 6358992001065379269L; + + /** 带货达人详情 */ + @JsonProperty("data") + private FinderGmvData data; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/compass/shop/FinderProductSimpleGmvData.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/compass/shop/FinderProductSimpleGmvData.java new file mode 100644 index 0000000000..7a635dc4b0 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/compass/shop/FinderProductSimpleGmvData.java @@ -0,0 +1,26 @@ +package me.chanjar.weixin.channel.bean.compass.shop; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 带货达人商品GMV数据 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +public class FinderProductSimpleGmvData implements Serializable { + private static final long serialVersionUID = -3740996985044711599L; + + /** 佣金率 */ + @JsonProperty("commission_ratio") + private Double commissionRatio; + + /** 成交金额,单位分 */ + @JsonProperty("pay_gmv") + private String payGmv; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/compass/shop/ShopField.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/compass/shop/ShopField.java new file mode 100644 index 0000000000..4acd91ace0 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/compass/shop/ShopField.java @@ -0,0 +1,44 @@ +package me.chanjar.weixin.channel.bean.compass.shop; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import java.util.List; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 维度数据 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +public class ShopField implements Serializable { + + private static final long serialVersionUID = -8669197081350262569L; + + /** 维度类别名 */ + @JsonProperty("field_name") + private String fieldName; + + /** 维度指标数据列表 */ + @JsonProperty("data_list") + private List dataList; + + + @Data + @NoArgsConstructor + public static class FieldDetail implements Serializable { + + private static final long serialVersionUID = 2900633035074950462L; + + /** 维度指标名 */ + @JsonProperty("dim_key") + private String dimKey; + + /** 维度指标值 */ + @JsonProperty("dim_value") + private String dimValue; + + } +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/compass/shop/ShopLiveData.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/compass/shop/ShopLiveData.java new file mode 100644 index 0000000000..d6a7b99451 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/compass/shop/ShopLiveData.java @@ -0,0 +1,36 @@ +package me.chanjar.weixin.channel.bean.compass.shop; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 店铺开播数据 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +public class ShopLiveData implements Serializable { + + /** 直播id */ + @JsonProperty("live_id") + private String liveId; + + /** 直播标题 */ + @JsonProperty("live_title") + private String liveTitle; + + /** 开播时间,unix时间戳 */ + @JsonProperty("live_time") + private String liveTime; + + /** 直播时长,单位秒 */ + @JsonProperty("live_duration") + private String liveDuration; + + /** 直播封面 */ + @JsonProperty("live_cover_img_url") + private String liveCoverImgUrl; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/compass/shop/ShopLiveListResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/compass/shop/ShopLiveListResponse.java new file mode 100644 index 0000000000..3ec9b68772 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/compass/shop/ShopLiveListResponse.java @@ -0,0 +1,26 @@ +package me.chanjar.weixin.channel.bean.compass.shop; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.List; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; + +/** + * 店铺开播列表 响应 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class ShopLiveListResponse extends WxChannelBaseResponse { + + private static final long serialVersionUID = -7110751559923117330L; + + /** 店铺开播列表 */ + @JsonProperty("live_list") + private List liveList; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/compass/shop/ShopOverall.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/compass/shop/ShopOverall.java new file mode 100644 index 0000000000..bf2fc8f42f --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/compass/shop/ShopOverall.java @@ -0,0 +1,42 @@ +package me.chanjar.weixin.channel.bean.compass.shop; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 电商概览数据 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +public class ShopOverall implements Serializable { + + private static final long serialVersionUID = 3304918097895132226L; + + /** 成交金额,单位分 */ + @JsonProperty("pay_gmv") + private String payGmv; + + /** 成交人数 */ + @JsonProperty("pay_uv") + private String payUv; + + /** 成交退款金额,单位分 */ + @JsonProperty("pay_refund_gmv") + private String payRefundGmv; + + /** 成交订单数 */ + @JsonProperty("pay_order_cnt") + private String payOrderCnt; + + /** 直播成交金额,单位分 */ + @JsonProperty("live_pay_gmv") + private String livePayGmv; + + /** 短视频成交金额,单位分 */ + @JsonProperty("feed_pay_gmv") + private String feedPayGmv; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/compass/shop/ShopOverallResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/compass/shop/ShopOverallResponse.java new file mode 100644 index 0000000000..4b371454ca --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/compass/shop/ShopOverallResponse.java @@ -0,0 +1,27 @@ +package me.chanjar.weixin.channel.bean.compass.shop; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; + +/** + * 获取电商概览数据响应 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class ShopOverallResponse extends WxChannelBaseResponse { + + private static final long serialVersionUID = 1632800741359642057L; + + /** + * 电商概览数据 + */ + @JsonProperty("data") + private ShopOverall data; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/compass/shop/ShopProductCompassData.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/compass/shop/ShopProductCompassData.java new file mode 100644 index 0000000000..03253e399e --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/compass/shop/ShopProductCompassData.java @@ -0,0 +1,143 @@ +package me.chanjar.weixin.channel.bean.compass.shop; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 店铺商品罗盘数据 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +public class ShopProductCompassData implements Serializable { + + private static final long serialVersionUID = 5387546181020447627L; + + /** 成交金额 */ + @JsonProperty("pay_gmv") + private String payGmv; + + /**下单金额,单位分 */ + @JsonProperty("create_gmv") + private String createGmv; + + /** 下单订单数 */ + @JsonProperty("create_cnt") + private String createCnt; + + /** 下单人数 */ + @JsonProperty("create_uv") + private String createUv; + + /** 下单件数 */ + @JsonProperty("create_product_cnt") + private String createProductCnt; + + /** 成交订单数 */ + @JsonProperty("pay_cnt") + private String payCnt; + + /** 成交人数 */ + @JsonProperty("pay_uv") + private String payUv; + + /** 成交件数 */ + @JsonProperty("pay_product_cnt") + private String payProductCnt; + + /** 成交金额(剔除退款) */ + @JsonProperty("pure_pay_gmv") + private String purePayGmv; + + /** 成交客单价(剔除退款) */ + @JsonProperty("pay_gmv_per_uv") + private String payGmvPerUv; + + /** 实际结算金额,单位分 */ + @JsonProperty("seller_actual_settle_amount") + private String sellerActualSettleAmount; + + /** 实际服务费金额,单位分 */ + @JsonProperty("platform_actual_commission") + private String platformActualCommission; + + /** 实际达人佣金支出,单位分 */ + @JsonProperty("finderuin_actual_commission") + private String finderuinActualCommission; + + /** 实际团长佣金支出,单位分 */ + @JsonProperty("captain_actual_commission") + private String captainActualCommission; + + /** 预估结算金额,单位分 */ + @JsonProperty("seller_predict_settle_amount") + private String sellerPredictSettleAmount; + + /** 预估服务费金额,单位分 */ + @JsonProperty("platform_predict_commission") + private String platformPredictCommission; + + /** 预估达人佣金支出,单位分 */ + @JsonProperty("finderuin_predict_commission") + private String finderuinPredictCommission; + + /** 预估团长佣金支出,单位分 */ + @JsonProperty("captain_predict_commission") + private String captainPredictCommission; + + /** 商品点击人数 */ + @JsonProperty("product_click_uv") + private String productClickUv; + + /** 商品点击次数 */ + @JsonProperty("product_click_cnt") + private String productClickCnt; + + /** 成交退款金额,单位分 */ + @JsonProperty("pay_refund_gmv") + private String payRefundGmv; + + /** 成交退款人数,单位分 */ + @JsonProperty("pay_refund_uv") + private String payRefundUv; + + /** 成交退款率 */ + @JsonProperty("pay_refund_ratio") + private Double payRefundRatio; + + /** 发货后成交退款率 */ + @JsonProperty("pay_refund_after_send_ratio") + private Double payRefundAfterSendRatio; + + /** 成交退款订单数 */ + @JsonProperty("pay_refund_cnt") + private String payRefundCnt; + + /** 成交退款件数 */ + @JsonProperty("pay_refund_product_cnt") + private String payRefundProductCnt; + + /** 发货前成交退款率 */ + @JsonProperty("pay_refund_before_send_ratio") + private Double payRefundBeforeSendRatio; + + /** 退款金额,单位分 */ + @JsonProperty("refund_gmv") + private String refundGmv; + + /** 退款件数 */ + @JsonProperty("refund_product_cnt") + private String refundProductCnt; + + /** 退款订单数 */ + @JsonProperty("refund_cnt") + private String refundCnt; + + /** 退款人数 */ + @JsonProperty("refund_uv") + private String refundUv; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/compass/shop/ShopProductDataParam.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/compass/shop/ShopProductDataParam.java new file mode 100644 index 0000000000..74d7306273 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/compass/shop/ShopProductDataParam.java @@ -0,0 +1,32 @@ +package me.chanjar.weixin.channel.bean.compass.shop; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.bean.compass.CompassFinderBaseParam; + +/** + * 商品数据 请求参数 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +public class ShopProductDataParam extends CompassFinderBaseParam { + + private static final long serialVersionUID = - 5016298274452168329L; + + /** 商品id */ + @JsonProperty("product_id") + private String productId; + + public ShopProductDataParam(String ds, String productId) { + super(ds); + this.productId = productId; + } + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/compass/shop/ShopProductDataResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/compass/shop/ShopProductDataResponse.java new file mode 100644 index 0000000000..bd7a22d243 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/compass/shop/ShopProductDataResponse.java @@ -0,0 +1,26 @@ +package me.chanjar.weixin.channel.bean.compass.shop; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; + +/** + * 商品详细信息 响应 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class ShopProductDataResponse extends WxChannelBaseResponse { + + private static final long serialVersionUID = 6903392663954301579L; + + /** 商品详细信息 */ + @JsonProperty("product_info") + private ShopProductInfo productInfo; + + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/compass/shop/ShopProductInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/compass/shop/ShopProductInfo.java new file mode 100644 index 0000000000..1eb55eaa75 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/compass/shop/ShopProductInfo.java @@ -0,0 +1,51 @@ +package me.chanjar.weixin.channel.bean.compass.shop; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 店铺带货商品数据 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +public class ShopProductInfo implements Serializable { + + private static final long serialVersionUID = 3376047696301017643L; + + /** 商品id */ + @JsonProperty("product_id") + private String productId; + + /** 商品图 */ + @JsonProperty("head_img_url") + private String headImgUrl; + + /** 商品标题 */ + @JsonProperty("title") + private String title; + + /** 商品价格,单位分 */ + @JsonProperty("price") + private String price; + + /** 商品一级类目 */ + @JsonProperty("first_category_id") + private String firstCategoryId; + + /** 商品二级类目 */ + @JsonProperty("second_category_id") + private String secondCategoryId; + + /** 商品三级类目 */ + @JsonProperty("third_category_id") + private String thirdCategoryId; + + /** 商品罗盘数据 */ + @JsonProperty("data") + private ShopProductCompassData data; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/compass/shop/ShopProductListResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/compass/shop/ShopProductListResponse.java new file mode 100644 index 0000000000..258b8f5845 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/compass/shop/ShopProductListResponse.java @@ -0,0 +1,26 @@ +package me.chanjar.weixin.channel.bean.compass.shop; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.List; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; + +/** + * 商品列表 响应 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class ShopProductListResponse extends WxChannelBaseResponse { + + private static final long serialVersionUID = -6328224902770141045L; + + /** 商品列表 */ + @JsonProperty("product_list") + private List productList; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/compass/shop/ShopSaleProfileData.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/compass/shop/ShopSaleProfileData.java new file mode 100644 index 0000000000..23639c5356 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/compass/shop/ShopSaleProfileData.java @@ -0,0 +1,24 @@ +package me.chanjar.weixin.channel.bean.compass.shop; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import java.util.List; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 店铺人群数据 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +public class ShopSaleProfileData implements Serializable { + + private static final long serialVersionUID = -6825849811081728787L; + + /** 维度数据列表 */ + @JsonProperty("field_list") + private List fieldList; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/compass/shop/ShopSaleProfileDataParam.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/compass/shop/ShopSaleProfileDataParam.java new file mode 100644 index 0000000000..36cab13860 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/compass/shop/ShopSaleProfileDataParam.java @@ -0,0 +1,32 @@ +package me.chanjar.weixin.channel.bean.compass.shop; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.bean.compass.CompassFinderBaseParam; + +/** + * 获取带货人群数据请求参数 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +public class ShopSaleProfileDataParam extends CompassFinderBaseParam { + + private static final long serialVersionUID = 240010632808576923L; + + /** 用户类型 */ + @JsonProperty("type") + private Integer type; + + public ShopSaleProfileDataParam(String ds, Integer type) { + super(ds); + this.type = type; + } + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/compass/shop/ShopSaleProfileDataResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/compass/shop/ShopSaleProfileDataResponse.java new file mode 100644 index 0000000000..a874cd6355 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/compass/shop/ShopSaleProfileDataResponse.java @@ -0,0 +1,25 @@ +package me.chanjar.weixin.channel.bean.compass.shop; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; + +/** + * 店铺人群数据 响应 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class ShopSaleProfileDataResponse extends WxChannelBaseResponse { + + private static final long serialVersionUID = 8520148855114842741L; + + /** 店铺人群数据 */ + @JsonProperty("data") + private ShopSaleProfileData data; + +} 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 new file mode 100644 index 0000000000..84a558b2b1 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/complaint/ComplaintHistory.java @@ -0,0 +1,46 @@ +package me.chanjar.weixin.channel.bean.complaint; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import java.util.List; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 纠纷历史 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +public class ComplaintHistory implements Serializable { + + private static final long serialVersionUID = -4706637116597650133L; + /** 历史操作类型,见 {@link me.chanjar.weixin.channel.enums.ComplaintItemType } */ + @JsonProperty("item_type") + private Integer itemType; + + /** 操作时间,Unix时间戳 */ + @JsonProperty("time") + private Long time; + + /** 用户联系电话 */ + @JsonProperty("phone_number") + private String phoneNumber; + + /** 相关文本内容 */ + @JsonProperty("content") + private String content; + + /** 相关图片media_id列表 */ + @JsonProperty("media_id_list") + private List mediaIds; + + /** 售后类型, 1-仅退款 2-退货退款 */ + @JsonProperty("after_sale_type") + private Integer afterSaleType; + + /** 售后原因,见 {@link me.chanjar.weixin.channel.enums.AfterSalesReason} */ + @JsonProperty("after_sale_reason") + private Integer afterSaleReason; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/complaint/ComplaintOrderResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/complaint/ComplaintOrderResponse.java new file mode 100644 index 0000000000..a0a8ec1e18 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/complaint/ComplaintOrderResponse.java @@ -0,0 +1,35 @@ +package me.chanjar.weixin.channel.bean.complaint; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.List; +import lombok.Data; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; + +/** + * 纠纷单响应 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +public class ComplaintOrderResponse extends WxChannelBaseResponse { + + private static final long serialVersionUID = 1968530826349555367L; + /** 售后单号 */ + @JsonProperty("after_sale_order_id") + private String afterSaleOrderId; + + /** 订单号 */ + @JsonProperty("order_id") + private String orderId; + + /** 纠纷历史 */ + @JsonProperty("history") + private List history; + + /** 纠纷单状态, 见 {@link me.chanjar.weixin.channel.enums.ComplaintStatus} */ + @JsonProperty("status") + private Integer status; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/complaint/ComplaintParam.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/complaint/ComplaintParam.java new file mode 100644 index 0000000000..0090348efe --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/complaint/ComplaintParam.java @@ -0,0 +1,34 @@ +package me.chanjar.weixin.channel.bean.complaint; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import java.util.List; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 纠纷单留言 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@JsonInclude(JsonInclude.Include.NON_NULL) +public class ComplaintParam implements Serializable { + + private static final long serialVersionUID = 6146118590005718327L; + /** 纠纷单号 */ + @JsonProperty("complaint_id") + private String complaintId; + + /** 留言内容,最多500字 */ + @JsonProperty("content") + private String content; + + /** 图片media_id列表,所有留言总图片数量最多20张 */ + @JsonProperty("media_id_list") + private List mediaIds; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/cooperation/CooperationData.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/cooperation/CooperationData.java new file mode 100644 index 0000000000..41020f4993 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/cooperation/CooperationData.java @@ -0,0 +1,47 @@ +package me.chanjar.weixin.channel.bean.cooperation; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 合作账号信息 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +public class CooperationData implements Serializable { + + private static final long serialVersionUID = 3930010847236599458L; + + /** 合作账号id 公众号: gh_开头id 小程序: appid */ + @JsonProperty("sharer_id") + private String sharerId; + + /** 邀请/合作账号状态 1已绑定 2已解绑 3邀请已拒绝 4邀请接受中 5邀请接受超时 6邀请接受失败 7邀请店铺取消 */ + @JsonProperty("status") + private Integer status; + + /** 合作账号名称 */ + @JsonProperty("sharer_name") + private String sharerName; + + /** 合作账号类型 2公众号 3小程序 */ + @JsonProperty("sharer_type") + private Integer sharerType; + + /** 接受绑定时间戳,ms */ + @JsonProperty("bind_time") + private Long bindTime; + + /** 用户拒绝时间戳,ms */ + @JsonProperty("reject_time") + private Long rejectTime; + + /** 商家取消时间戳,ms */ + @JsonProperty("cancel_time") + private Long cancelTime; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/cooperation/CooperationListResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/cooperation/CooperationListResponse.java new file mode 100644 index 0000000000..1b652b64d6 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/cooperation/CooperationListResponse.java @@ -0,0 +1,25 @@ +package me.chanjar.weixin.channel.bean.cooperation; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.List; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; + +/** + * 合作账号列表响应 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class CooperationListResponse extends WxChannelBaseResponse { + + private static final long serialVersionUID = 6998637882644598826L; + + /** 合作账号列表 */ + @JsonProperty("data_list") + private List dataList; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/cooperation/CooperationQrCode.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/cooperation/CooperationQrCode.java new file mode 100644 index 0000000000..272b9802da --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/cooperation/CooperationQrCode.java @@ -0,0 +1,23 @@ +package me.chanjar.weixin.channel.bean.cooperation; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 合作账号二维码数据 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +public class CooperationQrCode implements Serializable { + + private static final long serialVersionUID = -7096916911986699150L; + + /** base64编码后的图片数据 */ + @JsonProperty("qrcode_base64") + private Integer qrCodeBase64; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/cooperation/CooperationQrCodeResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/cooperation/CooperationQrCodeResponse.java new file mode 100644 index 0000000000..b18b2b1c85 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/cooperation/CooperationQrCodeResponse.java @@ -0,0 +1,24 @@ +package me.chanjar.weixin.channel.bean.cooperation; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; + +/** + * 合作账号二维码响应 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class CooperationQrCodeResponse extends WxChannelBaseResponse { + + private static final long serialVersionUID = 6998637882644598826L; + + /** 合作账号二维码 */ + @JsonProperty("data") + private CooperationQrCode data; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/cooperation/CooperationSharerParam.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/cooperation/CooperationSharerParam.java new file mode 100644 index 0000000000..4ca9bd8344 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/cooperation/CooperationSharerParam.java @@ -0,0 +1,30 @@ +package me.chanjar.weixin.channel.bean.cooperation; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 合作账号参数 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@JsonInclude(JsonInclude.Include.NON_NULL) +public class CooperationSharerParam implements Serializable { + + private static final long serialVersionUID = 5032621997764493109L; + + /** 合作账号id */ + @JsonProperty("sharer_id") + private String sharerId; + + /** 合作账号类型 */ + @JsonProperty("sharer_type") + private Integer sharerType; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/cooperation/CooperationStatus.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/cooperation/CooperationStatus.java new file mode 100644 index 0000000000..5267be6153 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/cooperation/CooperationStatus.java @@ -0,0 +1,23 @@ +package me.chanjar.weixin.channel.bean.cooperation; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 合作账号状态 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +public class CooperationStatus implements Serializable { + + private static final long serialVersionUID = -7096916911986699150L; + + /** 邀请/合作账号状态 1已绑定 2已解绑 3邀请已拒绝 4邀请接受中 5邀请接受超时 6邀请接受失败 7邀请店铺取消 */ + @JsonProperty("status") + private Integer status; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/cooperation/CooperationStatusResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/cooperation/CooperationStatusResponse.java new file mode 100644 index 0000000000..6507340c63 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/cooperation/CooperationStatusResponse.java @@ -0,0 +1,24 @@ +package me.chanjar.weixin.channel.bean.cooperation; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; + +/** + * 合作账号状态响应 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class CooperationStatusResponse extends WxChannelBaseResponse { + + private static final long serialVersionUID = 6998637882644598826L; + + /** 合作账号状态 */ + @JsonProperty("data") + private CooperationStatus data; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/coupon/AutoValidInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/coupon/AutoValidInfo.java new file mode 100644 index 0000000000..73c09def1e --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/coupon/AutoValidInfo.java @@ -0,0 +1,21 @@ +package me.chanjar.weixin.channel.bean.coupon; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 自动生效信息 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +public class AutoValidInfo implements Serializable { + + private static final long serialVersionUID = 1702505613539861103L; + /** 优惠券开启自动生效类型 0不启用自动生效 1启用自动生效,按领券开始时间(自动生效时间为 receive_info.start_time) */ + @JsonProperty("auto_valid_type") + private Integer autoValidType; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/coupon/CouponDetailInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/coupon/CouponDetailInfo.java new file mode 100644 index 0000000000..34f76716f9 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/coupon/CouponDetailInfo.java @@ -0,0 +1,43 @@ +package me.chanjar.weixin.channel.bean.coupon; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 优惠券信息 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor + +public class CouponDetailInfo implements Serializable { + + private static final long serialVersionUID = 5994815232349181577L; + /** 优惠券名称 **/ + @JsonProperty("name") + private String name; + + /** 优惠券有效信息 **/ + @JsonProperty("valid_info") + private ValidInfo validInfo; + + /** 推广信息 **/ + @JsonProperty("promote_info") + private PromoteInfo promoteInfo; + + /** 优惠信息 **/ + @JsonProperty("discount_info") + private DiscountInfo discountInfo; + + /** 额外信息 **/ + @JsonProperty("ext_info") + private ExtInfo extInfo; + + /** 领取信息 **/ + @JsonProperty("receive_info") + private ReceiveInfo receiveInfo; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/coupon/CouponIdInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/coupon/CouponIdInfo.java new file mode 100644 index 0000000000..b787016a09 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/coupon/CouponIdInfo.java @@ -0,0 +1,24 @@ +package me.chanjar.weixin.channel.bean.coupon; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 优惠券id + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class CouponIdInfo implements Serializable { + + private static final long serialVersionUID = 6284609705855608275L; + /** 优惠券ID */ + @JsonProperty("coupon_id") + private String couponId; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/coupon/CouponIdResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/coupon/CouponIdResponse.java new file mode 100644 index 0000000000..7556fa6f11 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/coupon/CouponIdResponse.java @@ -0,0 +1,21 @@ +package me.chanjar.weixin.channel.bean.coupon; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; + + +/** + * @author Zeyes + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class CouponIdResponse extends WxChannelBaseResponse { + + private static final long serialVersionUID = -3263189706802013651L; + @JsonProperty("data") + private CouponIdInfo data; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/coupon/CouponInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/coupon/CouponInfo.java new file mode 100644 index 0000000000..cd247f9d71 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/coupon/CouponInfo.java @@ -0,0 +1,38 @@ +package me.chanjar.weixin.channel.bean.coupon; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * @author Zeyes + */ +@Data +@NoArgsConstructor +public class CouponInfo extends CouponIdInfo { + + private static final long serialVersionUID = -5862063828870424262L; + /** 优惠券类型 **/ + @JsonProperty("type") + private Integer type; + + /** 优惠券状态 **/ + @JsonProperty("status") + private Integer status; + + /** 优惠券创建时间 */ + @JsonProperty("create_time") + private Long createTime; + + /** 优惠券更新时间 */ + @JsonProperty("update_time") + private Long updateTime; + + /** 优惠券信息 */ + @JsonProperty("coupon_info") + private CouponDetailInfo detail; + + /** 库存信息 */ + @JsonProperty("stock_info") + private StockInfo stockInfo; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/coupon/CouponInfoResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/coupon/CouponInfoResponse.java new file mode 100644 index 0000000000..801843025e --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/coupon/CouponInfoResponse.java @@ -0,0 +1,20 @@ +package me.chanjar.weixin.channel.bean.coupon; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; + +/** + * @author Zeyes + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class CouponInfoResponse extends WxChannelBaseResponse { + + private static final long serialVersionUID = 5261320058699488529L; + @JsonProperty("coupon") + private CouponInfo coupon; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/coupon/CouponListParam.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/coupon/CouponListParam.java new file mode 100644 index 0000000000..6c7fc03a6e --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/coupon/CouponListParam.java @@ -0,0 +1,45 @@ +package me.chanjar.weixin.channel.bean.coupon; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * 获取优惠券ID列表接口的请求参数 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@JsonInclude(Include.NON_NULL) +public class CouponListParam implements Serializable { + private static final long serialVersionUID = 7123047113279657365L; + + /** + * 优惠券状态 {@link me.chanjar.weixin.channel.enums.WxCouponStatus} + */ + @JsonProperty("status") + private Integer status; + + /** + * 第几页(最小填1) + */ + @JsonProperty("page") + private Integer page; + + /** + * 每页数量(不超过200) + */ + @JsonProperty("page_size") + private Integer pageSize; + + /** + * 分页上下文 + */ + @JsonProperty("page_ctx") + private String pageCtx; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/coupon/CouponListResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/coupon/CouponListResponse.java new file mode 100644 index 0000000000..66d6f63eef --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/coupon/CouponListResponse.java @@ -0,0 +1,31 @@ +package me.chanjar.weixin.channel.bean.coupon; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.List; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; + +/** + * @author Zeyes + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class CouponListResponse extends WxChannelBaseResponse { + + private static final long serialVersionUID = -5330296358041282751L; + /** 优惠券id列表 */ + @JsonProperty("coupons") + private List coupons; + + /** 优惠券总数 */ + @JsonProperty("total_num") + private Integer totalNum; + + /** 优惠券上下文 */ + @JsonProperty("page_ctx") + private String pageCtx; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/coupon/CouponParam.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/coupon/CouponParam.java new file mode 100644 index 0000000000..fa89b0a1e4 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/coupon/CouponParam.java @@ -0,0 +1,50 @@ +package me.chanjar.weixin.channel.bean.coupon; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +/** + * 优惠券参数 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class CouponParam extends CouponIdInfo { + + private static final long serialVersionUID = -3663331372622943337L; + /** 优惠券类型 **/ + @JsonProperty("type") + private Integer type; + + /** 优惠券名称,最长10个中文字符 */ + @JsonProperty("name") + private String name; + + /** 优惠信息 **/ + @JsonProperty("discount_info") + private DiscountInfo discountInfo; + + /** 额外信息 **/ + @JsonProperty("ext_info") + private ExtInfo extInfo; + + /** 推广信息 **/ + @JsonProperty("promote_info") + private PromoteInfo promoteInfo; + + /** 领取信息 **/ + @JsonProperty("receive_info") + private ReceiveInfo receiveInfo; + + /** 优惠券有效信息 **/ + @JsonProperty("valid_info") + private ValidInfo validInfo; + + /** 优惠券自动生效信息 **/ + @JsonProperty("auto_valid_info") + private AutoValidInfo autoValidInfo; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/coupon/CouponStatusParam.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/coupon/CouponStatusParam.java new file mode 100644 index 0000000000..405ad52400 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/coupon/CouponStatusParam.java @@ -0,0 +1,28 @@ +package me.chanjar.weixin.channel.bean.coupon; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * @author Zeyes + */ +@Data +@EqualsAndHashCode(callSuper = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +public class CouponStatusParam extends CouponIdInfo { + + private static final long serialVersionUID = -7108348049925634704L; + /** 状态 */ + @JsonProperty("status") + private Integer status; + + public CouponStatusParam() { + } + + public CouponStatusParam(String couponId, Integer status) { + super(couponId); + this.status = status; + } +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/coupon/DiscountCondition.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/coupon/DiscountCondition.java new file mode 100644 index 0000000000..e249455526 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/coupon/DiscountCondition.java @@ -0,0 +1,30 @@ +package me.chanjar.weixin.channel.bean.coupon; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import java.util.List; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 折扣条件 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +public class DiscountCondition implements Serializable { + + private static final long serialVersionUID = 3250293381093835082L; + /** 优惠券使用条件, 满 x 件商品可用 */ + @JsonProperty("product_cnt") + private Integer productCnt; + + /** 优惠券使用条件, 价格满 x 可用,单位分 */ + @JsonProperty("product_price") + private Integer productPrice; + + /** 优惠券使用条件, 指定商品 id 可用 */ + @JsonProperty("product_ids") + private List productIds; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/coupon/DiscountInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/coupon/DiscountInfo.java new file mode 100644 index 0000000000..7988e47ce6 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/coupon/DiscountInfo.java @@ -0,0 +1,29 @@ +package me.chanjar.weixin.channel.bean.coupon; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 优惠信息 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +public class DiscountInfo implements Serializable { + + private static final long serialVersionUID = 3660070880545144112L; + /** 优惠券折扣数 * 1000, 例如 5.1折-> 5100 */ + @JsonProperty("discount_num") + private Integer discountNum; + + /** 优惠券减少金额, 单位分, 例如0.5元-> 50 */ + @JsonProperty("discount_fee") + private Integer discountFee; + + /** 优惠条件 */ + @JsonProperty("discount_condition") + private DiscountCondition discountCondition; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/coupon/ExtInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/coupon/ExtInfo.java new file mode 100644 index 0000000000..69cf3dc073 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/coupon/ExtInfo.java @@ -0,0 +1,33 @@ +package me.chanjar.weixin.channel.bean.coupon; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 额外信息 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +public class ExtInfo implements Serializable { + + private static final long serialVersionUID = 9053035437087423233L; + /** 商品折扣券领取后跳转的商品id **/ + @JsonProperty("jump_product_id") + private String jumpProductId; + + /** 备注信息 **/ + @JsonProperty("notes") + private String notes; + + /** 优惠券有效时间 **/ + @JsonProperty("valid_time") + private Long validTime; + + /** 优惠券失效时间戳 **/ + @JsonProperty("invalid_time") + private Long invalidTime; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/coupon/PromoteInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/coupon/PromoteInfo.java new file mode 100644 index 0000000000..75d48e6d3e --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/coupon/PromoteInfo.java @@ -0,0 +1,21 @@ +package me.chanjar.weixin.channel.bean.coupon; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 推广信息 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +public class PromoteInfo implements Serializable { + + private static final long serialVersionUID = -3030639750899957382L; + /** 推广类型 {@link me.chanjar.weixin.channel.enums.PromoteType} */ + @JsonProperty("promote_type") + private Integer promoteType; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/coupon/ReceiveInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/coupon/ReceiveInfo.java new file mode 100644 index 0000000000..9a602ac390 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/coupon/ReceiveInfo.java @@ -0,0 +1,33 @@ +package me.chanjar.weixin.channel.bean.coupon; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 领取信息 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +public class ReceiveInfo implements Serializable { + + private static final long serialVersionUID = 755956808504040633L; + /** 优惠券领用结束时间 **/ + @JsonProperty("end_time") + private Long endTime; + + /** 单人限领张数 **/ + @JsonProperty("limit_num_one_person") + private Integer limitNumOnePerson; + + /** 优惠券领用开始时间 **/ + @JsonProperty("start_time") + private Long startTime; + + /** 优惠券领用总数 **/ + @JsonProperty("total_num") + private Integer totalNum; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/coupon/StockInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/coupon/StockInfo.java new file mode 100644 index 0000000000..07aaf4a1ec --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/coupon/StockInfo.java @@ -0,0 +1,29 @@ +package me.chanjar.weixin.channel.bean.coupon; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 库存信息 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +public class StockInfo implements Serializable { + + private static final long serialVersionUID = -6078383881065929862L; + /** 优惠券剩余量 */ + @JsonProperty("issued_num") + private Integer issuedNum; + + /** 优惠券领用量 */ + @JsonProperty("receive_num") + private Integer receiveNum; + + /** 优惠券已用量 */ + @JsonProperty("used_num") + private Integer usedNum; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/coupon/UserCoupon.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/coupon/UserCoupon.java new file mode 100644 index 0000000000..06436a9e73 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/coupon/UserCoupon.java @@ -0,0 +1,50 @@ +package me.chanjar.weixin.channel.bean.coupon; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +/** + * 用户优惠券 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class UserCoupon extends UserCouponIdInfo { + + private static final long serialVersionUID = -4777537717885622888L; + /** 优惠券状态 {@link me.chanjar.weixin.channel.enums.UserCouponStatus} */ + @JsonProperty("status") + private Integer status; + + /** 优惠券派发时间 */ + @JsonProperty("create_time") + private Long createTime; + + /** 优惠券更新时间 */ + @JsonProperty("update_time") + private Long updateTime; + + /** 优惠券生效时间 */ + @JsonProperty("start_time") + private Long startTime; + + /** 优惠券失效时间 */ + @JsonProperty("end_time") + private Long endTime; + + /** 附加信息 */ + @JsonProperty("ext_info") + private UserExtInfo extInfo; + + /** 优惠券使用的订单id */ + @JsonProperty("order_id") + private String orderId; + + /** 优惠券金额 */ + @JsonProperty("discount_fee") + private Integer discountFee; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/coupon/UserCouponIdInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/coupon/UserCouponIdInfo.java new file mode 100644 index 0000000000..d68d881c98 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/coupon/UserCouponIdInfo.java @@ -0,0 +1,20 @@ +package me.chanjar.weixin.channel.bean.coupon; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 用户优惠券id + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +public class UserCouponIdInfo extends CouponIdInfo { + + private static final long serialVersionUID = -8285585134793264542L; + /** 用户优惠券ID */ + @JsonProperty("user_coupon_id") + private String userCouponId; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/coupon/UserCouponIdParam.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/coupon/UserCouponIdParam.java new file mode 100644 index 0000000000..aa2eb15421 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/coupon/UserCouponIdParam.java @@ -0,0 +1,29 @@ +package me.chanjar.weixin.channel.bean.coupon; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.Data; + +/** + * @author Zeyes + */ +@Data +public class UserCouponIdParam implements Serializable { + + private static final long serialVersionUID = 3967276158727848348L; + /** 用户openid */ + @JsonProperty("openid") + private String openid; + + /** 用户优惠券ID */ + @JsonProperty("user_coupon_id") + private String userCouponId; + + public UserCouponIdParam() { + } + + public UserCouponIdParam(String openid, String userCouponId) { + this.openid = openid; + this.userCouponId = userCouponId; + } +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/coupon/UserCouponListParam.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/coupon/UserCouponListParam.java new file mode 100644 index 0000000000..f14f5d7f6e --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/coupon/UserCouponListParam.java @@ -0,0 +1,24 @@ +package me.chanjar.weixin.channel.bean.coupon; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +/** + * @author Zeyes + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +public class UserCouponListParam extends CouponListParam { + private static final long serialVersionUID = -1056132009327357435L; + + /** + * openId + */ + @JsonProperty("openid") + private String openId; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/coupon/UserCouponListResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/coupon/UserCouponListResponse.java new file mode 100644 index 0000000000..2c3582e678 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/coupon/UserCouponListResponse.java @@ -0,0 +1,30 @@ +package me.chanjar.weixin.channel.bean.coupon; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.List; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; + +/** + * @author Zeyes + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class UserCouponListResponse extends WxChannelBaseResponse { + + private static final long serialVersionUID = 5201633937239352879L; + /** 优惠券id列表 */ + @JsonProperty("user_coupon_list") + private List coupons; + + /** 优惠券总数 */ + @JsonProperty("total_num") + private Integer totalNum; + + /** 优惠券上下文 */ + @JsonProperty("page_ctx") + private String pageCtx; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/coupon/UserCouponResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/coupon/UserCouponResponse.java new file mode 100644 index 0000000000..aeb9d89afb --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/coupon/UserCouponResponse.java @@ -0,0 +1,27 @@ +package me.chanjar.weixin.channel.bean.coupon; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; + +/** + * @author Zeyes + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class UserCouponResponse extends WxChannelBaseResponse { + + private static final long serialVersionUID = 1434098386857953234L; + @JsonProperty("user_coupon") + private UserCoupon coupon; + + @JsonProperty("openid") + private String openid; + + @JsonProperty("unionid") + private String unionid; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/coupon/UserExtInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/coupon/UserExtInfo.java new file mode 100644 index 0000000000..18962361ec --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/coupon/UserExtInfo.java @@ -0,0 +1,21 @@ +package me.chanjar.weixin.channel.bean.coupon; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 用户优惠券附加信息 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +public class UserExtInfo implements Serializable { + + private static final long serialVersionUID = 8304922825230343409L; + /** 优惠券核销时间 */ + @JsonProperty("use_time") + private Long useTime; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/coupon/ValidInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/coupon/ValidInfo.java new file mode 100644 index 0000000000..10df794324 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/coupon/ValidInfo.java @@ -0,0 +1,33 @@ +package me.chanjar.weixin.channel.bean.coupon; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 优惠券有效信息 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +public class ValidInfo implements Serializable { + + private static final long serialVersionUID = -4550516248380285635L; + /** 优惠券有效期类型 {@link me.chanjar.weixin.channel.enums.CouponValidType} */ + @JsonProperty("valid_type") + private Integer validType; + + /** 优惠券有效天数,valid_type=2时才有意义 */ + @JsonProperty("valid_day_num") + private Integer validDayNum; + + /** 优惠券有效期开始时间,valid_type=1时才有意义 */ + @JsonProperty("start_time") + private Long startTime; + + /** 优惠券有效期结束时间,valid_type=1时才有意义 */ + @JsonProperty("end_time") + private Long endTime; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/delivery/DeliveryCompanyInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/delivery/DeliveryCompanyInfo.java new file mode 100644 index 0000000000..349d70cbb1 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/delivery/DeliveryCompanyInfo.java @@ -0,0 +1,25 @@ +package me.chanjar.weixin.channel.bean.delivery; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 快递公司信息 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +public class DeliveryCompanyInfo implements Serializable { + + private static final long serialVersionUID = 4225666604513570564L; + /** 快递公司id */ + @JsonProperty("delivery_id") + private String id; + + /** 快递公司名称 */ + @JsonProperty("delivery_name") + private String name; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/delivery/DeliveryCompanyResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/delivery/DeliveryCompanyResponse.java new file mode 100644 index 0000000000..d74a9439ea --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/delivery/DeliveryCompanyResponse.java @@ -0,0 +1,22 @@ +package me.chanjar.weixin.channel.bean.delivery; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.List; +import lombok.Data; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; + +/** + * 快递公司列表响应 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +public class DeliveryCompanyResponse extends WxChannelBaseResponse { + + private static final long serialVersionUID = -7695903997951385166L; + /** 快递公司 */ + @JsonProperty("company_list") + private List companyList; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/delivery/DeliveryInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/delivery/DeliveryInfo.java new file mode 100644 index 0000000000..23ab8dad2c --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/delivery/DeliveryInfo.java @@ -0,0 +1,34 @@ +package me.chanjar.weixin.channel.bean.delivery; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import java.util.List; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 物流信息 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +public class DeliveryInfo implements Serializable { + + private static final long serialVersionUID = -6205626967305385248L; + /** 快递单号 */ + @JsonProperty("waybill_id") + private String waybillId; + + /** 快递公司id,通过【获取快递公司列表】接口获得,非主流快递公司可以填OTHER */ + @JsonProperty("delivery_id") + private String deliveryId; + + /** 发货方式,1:自寄快递发货,3:虚拟商品无需物流发货(只有deliver_method=1的订单可以使用虚拟发货) */ + @JsonProperty("deliver_type") + private Integer deliverType; + + /** 包裹中商品信息 */ + @JsonProperty("product_infos") + private List productInfos; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/delivery/DeliverySendParam.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/delivery/DeliverySendParam.java new file mode 100644 index 0000000000..f486032bc4 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/delivery/DeliverySendParam.java @@ -0,0 +1,31 @@ +package me.chanjar.weixin.channel.bean.delivery; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import java.util.List; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 订单发货信息 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@JsonInclude(Include.NON_NULL) +public class DeliverySendParam implements Serializable { + + private static final long serialVersionUID = 4555821308266899135L; + /** 订单ID */ + @JsonProperty("order_id") + private String orderId; + + /** 物流信息 */ + @JsonProperty("delivery_list") + private List deliveryList; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/delivery/FreightProductInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/delivery/FreightProductInfo.java new file mode 100644 index 0000000000..2a7c7dd3c6 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/delivery/FreightProductInfo.java @@ -0,0 +1,36 @@ +package me.chanjar.weixin.channel.bean.delivery; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * 包裹中商品信息 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +public class FreightProductInfo implements Serializable { + private static final long serialVersionUID = -3751269707150372172L; + + /** + * 商品id + */ + @JsonProperty("product_id") + private String productId; + + /** + * sku_id + */ + @JsonProperty("sku_id") + private String skuId; + + /** + * 商品数量 + */ + @JsonProperty("product_cnt") + private Integer productCnt; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/delivery/FreshInspectParam.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/delivery/FreshInspectParam.java new file mode 100644 index 0000000000..a6db90f2f9 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/delivery/FreshInspectParam.java @@ -0,0 +1,31 @@ +package me.chanjar.weixin.channel.bean.delivery; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import java.util.List; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 商品打包信息 参数 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@JsonInclude(Include.NON_NULL) +public class FreshInspectParam implements Serializable { + private static final long serialVersionUID = -1635894867602084789L; + + /** 订单ID */ + @JsonProperty("order_id") + private String orderId; + + /** 商品打包信息 */ + @JsonProperty("audit_items") + private List auditItems; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/delivery/PackageAuditInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/delivery/PackageAuditInfo.java new file mode 100644 index 0000000000..bbb4e6c484 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/delivery/PackageAuditInfo.java @@ -0,0 +1,32 @@ +package me.chanjar.weixin.channel.bean.delivery; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.enums.PackageAuditItemType; + +/** + * 商品打包信息 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class PackageAuditInfo implements Serializable { + private static final long serialVersionUID = 1118087167138310282L; + + /** + * 审核项名称,枚举类型参考 {@link PackageAuditItemType} + * 使用方法:DeliveryAuditItemType.EXPRESS_PIC.getKey() + */ + @JsonProperty("item_name") + private String itemName; + + /** 图片/视频url */ + @JsonProperty("item_value") + private String itemValue; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/freight/AddressInfoList.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/freight/AddressInfoList.java new file mode 100644 index 0000000000..4d8c7ec4a5 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/freight/AddressInfoList.java @@ -0,0 +1,23 @@ +package me.chanjar.weixin.channel.bean.freight; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import java.util.List; +import lombok.Data; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.bean.base.AddressInfo; + +/** + * 地址列表 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +public class AddressInfoList implements Serializable { + + private static final long serialVersionUID = 5923805297331862706L; + /** 地址列表 */ + @JsonProperty("address_infos") + private List addressInfos; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/freight/AllConditionFreeDetail.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/freight/AllConditionFreeDetail.java new file mode 100644 index 0000000000..fd9aee451d --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/freight/AllConditionFreeDetail.java @@ -0,0 +1,32 @@ +package me.chanjar.weixin.channel.bean.freight; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 计费规则列表 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +public class AllConditionFreeDetail implements Serializable { + + private static final long serialVersionUID = -1649520737632417036L; + /** 计费规则列表 */ + @JsonProperty("condition_free_detail_list") + private List list; + + @JsonIgnore + public void addDetail(ConditionFreeDetail detail) { + if (list == null) { + list = new ArrayList<>(16); + } + list.add(detail); + } +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/freight/AllFreightCalcMethod.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/freight/AllFreightCalcMethod.java new file mode 100644 index 0000000000..2c5523ebe4 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/freight/AllFreightCalcMethod.java @@ -0,0 +1,31 @@ +package me.chanjar.weixin.channel.bean.freight; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; +import lombok.Data; + +/** + * 具体计费方法,默认运费,指定地区运费等 + * + * @author Zeyes + */ +@Data +public class AllFreightCalcMethod implements Serializable { + + private static final long serialVersionUID = 6330919525271991949L; + /** 计算方法列表 */ + @JsonProperty("freight_calc_method_list") + private List list; + + public AllFreightCalcMethod() { + } + + public void addDetail(FreightCalcMethod detail) { + if (list == null) { + list = new ArrayList<>(16); + } + list.add(detail); + } +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/freight/ConditionFreeDetail.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/freight/ConditionFreeDetail.java new file mode 100644 index 0000000000..cd0b76990d --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/freight/ConditionFreeDetail.java @@ -0,0 +1,38 @@ +package me.chanjar.weixin.channel.bean.freight; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +/** + * 计费规则 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class ConditionFreeDetail extends AddressInfoList { + + private static final long serialVersionUID = 9204578767029379142L; + /** 最低件数 */ + @JsonProperty("min_piece") + private Integer minPiece; + + /** 最低重量,单位千克,订单商品总质量小于一千克,算作一千克 */ + @JsonProperty("min_weight") + private Double minWeight; + + /** 最低金额,单位(分) */ + @JsonProperty("min_amount") + private Integer minAmount; + + /** 计费方式对应的选项是否已设置 */ + @JsonProperty("valuation_flag") + private Integer valuationFlag; + + /** 金额是否设置 */ + @JsonProperty("amount_flag") + private Integer amountFlag; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/freight/FreightCalcMethod.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/freight/FreightCalcMethod.java new file mode 100644 index 0000000000..aab949bc44 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/freight/FreightCalcMethod.java @@ -0,0 +1,43 @@ +package me.chanjar.weixin.channel.bean.freight; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +/** + * 运费计算方法 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class FreightCalcMethod extends AddressInfoList { + + private static final long serialVersionUID = -8857987538121721376L; + /** 是否默认运费 */ + @JsonProperty("is_default") + private Boolean isDefault; + + /** 快递公司 */ + @JsonProperty("delivery_id") + private String deliveryId; + + /** 首段运费需要满足的数量 */ + @JsonProperty("first_val_amount") + private Integer firstValAmount; + + /** 首段运费的金额 */ + @JsonProperty("first_price") + private Integer firstPrice; + + /** 续费的数量 */ + @JsonProperty("second_val_amount") + private Integer secondValAmount; + + /** 续费的金额 */ + @JsonProperty("second_price") + private Integer secondPrice; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/freight/FreightTemplate.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/freight/FreightTemplate.java new file mode 100644 index 0000000000..e28f90ad41 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/freight/FreightTemplate.java @@ -0,0 +1,71 @@ +package me.chanjar.weixin.channel.bean.freight; + +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 FreightTemplate implements Serializable { + + private static final long serialVersionUID = -7876281924385999053L; + /** 模板id */ + @JsonProperty("template_id") + private String templateId; + + /** 模板名称 */ + @JsonProperty("name") + private String name; + + /** 计费类型,PIECE:按件数,WEIGHT:按重量 */ + @JsonProperty("valuation_type") + private String valuationType; + + /** 发货时间期限 {@link me.chanjar.weixin.channel.enums.SendTime} */ + @JsonProperty("send_time") + private String sendTime; + + /** 发货地址 */ + @JsonProperty("address_info") + private AddressInfo addressInfo; + + /** 运输方式,EXPRESS:快递 */ + @JsonProperty("delivery_type") + private String deliveryType; + + /** 计费方式:FREE包邮 CONDITION_FREE条件包邮 NO_FREE不包邮 */ + @JsonProperty("shipping_method") + private String shippingMethod; + + /** 条件包邮详情 */ + @JsonProperty("all_condition_free_detail") + private AllConditionFreeDetail allConditionFreeDetail; + + /** 具体计费方法,默认运费,指定地区运费等 */ + @JsonProperty("all_freight_calc_method") + private AllFreightCalcMethod allFreightCalcMethod; + + /** 创建时间戳 */ + @JsonProperty("create_time") + private Long createTime; + + /** 更新时间戳 */ + @JsonProperty("update_time") + private Long updateTime; + + /** 是否默认模板 */ + @JsonProperty("is_default") + private Boolean isDefault; + + /** 不发货区域 */ + @JsonProperty("not_send_area") + private NotSendArea notSendArea; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/freight/NotSendArea.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/freight/NotSendArea.java new file mode 100644 index 0000000000..1c480fc227 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/freight/NotSendArea.java @@ -0,0 +1,18 @@ +package me.chanjar.weixin.channel.bean.freight; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +/** + * 不发货区域 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class NotSendArea extends AddressInfoList { + + private static final long serialVersionUID = -1836467830293286560L; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/freight/TemplateAddParam.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/freight/TemplateAddParam.java new file mode 100644 index 0000000000..9c400533bf --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/freight/TemplateAddParam.java @@ -0,0 +1,26 @@ +package me.chanjar.weixin.channel.bean.freight; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 运费模板 请求参数 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@JsonInclude(Include.NON_NULL) +public class TemplateAddParam implements Serializable { + + private static final long serialVersionUID = 2602919369418149309L; + /** 起始位置 */ + @JsonProperty("freight_template") + private FreightTemplate template; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/freight/TemplateIdResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/freight/TemplateIdResponse.java new file mode 100644 index 0000000000..e895d066cb --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/freight/TemplateIdResponse.java @@ -0,0 +1,24 @@ +package me.chanjar.weixin.channel.bean.freight; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; + +/** + * 运费模板 列表 响应 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class TemplateIdResponse extends WxChannelBaseResponse { + + private static final long serialVersionUID = 5179651364165620640L; + /** 运费模板id */ + @JsonProperty("template_id") + private String templateId; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/freight/TemplateInfoResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/freight/TemplateInfoResponse.java new file mode 100644 index 0000000000..f37e3dc2d1 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/freight/TemplateInfoResponse.java @@ -0,0 +1,24 @@ +package me.chanjar.weixin.channel.bean.freight; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; + +/** + * 运费模板 响应 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class TemplateInfoResponse extends WxChannelBaseResponse { + + private static final long serialVersionUID = -8381510839783330617L; + /** 运费模板id */ + @JsonProperty("freight_template") + private FreightTemplate template; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/freight/TemplateListParam.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/freight/TemplateListParam.java new file mode 100644 index 0000000000..628d907eb1 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/freight/TemplateListParam.java @@ -0,0 +1,27 @@ +package me.chanjar.weixin.channel.bean.freight; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; +import lombok.Data; +import lombok.EqualsAndHashCode; +import me.chanjar.weixin.channel.bean.base.OffsetParam; + +/** + * 运费模板 列表 请求参数 + * + * @author Zeyes + */ +@Data +@JsonInclude(Include.NON_NULL) +@EqualsAndHashCode(callSuper = true) +public class TemplateListParam extends OffsetParam { + + private static final long serialVersionUID = -6716154891499581562L; + + public TemplateListParam() { + } + + public TemplateListParam(Integer offset, Integer limit) { + super(offset, limit); + } +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/freight/TemplateListResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/freight/TemplateListResponse.java new file mode 100644 index 0000000000..a6fcd7d3e3 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/freight/TemplateListResponse.java @@ -0,0 +1,24 @@ +package me.chanjar.weixin.channel.bean.freight; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.List; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; + +/** + * 运费模板 列表 响应 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class TemplateListResponse extends WxChannelBaseResponse { + + private static final long serialVersionUID = 5375602442595264719L; + /** 运费模板 id 列表 */ + @JsonProperty("template_id_list") + private List ids; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/fund/AccountInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/fund/AccountInfo.java new file mode 100644 index 0000000000..f6248f96ba --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/fund/AccountInfo.java @@ -0,0 +1,53 @@ +package me.chanjar.weixin.channel.bean.fund; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 账户信息 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@JsonInclude(JsonInclude.Include.NON_NULL) +public class AccountInfo implements Serializable { + + private static final long serialVersionUID = -2107134853480093451L; + /** 账户类型 {@link me.chanjar.weixin.channel.enums.AccountType} */ + @JsonProperty("bank_account_type") + private String bankAccountType; + + /** 开户银行 */ + @JsonProperty("account_bank") + private String accountBank; + + /** 开户银行省市编码 */ + @JsonProperty("bank_address_code") + private String bankAddressCode; + + /** 开户银行联行号 */ + @JsonProperty("bank_branch_id") + private String bankBranchId; + + /** 开户银行全称 */ + @JsonProperty("bank_name") + private String bankName; + + /** 银行账号 */ + @JsonProperty("account_number") + private String accountNumber; + + /** 开户银行名称前端展示值 */ + @JsonProperty("account_bank4show") + private String accountBank4show; + + /** 账户名称 */ + @JsonProperty("account_name") + private String accountName; + + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/fund/AccountInfoParam.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/fund/AccountInfoParam.java new file mode 100644 index 0000000000..ec6010bd07 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/fund/AccountInfoParam.java @@ -0,0 +1,25 @@ +package me.chanjar.weixin.channel.bean.fund; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 账户信息 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@JsonInclude(JsonInclude.Include.NON_NULL) +public class AccountInfoParam implements Serializable { + + private static final long serialVersionUID = 1689204583402779134L; + @JsonProperty("account_info") + private AccountInfo accountInfo; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/fund/AccountInfoResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/fund/AccountInfoResponse.java new file mode 100644 index 0000000000..b54a34a2e7 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/fund/AccountInfoResponse.java @@ -0,0 +1,23 @@ +package me.chanjar.weixin.channel.bean.fund; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; + +/** + * 账户信息响应 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +public class AccountInfoResponse extends WxChannelBaseResponse { + + private static final long serialVersionUID = -8316068503468969533L; + /** 账户信息 */ + @JsonProperty("account_info") + private AccountInfo accountInfo; + + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/fund/BalanceInfoResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/fund/BalanceInfoResponse.java new file mode 100644 index 0000000000..def7e86675 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/fund/BalanceInfoResponse.java @@ -0,0 +1,30 @@ +package me.chanjar.weixin.channel.bean.fund; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; + +/** + * 账户余额信息 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +public class BalanceInfoResponse extends WxChannelBaseResponse { + + private static final long serialVersionUID = 4480496860612566921L; + /** 可提现余额 */ + @JsonProperty("available_amount") + private Integer availableAmount; + + /** 待结算余额 */ + @JsonProperty("pending_amount") + private Integer pendingAmount; + + /** 二级商户号 */ + @JsonProperty("sub_mchid") + private String subMchid; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/fund/FlowListResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/fund/FlowListResponse.java new file mode 100644 index 0000000000..9306b4516a --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/fund/FlowListResponse.java @@ -0,0 +1,30 @@ +package me.chanjar.weixin.channel.bean.fund; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.List; +import lombok.Data; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; + +/** + * 流水列表响应 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +public class FlowListResponse extends WxChannelBaseResponse { + + private static final long serialVersionUID = 8017827444308973489L; + /** 流水单号列表 */ + @JsonProperty("flow_ids") + private List flowIds; + + /** 是否还有下一页 */ + @JsonProperty("has_more") + private boolean hasMore; + + /** 分页参数,深翻页时使用 */ + @JsonProperty("next_key") + private String nextKey; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/fund/FlowRelatedInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/fund/FlowRelatedInfo.java new file mode 100644 index 0000000000..4edecbb3b1 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/fund/FlowRelatedInfo.java @@ -0,0 +1,45 @@ +package me.chanjar.weixin.channel.bean.fund; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 流水关联信息 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +public class FlowRelatedInfo implements Serializable { + + private static final long serialVersionUID = 3757839018198212504L; + /** 关联类型, 1 订单, 2售后,3 提现,4 运费险 */ + @JsonProperty("related_type") + private Integer relatedType; + + /** 关联订单号 */ + @JsonProperty("order_id") + private String orderId; + + /** 关联售后单号 */ + @JsonProperty("aftersale_id") + private String afterSaleId; + + /** 关联提现单号 */ + @JsonProperty("withdraw_id") + private String withdrawId; + + /** 记账时间 */ + @JsonProperty("bookkeeping_time") + private String bookkeepingTime; + + /** 关联运费险单号 */ + @JsonProperty("insurance_id") + private String insuranceId; + + /** 关联支付单号 */ + @JsonProperty("transaction_id") + private String transactionId; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/fund/FundsFlow.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/fund/FundsFlow.java new file mode 100644 index 0000000000..9b01e820fa --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/fund/FundsFlow.java @@ -0,0 +1,51 @@ +package me.chanjar.weixin.channel.bean.fund; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import java.util.List; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 资金流水 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +public class FundsFlow implements Serializable { + + private static final long serialVersionUID = -2785498655066305510L; + /** 流水id */ + @JsonProperty("flow_id") + private String flowId; + + /** 资金类型,见 {@link me.chanjar.weixin.channel.enums.FundsType} */ + @JsonProperty("funds_type") + private Integer fundsType; + + /** 流水类型, 1 收入,2 支出 */ + @JsonProperty("flow_type") + private Integer flowType; + + /** 流水金额 */ + @JsonProperty("amount") + private Integer amount; + + /** 余额 */ + @JsonProperty("balance") + private Integer balance; + + /** 流水关联信息 */ + @JsonProperty("related_info_list") + private List relatedInfos; + + /** 记账时间 */ + @JsonProperty("bookkeeping_time") + private String bookkeepingTime; + + /** 备注 */ + @JsonProperty("remark") + private String remark; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/fund/FundsFlowResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/fund/FundsFlowResponse.java new file mode 100644 index 0000000000..7db351263f --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/fund/FundsFlowResponse.java @@ -0,0 +1,23 @@ +package me.chanjar.weixin.channel.bean.fund; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; + +/** + * 资金流水响应 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class FundsFlowResponse extends WxChannelBaseResponse { + + private static final long serialVersionUID = -1130785908352355914L; + /** 流水信息 */ + @JsonProperty("funds_flow") + private FundsFlow fundsFlow; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/fund/FundsListParam.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/fund/FundsListParam.java new file mode 100644 index 0000000000..b5312e3a2a --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/fund/FundsListParam.java @@ -0,0 +1,49 @@ +package me.chanjar.weixin.channel.bean.fund; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 资金流水参数 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +public class FundsListParam implements Serializable { + + private static final long serialVersionUID = 2998955690332382229L; + /** 页码,从1开始 */ + @JsonProperty("page") + private Integer page; + + /** 页数,不填默认为10 */ + @JsonProperty("page_size") + protected Integer pageSize; + + /** 流水产生的开始时间,uinx时间戳 */ + @JsonProperty("start_time") + private Long startTime; + + /** 流水产生的结束时间,unix时间戳 */ + @JsonProperty("end_time") + private Long endTime; + + /** 流水类型, 1 收入,2 支出 */ + @JsonProperty("flow_type") + private Integer flowType; + + /** 关联支付单号 */ + @JsonProperty("transaction_id") + private String transactionId; + + /** + * 分页参数,翻页时写入上一页返回的next_key(page为上一页加一, 并且page_size与上一页相同的时候才生效),page * page_size >= 10000时必填 + */ + @JsonProperty("next_key") + private String nextKey; + + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/fund/WithdrawDetailResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/fund/WithdrawDetailResponse.java new file mode 100644 index 0000000000..a1e726fb51 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/fund/WithdrawDetailResponse.java @@ -0,0 +1,55 @@ +package me.chanjar.weixin.channel.bean.fund; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; + +/** + * 提现详情响应 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class WithdrawDetailResponse extends WxChannelBaseResponse { + + private static final long serialVersionUID = 1473346677401168323L; + /** 金额 */ + @JsonProperty("amount") + private Integer amount; + + /** 创建时间 */ + @JsonProperty("create_time") + private Long createTime; + + /** 更新时间 */ + @JsonProperty("update_time") + private Long updateTime; + + /** 失败原因 */ + @JsonProperty("reason") + private String reason; + + /** 备注 */ + @JsonProperty("remark") + private String remark; + + /** 银行附言 */ + @JsonProperty("bank_memo") + private String bankMemo; + + /** 银行名称 */ + @JsonProperty("bank_name") + private String bankName; + + /** 银行账户 */ + @JsonProperty("bank_num") + private String bankNum; + + /** 提现状态 {@link me.chanjar.weixin.channel.enums.WithdrawStatus} */ + @JsonProperty("status") + private String status; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/fund/WithdrawListParam.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/fund/WithdrawListParam.java new file mode 100644 index 0000000000..a44b68567d --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/fund/WithdrawListParam.java @@ -0,0 +1,36 @@ +package me.chanjar.weixin.channel.bean.fund; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 提现列表参数 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class WithdrawListParam implements Serializable { + + private static final long serialVersionUID = -672422656564313999L; + /** 页码,从1开始 */ + @JsonProperty("page_num") + private Integer pageNum; + + /** 页数 */ + @JsonProperty("page_size") + private Integer pageSize; + + /** 开始时间 */ + @JsonProperty("start_time") + private Long startTime; + + /** 结束时间 */ + @JsonProperty("end_time") + private Long endTime; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/fund/WithdrawListResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/fund/WithdrawListResponse.java new file mode 100644 index 0000000000..b1dabc2a4b --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/fund/WithdrawListResponse.java @@ -0,0 +1,23 @@ +package me.chanjar.weixin.channel.bean.fund; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.List; +import lombok.Data; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; + +/** + * 提现列表响应 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +public class WithdrawListResponse extends WxChannelBaseResponse { + + private static final long serialVersionUID = -7950467108750325235L; + /** 提现单号列表 */ + @JsonProperty("withdraw_ids") + private List withdrawIds; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/fund/WithdrawSubmitParam.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/fund/WithdrawSubmitParam.java new file mode 100644 index 0000000000..65b8cdd12c --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/fund/WithdrawSubmitParam.java @@ -0,0 +1,32 @@ +package me.chanjar.weixin.channel.bean.fund; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 提现提交参数 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class WithdrawSubmitParam implements Serializable { + + private static final long serialVersionUID = 5801338663530567830L; + /** 提现金额(单位:分) */ + @JsonProperty("amount") + private Integer amount; + + /** 提现备注 */ + @JsonProperty("remark") + private String remark; + + /** 银行附言 */ + @JsonProperty("bank_memo") + private String bankMemo; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/fund/WithdrawSubmitResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/fund/WithdrawSubmitResponse.java new file mode 100644 index 0000000000..0002b158d2 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/fund/WithdrawSubmitResponse.java @@ -0,0 +1,23 @@ +package me.chanjar.weixin.channel.bean.fund; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; + +/** + * 提现提交响应 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class WithdrawSubmitResponse extends WxChannelBaseResponse { + + private static final long serialVersionUID = -8269579250564427758L; + /** 二维码ticket,可用于获取二维码和查询二维码状态 */ + @JsonProperty("qrcode_ticket") + private String qrcodeTicket; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/fund/bank/BankCityInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/fund/bank/BankCityInfo.java new file mode 100644 index 0000000000..04a69a8e87 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/fund/bank/BankCityInfo.java @@ -0,0 +1,29 @@ +package me.chanjar.weixin.channel.bean.fund.bank; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 银行城市信息 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +public class BankCityInfo implements Serializable { + + private static final long serialVersionUID = 374087891799491196L; + /** 城市名称 */ + @JsonProperty("city_name") + private String cityName; + + /** 城市编号 */ + @JsonProperty("city_code") + private Integer cityCode; + + /** 开户银行省市编码 */ + @JsonProperty("bank_address_code") + private String bankAddressCode; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/fund/bank/BankCityResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/fund/bank/BankCityResponse.java new file mode 100644 index 0000000000..5cb148c79b --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/fund/bank/BankCityResponse.java @@ -0,0 +1,28 @@ +package me.chanjar.weixin.channel.bean.fund.bank; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.List; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; + +/** + * 银行城市信息响应 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class BankCityResponse extends WxChannelBaseResponse { + + private static final long serialVersionUID = -6212360101083304631L; + /** 银行城市信息列表 */ + @JsonProperty("data") + private List data; + + /** 总数 */ + @JsonProperty("total_count") + private Integer totalCount; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/fund/bank/BankInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/fund/bank/BankInfo.java new file mode 100644 index 0000000000..1bb58badb4 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/fund/bank/BankInfo.java @@ -0,0 +1,46 @@ +package me.chanjar.weixin.channel.bean.fund.bank; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 银行信息 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +public class BankInfo implements Serializable { + + private static final long serialVersionUID = -4837989875996346711L; + /** 开户银行 */ + @JsonProperty("account_bank") + private String accountBank; + + /** 银行编码 */ + @JsonProperty("bank_code") + private String bankCode; + + /** 银行联号 */ + @JsonProperty("bank_id") + private String bankId; + + /** 银行名称(不包括支行) */ + @JsonProperty("bank_name") + private String bankName; + + /** 银行类型(1.对公,2.对私) */ + @JsonProperty("bank_type") + private Integer bankType; + + /** 是否需要填写支行信息 */ + @JsonProperty("need_branch") + private Boolean needBranch; + + /** 支行联号 */ + @JsonProperty("branch_id") + private String branchId; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/fund/bank/BankInfoResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/fund/bank/BankInfoResponse.java new file mode 100644 index 0000000000..499d9fcbb5 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/fund/bank/BankInfoResponse.java @@ -0,0 +1,28 @@ +package me.chanjar.weixin.channel.bean.fund.bank; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.List; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; + +/** + * 银行信息响应 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class BankInfoResponse extends WxChannelBaseResponse { + + private static final long serialVersionUID = 8583893898929290526L; + /** 银行信息列表 */ + @JsonProperty("data") + private List data; + + /** 总数 */ + @JsonProperty("total_count") + private Integer totalCount; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/fund/bank/BankListResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/fund/bank/BankListResponse.java new file mode 100644 index 0000000000..9517859c42 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/fund/bank/BankListResponse.java @@ -0,0 +1,24 @@ +package me.chanjar.weixin.channel.bean.fund.bank; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.List; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; + +/** + * 银行信息响应 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class BankListResponse extends WxChannelBaseResponse { + + private static final long serialVersionUID = 7912035853286944260L; + /** 银行信息列表 */ + @JsonProperty("data") + private List data; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/fund/bank/BankProvinceInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/fund/bank/BankProvinceInfo.java new file mode 100644 index 0000000000..955a25e8ad --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/fund/bank/BankProvinceInfo.java @@ -0,0 +1,25 @@ +package me.chanjar.weixin.channel.bean.fund.bank; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 银行省份信息 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +public class BankProvinceInfo implements Serializable { + + private static final long serialVersionUID = -3409931656361300144L; + /** 省份名称 */ + @JsonProperty("province_name") + private String provinceName; + + /** 省份编码 */ + @JsonProperty("province_code") + private Integer provinceCode; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/fund/bank/BankProvinceResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/fund/bank/BankProvinceResponse.java new file mode 100644 index 0000000000..f509d24304 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/fund/bank/BankProvinceResponse.java @@ -0,0 +1,26 @@ +package me.chanjar.weixin.channel.bean.fund.bank; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.List; +import lombok.Data; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; + +/** + * 银行省份信息响应 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +public class BankProvinceResponse extends WxChannelBaseResponse { + + private static final long serialVersionUID = -6187805847136359892L; + /** 银行省份信息列表 */ + @JsonProperty("data") + private List data; + + /** 总数 */ + @JsonProperty("total_count") + private Integer totalCount; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/fund/bank/BankSearchParam.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/fund/bank/BankSearchParam.java new file mode 100644 index 0000000000..abc9c1ec77 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/fund/bank/BankSearchParam.java @@ -0,0 +1,37 @@ +package me.chanjar.weixin.channel.bean.fund.bank; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 银行查询参数 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@JsonInclude(JsonInclude.Include.NON_NULL) +public class BankSearchParam implements Serializable { + + private static final long serialVersionUID = 6070269209439188188L; + /** 偏移量 */ + @JsonProperty("offset") + private Integer offset; + + /** 每页数据大小 */ + @JsonProperty("limit") + private Integer limit; + + /** 银行关键字 */ + @JsonProperty("key_words") + private String keyWords; + + /** 银行类型(1:对私银行,2:对公银行; 默认对公) */ + @JsonProperty("bank_type") + private Integer bankType; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/fund/bank/BranchInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/fund/bank/BranchInfo.java new file mode 100644 index 0000000000..c4cec9bc76 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/fund/bank/BranchInfo.java @@ -0,0 +1,25 @@ +package me.chanjar.weixin.channel.bean.fund.bank; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 分店信息 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +public class BranchInfo implements Serializable { + + private static final long serialVersionUID = -2744729367131146892L; + /** 支行联号 */ + @JsonProperty("branch_id") + private Integer branchId; + + /** 银行全称(含支行) */ + @JsonProperty("branch_name") + private String branchName; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/fund/bank/BranchInfoResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/fund/bank/BranchInfoResponse.java new file mode 100644 index 0000000000..c7cfda4646 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/fund/bank/BranchInfoResponse.java @@ -0,0 +1,49 @@ +package me.chanjar.weixin.channel.bean.fund.bank; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.List; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; + +/** + * 支行信息响应 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class BranchInfoResponse extends WxChannelBaseResponse { + + private static final long serialVersionUID = -1419832502854175767L; + /** 总数 */ + @JsonProperty("total_count") + private Integer totalCount; + + /** 当前分页数量 */ + @JsonProperty("count") + private Integer count; + + /** 银行名称 */ + @JsonProperty("account_bank") + private String accountBank; + + /** 银行编码 */ + @JsonProperty("account_bank_code") + private String accountBankCode; + + /** 银行别名 */ + @JsonProperty("bank_alias") + private String bankAlias; + + /** 银行别名编码 */ + @JsonProperty("bank_alias_code") + private String bankAliasCode; + + /** 支行信息列表 */ + @JsonProperty("data") + private List data; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/fund/bank/BranchSearchParam.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/fund/bank/BranchSearchParam.java new file mode 100644 index 0000000000..47527efe1e --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/fund/bank/BranchSearchParam.java @@ -0,0 +1,35 @@ +package me.chanjar.weixin.channel.bean.fund.bank; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 银行支行信息 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class BranchSearchParam implements Serializable { + + private static final long serialVersionUID = -8800316690160248833L; + /** 银行编码,通过查询银行信息或者搜索银行信息获取 */ + @JsonProperty("bank_code") + private String bankCode; + + /** 城市编号,通过查询城市列表获取 */ + @JsonProperty("city_code") + private String cityCode; + + /** 偏移量 */ + @JsonProperty("offset") + private Integer offset; + + /** 限制个数 */ + @JsonProperty("limit") + private Integer limit; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/fund/qrcode/QrCheckResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/fund/qrcode/QrCheckResponse.java new file mode 100644 index 0000000000..e1a52ab9a3 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/fund/qrcode/QrCheckResponse.java @@ -0,0 +1,36 @@ +package me.chanjar.weixin.channel.bean.fund.qrcode; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; + +/** + * 二维码校验响应 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class QrCheckResponse extends WxChannelBaseResponse { + + private static final long serialVersionUID = -3860756719827268969L; + /** 扫码状态 {@link me.chanjar.weixin.channel.enums.QrCheckStatus} */ + @JsonProperty("status") + private Integer status; + + /** 业务返回错误码 */ + @JsonProperty("self_check_err_code") + private Integer selfCheckErrCode; + + /** 业务返回错误信息 */ + @JsonProperty("self_check_err_msg") + private String selfCheckErrMsg; + + /** 扫码者身份 0非管理员 1管理员 2次管理员 */ + @JsonProperty("scan_user_type") + private Integer scanUserType; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/fund/qrcode/QrCodeResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/fund/qrcode/QrCodeResponse.java new file mode 100644 index 0000000000..d6c015c0cd --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/fund/qrcode/QrCodeResponse.java @@ -0,0 +1,24 @@ +package me.chanjar.weixin.channel.bean.fund.qrcode; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; + +/** + * 二维码响应 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class QrCodeResponse extends WxChannelBaseResponse { + + private static final long serialVersionUID = 4521008628337929496L; + /** 二维码(base64编码二进制,需要base64解码) */ + @JsonProperty("qrcode_buf") + private String qrcodeBuf; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/home/background/BackgroundApplyResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/home/background/BackgroundApplyResponse.java new file mode 100644 index 0000000000..b0d8769874 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/home/background/BackgroundApplyResponse.java @@ -0,0 +1,25 @@ +package me.chanjar.weixin.channel.bean.home.background; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; + +/** + * 提交背景图申请 结果 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class BackgroundApplyResponse extends WxChannelBaseResponse { + + private static final long serialVersionUID = -5627456997199822109L; + + /** 申请编号 */ + @JsonProperty("apply_id") + private Integer applyId; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/home/background/BackgroundApplyResult.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/home/background/BackgroundApplyResult.java new file mode 100644 index 0000000000..45ca4ac1dd --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/home/background/BackgroundApplyResult.java @@ -0,0 +1,35 @@ +package me.chanjar.weixin.channel.bean.home.background; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 背景图审核信息 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +public class BackgroundApplyResult implements Serializable { + + private static final long serialVersionUID = 3154900058221168732L; + + /** 申请编号 */ + @JsonProperty("apply_id") + private Integer applyId; + + /** 申请状态。1审核中 2审核驳回 */ + @JsonProperty("state") + private Integer state; + + /** 审核结果描述。state为审核驳回时有值。 */ + @JsonProperty("audit_desc") + private String auditDesc; + + /** 图片url */ + @JsonProperty("img_url") + private String imgUrl; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/home/background/BackgroundGetResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/home/background/BackgroundGetResponse.java new file mode 100644 index 0000000000..a0fbf33a80 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/home/background/BackgroundGetResponse.java @@ -0,0 +1,28 @@ +package me.chanjar.weixin.channel.bean.home.background; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; + +/** + * 背景图返回结果 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class BackgroundGetResponse extends WxChannelBaseResponse { + + private static final long serialVersionUID = -9158761351220981959L; + + /** 当前生效的背景图片url */ + @JsonProperty("img_url") + private String imgUrl; + + /** 背景图审核信息 */ + @JsonProperty("apply") + private BackgroundApplyResult apply; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/home/banner/BannerApplyDetail.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/home/banner/BannerApplyDetail.java new file mode 100644 index 0000000000..e9e58057fd --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/home/banner/BannerApplyDetail.java @@ -0,0 +1,33 @@ +package me.chanjar.weixin.channel.bean.home.banner; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 精选展示位申请详情 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@JsonInclude(Include.NON_NULL) +public class BannerApplyDetail implements Serializable { + + private static final long serialVersionUID = -4622897527243343862L; + + /** 审核状态。 1-审核中;2-审核驳回 */ + @JsonProperty("audit_state") + private Integer auditState; + + /** 审核结果描述。audit_state为驳回时有值。 */ + @JsonProperty("audit_desc") + private String auditDesc; + + /** 精选展示位申请明细 */ + @JsonProperty("banner") + private BannerItem banner; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/home/banner/BannerApplyInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/home/banner/BannerApplyInfo.java new file mode 100644 index 0000000000..651c5c76fe --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/home/banner/BannerApplyInfo.java @@ -0,0 +1,35 @@ +package me.chanjar.weixin.channel.bean.home.banner; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import java.util.List; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 精选展示位申请信息 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +public class BannerApplyInfo implements Serializable { + + private static final long serialVersionUID = 72190625450999960L; + + /** 申请编号 */ + @JsonProperty("apply_id") + private Integer applyId; + + /** 申请状态 1-审核中;2-审核驳回 */ + @JsonProperty("state") + private Integer state; + + /** 展示位的展示样式 1-小图模式;2-大图模式 */ + @JsonProperty("scale") + private Integer scale; + + /** 精选展示位申请明细 */ + @JsonProperty("banner") + private List banner; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/home/banner/BannerApplyParam.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/home/banner/BannerApplyParam.java new file mode 100644 index 0000000000..04c7abc2a7 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/home/banner/BannerApplyParam.java @@ -0,0 +1,28 @@ +package me.chanjar.weixin.channel.bean.home.banner; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 精选展示位申请参数 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@JsonInclude(Include.NON_NULL) +public class BannerApplyParam implements Serializable { + + private static final long serialVersionUID = 9083668032979490150L; + + /** banner */ + @JsonProperty("banner") + private BannerInfo banner; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/home/banner/BannerApplyResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/home/banner/BannerApplyResponse.java new file mode 100644 index 0000000000..f83f119d13 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/home/banner/BannerApplyResponse.java @@ -0,0 +1,25 @@ +package me.chanjar.weixin.channel.bean.home.banner; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; + +/** + * 提交精选展位申请 结果 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class BannerApplyResponse extends WxChannelBaseResponse { + + private static final long serialVersionUID = -2194587734444499201L; + + /** 申请编号 */ + @JsonProperty("apply_id") + private Integer applyId; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/home/banner/BannerGetResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/home/banner/BannerGetResponse.java new file mode 100644 index 0000000000..1c6a920636 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/home/banner/BannerGetResponse.java @@ -0,0 +1,28 @@ +package me.chanjar.weixin.channel.bean.home.banner; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; + +/** + * 精选展位返回结果 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class BannerGetResponse extends WxChannelBaseResponse { + + private static final long serialVersionUID = -1563254921362215934L; + + /** 当前生效的展示位 */ + @JsonProperty("banner") + private BannerInfo banner; + + /** 最近一次流程中的申请。不返回已生效或已撤销的申请 */ + @JsonProperty("apply") + private BannerApplyInfo apply; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/home/banner/BannerInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/home/banner/BannerInfo.java new file mode 100644 index 0000000000..24b501a97d --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/home/banner/BannerInfo.java @@ -0,0 +1,31 @@ +package me.chanjar.weixin.channel.bean.home.banner; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import java.util.List; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 精选展示位 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@JsonInclude(Include.NON_NULL) +public class BannerInfo implements Serializable { + + private static final long serialVersionUID = -2003175482038217418L; + + /** 展示位的展示样式 1-小图模式;2-大图模式 */ + @JsonProperty("scale") + private Integer scale; + + /** 精选展示位明细 */ + @JsonProperty("banner") + private List banner; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/home/banner/BannerItem.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/home/banner/BannerItem.java new file mode 100644 index 0000000000..9a5cad9649 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/home/banner/BannerItem.java @@ -0,0 +1,41 @@ +package me.chanjar.weixin.channel.bean.home.banner; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 精选展示位明细 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@JsonInclude(Include.NON_NULL) +public class BannerItem implements Serializable { + + private static final long serialVersionUID = 6982412458700854481L; + + /** 展示位类型 1-商品 3-视频号 4-公众号 {@link me.chanjar.weixin.channel.enums.BannerType} */ + @JsonProperty("type") + private Integer type; + + /** 展示位信息 */ + @JsonProperty("banner") + private BannerItemDetail banner; + + /** 商品 */ + @JsonProperty("product") + private BannerItemProduct product; + + /** 视频号 */ + @JsonProperty("finder") + private BannerItemFinder finder; + + /** 公众号 */ + @JsonProperty("official_account") + private BannerItemOfficialAccount officialAccount; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/home/banner/BannerItemDetail.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/home/banner/BannerItemDetail.java new file mode 100644 index 0000000000..b5cfb4a38c --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/home/banner/BannerItemDetail.java @@ -0,0 +1,33 @@ +package me.chanjar.weixin.channel.bean.home.banner; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 精选展示位明细中的明细 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@JsonInclude(Include.NON_NULL) +public class BannerItemDetail implements Serializable { + + private static final long serialVersionUID = 5975434996207526173L; + + /** 图片url */ + @JsonProperty("img_url") + private String imgUrl; + + /** 标题 */ + @JsonProperty("title") + private String title; + + /** 描述 */ + @JsonProperty("description") + private String description; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/home/banner/BannerItemFinder.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/home/banner/BannerItemFinder.java new file mode 100644 index 0000000000..735a2038da --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/home/banner/BannerItemFinder.java @@ -0,0 +1,29 @@ +package me.chanjar.weixin.channel.bean.home.banner; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 精选展示位明细中的视频号数据 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@JsonInclude(Include.NON_NULL) +public class BannerItemFinder implements Serializable { + + private static final long serialVersionUID = -7397790079913284012L; + + /** 视频号ID */ + @JsonProperty("finder_user_name") + private String finderUserName; + + /** 视频号视频的唯一标识 */ + @JsonProperty("feed_id") + private String feedId; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/home/banner/BannerItemOfficialAccount.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/home/banner/BannerItemOfficialAccount.java new file mode 100644 index 0000000000..0488829642 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/home/banner/BannerItemOfficialAccount.java @@ -0,0 +1,25 @@ +package me.chanjar.weixin.channel.bean.home.banner; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 精选展示位明细中的公众号数据 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@JsonInclude(Include.NON_NULL) +public class BannerItemOfficialAccount implements Serializable { + + private static final long serialVersionUID = -5596947592282082891L; + + /** 公众号文章url */ + @JsonProperty("url") + private String url; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/home/banner/BannerItemProduct.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/home/banner/BannerItemProduct.java new file mode 100644 index 0000000000..87a51823f0 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/home/banner/BannerItemProduct.java @@ -0,0 +1,25 @@ +package me.chanjar.weixin.channel.bean.home.banner; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 精选展示位明细中的商品 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@JsonInclude(Include.NON_NULL) +public class BannerItemProduct implements Serializable { + + private static final long serialVersionUID = 8034487065591522594L; + + /** 商品id */ + @JsonProperty("product_id") + private Long productId; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/home/tree/CatTreeNode.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/home/tree/CatTreeNode.java new file mode 100644 index 0000000000..c545b8637f --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/home/tree/CatTreeNode.java @@ -0,0 +1,32 @@ +package me.chanjar.weixin.channel.bean.home.tree; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 主页分类信息 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class CatTreeNode implements Serializable { + + private static final long serialVersionUID = 3154219180098003510L; + + /** 分类id */ + @JsonProperty("id") + private Integer id; + + /** 分类名字 */ + @JsonProperty("name") + private String name; + + /** 是否在用户端展示该分类。1为是,0为否 */ + @JsonProperty("is_displayed") + private Boolean displayed; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/home/tree/LevelTreeInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/home/tree/LevelTreeInfo.java new file mode 100644 index 0000000000..104588202e --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/home/tree/LevelTreeInfo.java @@ -0,0 +1,24 @@ +package me.chanjar.weixin.channel.bean.home.tree; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import java.util.List; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 分类信息 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class LevelTreeInfo implements Serializable { + + /** 一级分类 */ + @JsonProperty("level_1") + private List level1; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/home/tree/OneLevelTreeNode.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/home/tree/OneLevelTreeNode.java new file mode 100644 index 0000000000..76499c86e7 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/home/tree/OneLevelTreeNode.java @@ -0,0 +1,25 @@ +package me.chanjar.weixin.channel.bean.home.tree; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.List; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +/** + * 一级分类 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class OneLevelTreeNode extends CatTreeNode { + + /** 二级分类 */ + @JsonProperty("level_2") + private List level2; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/home/tree/TreeAuditResult.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/home/tree/TreeAuditResult.java new file mode 100644 index 0000000000..b85dda46dd --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/home/tree/TreeAuditResult.java @@ -0,0 +1,27 @@ +package me.chanjar.weixin.channel.bean.home.tree; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import java.util.List; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 展示在店铺主页的商品分类 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +public class TreeAuditResult implements Serializable { + + private static final long serialVersionUID = 8142657614529852121L; + + /** 版本号。设置分类树的接口会用到 */ + @JsonProperty("version") + private Integer version; + + /** 展示在店铺主页的商品分类 */ + @JsonProperty("audit_results") + private List auditResults; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/home/tree/TreeAuditResultDetail.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/home/tree/TreeAuditResultDetail.java new file mode 100644 index 0000000000..92df865061 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/home/tree/TreeAuditResultDetail.java @@ -0,0 +1,27 @@ +package me.chanjar.weixin.channel.bean.home.tree; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 分类审核结果 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +public class TreeAuditResultDetail implements Serializable { + + private static final long serialVersionUID = -6085892397971684732L; + + /** 该分类ID的审核结果 */ + @JsonProperty("level_id") + private Integer level_id; + + /** 审核结果枚举。1:不通过;2:通过 */ + @JsonProperty("result_code") + private Integer result_code; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/home/tree/TreeProductEditInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/home/tree/TreeProductEditInfo.java new file mode 100644 index 0000000000..d7dd831c3d --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/home/tree/TreeProductEditInfo.java @@ -0,0 +1,33 @@ +package me.chanjar.weixin.channel.bean.home.tree; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import java.util.List; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 添加/删除分类关联的商品 参数 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class TreeProductEditInfo implements Serializable { + + private static final long serialVersionUID = -5596947592282082891L; + + /** 一级分类id */ + @JsonProperty("level_1_id") + private Integer level1Id; + + /** 二级分类id */ + @JsonProperty("level_2_id") + private Integer level2Id; + + /** 商品id列表 */ + @JsonProperty("product_ids") + private List productIds; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/home/tree/TreeProductEditParam.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/home/tree/TreeProductEditParam.java new file mode 100644 index 0000000000..fb42162ca6 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/home/tree/TreeProductEditParam.java @@ -0,0 +1,25 @@ +package me.chanjar.weixin.channel.bean.home.tree; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 添加/删除分类关联的商品 参数 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class TreeProductEditParam implements Serializable { + + private static final long serialVersionUID = -4906016235749892703L; + + /** 参数 */ + @JsonProperty("req") + private TreeProductEditInfo req; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/home/tree/TreeProductListInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/home/tree/TreeProductListInfo.java new file mode 100644 index 0000000000..a37e784d14 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/home/tree/TreeProductListInfo.java @@ -0,0 +1,36 @@ +package me.chanjar.weixin.channel.bean.home.tree; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 查询分类关联的商品 参数 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class TreeProductListInfo implements Serializable { + + private static final long serialVersionUID = 2774682583380930076L; + + /** 一级分类id */ + @JsonProperty("level_1_id") + private Integer level1Id; + + /** 二级分类id */ + @JsonProperty("level_2_id") + private Integer level2Id; + + /** 分页大小 */ + @JsonProperty("page_size") + private Integer pageSize; + + /** 从头拉取填空。翻页拉取的话填resp返回的值 */ + @JsonProperty("page_context") + private String pageContext; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/home/tree/TreeProductListParam.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/home/tree/TreeProductListParam.java new file mode 100644 index 0000000000..7bb6a700e2 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/home/tree/TreeProductListParam.java @@ -0,0 +1,24 @@ +package me.chanjar.weixin.channel.bean.home.tree; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 查询分类关联的商品 参数 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class TreeProductListParam implements Serializable { + + private static final long serialVersionUID = -8444106841479328711L; + + /** 参数 */ + @JsonProperty("req") + private TreeProductListInfo req; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/home/tree/TreeProductListResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/home/tree/TreeProductListResponse.java new file mode 100644 index 0000000000..ed0081d70c --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/home/tree/TreeProductListResponse.java @@ -0,0 +1,24 @@ +package me.chanjar.weixin.channel.bean.home.tree; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; + +/** + * 资金流水响应 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class TreeProductListResponse extends WxChannelBaseResponse { + + private static final long serialVersionUID = 4566848209585635054L; + + /** 结果 */ + @JsonProperty("resp") + private TreeProductListResult resp; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/home/tree/TreeProductListResult.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/home/tree/TreeProductListResult.java new file mode 100644 index 0000000000..6e0fdfea6c --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/home/tree/TreeProductListResult.java @@ -0,0 +1,31 @@ +package me.chanjar.weixin.channel.bean.home.tree; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import java.util.List; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 资金流水响应 结果 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +public class TreeProductListResult implements Serializable { + + private static final long serialVersionUID = 4566848209585635054L; + + /** 关联的商品ID。如果返回为空,返回翻页到底了 */ + @JsonProperty("product_ids") + private List productIds; + + /** 总条数 */ + @JsonProperty("total_count") + private Integer totalCount; + + /** 拉取下一页的话,需要把这个值填到req的page_context里面 */ + @JsonProperty("page_context") + private String pageContext; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/home/tree/TreeShowGetResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/home/tree/TreeShowGetResponse.java new file mode 100644 index 0000000000..f3784c48fb --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/home/tree/TreeShowGetResponse.java @@ -0,0 +1,20 @@ +package me.chanjar.weixin.channel.bean.home.tree; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; + +/** + * @author Zeyes + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class TreeShowGetResponse extends WxChannelBaseResponse { + + /** resp */ + @JsonProperty("resp") + private TreeShowInfo resp; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/home/tree/TreeShowInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/home/tree/TreeShowInfo.java new file mode 100644 index 0000000000..09da2c5b0c --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/home/tree/TreeShowInfo.java @@ -0,0 +1,45 @@ +package me.chanjar.weixin.channel.bean.home.tree; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import java.util.List; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 分类展示信息 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class TreeShowInfo implements Serializable { + + /** 分类树 */ + @JsonProperty("tree") + private LevelTreeInfo tree; + + /** 版本号。通过获取商品分类树或者本接口得到 */ + @JsonProperty("version") + private Integer version; + + /** 表示有哪一些分类ID清空关联得商品,如果不清空,那么分类ID和商品得关联关系会一直存在。如果是一级分类,就填"1"。如果是二级分类,就填"1.2"。 */ + @JsonProperty("classification_id_deleted") + private List classificationIdDeleted; + + // 一些自定义的方法 + + /** + * 创建Tree节点 + * + * @return Tree节点 + */ + protected LevelTreeInfo createTree() { + if (tree == null) { + tree = new LevelTreeInfo(); + } + return tree; + } +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/home/tree/TreeShowParam.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/home/tree/TreeShowParam.java new file mode 100644 index 0000000000..7277c528f4 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/home/tree/TreeShowParam.java @@ -0,0 +1,24 @@ +package me.chanjar.weixin.channel.bean.home.tree; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 设置展示在店铺主页的商品分类 参数 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class TreeShowParam implements Serializable { + + private static final long serialVersionUID = -1577647561992899360L; + + /** 分类信息 */ + @JsonProperty("req") + private TreeShowInfo req; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/home/tree/TreeShowSetResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/home/tree/TreeShowSetResponse.java new file mode 100644 index 0000000000..ad65332644 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/home/tree/TreeShowSetResponse.java @@ -0,0 +1,20 @@ +package me.chanjar.weixin.channel.bean.home.tree; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; + +/** + * @author Zeyes + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class TreeShowSetResponse extends WxChannelBaseResponse { + + /** resp */ + @JsonProperty("resp") + private TreeAuditResult resp; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/home/window/WindowProductIndexParam.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/home/window/WindowProductIndexParam.java new file mode 100644 index 0000000000..fcc16bd0f6 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/home/window/WindowProductIndexParam.java @@ -0,0 +1,28 @@ +package me.chanjar.weixin.channel.bean.home.window; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 主页商品排序参数 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class WindowProductIndexParam implements Serializable { + + private static final long serialVersionUID = 1370480140179330908L; + + /** 商品id */ + @JsonProperty("product_id") + private String productId; + + /** 商品重新排序后的新序号,最大移动步长为500(即新序号与当前序号的距离小于500) */ + @JsonProperty("index_num") + private Integer indexNum; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/home/window/WindowProductListParam.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/home/window/WindowProductListParam.java new file mode 100644 index 0000000000..9245df9887 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/home/window/WindowProductListParam.java @@ -0,0 +1,26 @@ +package me.chanjar.weixin.channel.bean.home.window; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 获取主页展示商品列表 参数 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class WindowProductListParam implements Serializable { + + /** 每页数量(默认10,不超过30) */ + @JsonProperty("page_size") + private Integer pageSize; + + /** 由上次请求返回,记录翻页的上下文。传入时会从上次返回的结果往后翻一页,不传默认获取第一页数据。 */ + @JsonProperty("next_key") + private String nextKey; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/home/window/WindowProductSetting.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/home/window/WindowProductSetting.java new file mode 100644 index 0000000000..725470b912 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/home/window/WindowProductSetting.java @@ -0,0 +1,34 @@ +package me.chanjar.weixin.channel.bean.home.window; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 主页商品配置 返回结果 / 设置请求参数 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@JsonInclude(JsonInclude.Include.NON_NULL) +public class WindowProductSetting implements Serializable { + + private static final long serialVersionUID = -5931781905709862287L; + + /** 商品id */ + @JsonProperty("product_id") + private String productId; + + /** 是否隐藏,设置为隐藏的商品只在首页不可见,并不代表下架。 */ + @JsonProperty("is_set_hide") + private Integer setHide; + + /** 是否置顶 */ + @JsonProperty("is_set_top") + private Integer setTop; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/home/window/WindowProductSettingResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/home/window/WindowProductSettingResponse.java new file mode 100644 index 0000000000..495910e37d --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/home/window/WindowProductSettingResponse.java @@ -0,0 +1,33 @@ +package me.chanjar.weixin.channel.bean.home.window; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.List; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; + +/** + * 主页商品配置列表 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class WindowProductSettingResponse extends WxChannelBaseResponse { + + private static final long serialVersionUID = 1L; + + /** 商品信息 */ + @JsonProperty("products") + private List products; + + /** 本次翻页的上下文,用于请求下一页 */ + @JsonProperty("next_key") + private String nextKey; + + /** 商品总数 */ + @JsonProperty("total_num") + private Integer totalNum; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/image/ChannelImageInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/image/ChannelImageInfo.java new file mode 100644 index 0000000000..3e12c7e830 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/image/ChannelImageInfo.java @@ -0,0 +1,30 @@ +package me.chanjar.weixin.channel.bean.image; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 微信图片信息 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +public class ChannelImageInfo implements Serializable { + + private static final long serialVersionUID = 8883519290965944530L; + + /** 开放平台media_id */ + @JsonProperty("media_id") + private String mediaId; + + /** 图片链接,有访问频率限制 */ + @JsonProperty("img_url") + private String url; + + /** 微信支付media_id */ + @JsonProperty("pay_media_id") + private String payMediaId; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/image/ChannelImageResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/image/ChannelImageResponse.java new file mode 100644 index 0000000000..903af375af --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/image/ChannelImageResponse.java @@ -0,0 +1,32 @@ +package me.chanjar.weixin.channel.bean.image; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import java.io.File; +import lombok.Data; +import lombok.EqualsAndHashCode; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; + +/** + * @author Zeyes + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class ChannelImageResponse extends WxChannelBaseResponse { + + private static final long serialVersionUID = -4163511427507976489L; + + @JsonIgnore + private File file; + + private String contentType; + + public ChannelImageResponse() { + } + + public ChannelImageResponse(File file, String contentType) { + this.errCode = SUCCESS_CODE; + this.errMsg = "ok"; + this.file = file; + this.contentType = contentType; + } +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/image/QualificationFileId.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/image/QualificationFileId.java new file mode 100644 index 0000000000..905720a8dc --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/image/QualificationFileId.java @@ -0,0 +1,24 @@ +package me.chanjar.weixin.channel.bean.image; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 资质文件id + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class QualificationFileId implements Serializable { + + private static final long serialVersionUID = -546135264746778249L; + + /** 文件id */ + @JsonProperty("file_id") + private String id; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/image/QualificationFileResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/image/QualificationFileResponse.java new file mode 100644 index 0000000000..5a4332885c --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/image/QualificationFileResponse.java @@ -0,0 +1,24 @@ +package me.chanjar.weixin.channel.bean.image; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; + +/** + * 资质文件id响应 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class QualificationFileResponse extends WxChannelBaseResponse { + + private static final long serialVersionUID = 5172377567441096813L; + + /** 文件数据 */ + @JsonProperty("data") + private QualificationFileId data; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/image/UploadImageResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/image/UploadImageResponse.java new file mode 100644 index 0000000000..f1625bd3c4 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/image/UploadImageResponse.java @@ -0,0 +1,24 @@ +package me.chanjar.weixin.channel.bean.image; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; + +/** + * 微信图片信息响应 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class UploadImageResponse extends WxChannelBaseResponse { + + private static final long serialVersionUID = -609315696774437877L; + + /** 图片信息 */ + @JsonProperty("pic_file") + private ChannelImageInfo imgInfo; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/lead/component/request/GetFinderLiveDataListRequest.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/lead/component/request/GetFinderLiveDataListRequest.java new file mode 100644 index 0000000000..3ea61547e9 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/lead/component/request/GetFinderLiveDataListRequest.java @@ -0,0 +1,39 @@ +package me.chanjar.weixin.channel.bean.lead.component.request; + + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 留资直播间数据详情 + * @author imyzt + * @date 2024/01/27 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@JsonInclude(JsonInclude.Include.NON_NULL) +public class GetFinderLiveDataListRequest { + + /** + * 开始时间 + */ + @JsonProperty("start_time") + private Long startTime; + + /** + * 结束时间 + */ + @JsonProperty("end_time") + private Long endTime; + + /** + * 顺序翻页,传入上次请求返回的last_buffer, 会从上次返回的结果往后翻一页 + */ + @JsonProperty("last_buffer") + private String lastBuffer; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/lead/component/request/GetFinderLiveLeadsDataRequest.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/lead/component/request/GetFinderLiveLeadsDataRequest.java new file mode 100644 index 0000000000..9eadd590b5 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/lead/component/request/GetFinderLiveLeadsDataRequest.java @@ -0,0 +1,40 @@ +package me.chanjar.weixin.channel.bean.lead.component.request; + + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 获取账号收集的留资数据详情 + * @author imyzt + * @date 2024/01/27 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@JsonInclude(JsonInclude.Include.NON_NULL) +public class GetFinderLiveLeadsDataRequest { + + /** + * 开始时间 + */ + @JsonProperty("start_time") + private Long startTime; + + /** + * 结束时间 + */ + @JsonProperty("end_time") + private Long endTime; + + /** + * 来源类型 + * source_type 来源类型 0 直播 + */ + @JsonProperty("source_type") + private Long sourceType; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/lead/component/request/GetLeadInfoByComponentRequest.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/lead/component/request/GetLeadInfoByComponentRequest.java new file mode 100644 index 0000000000..9f34ee4405 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/lead/component/request/GetLeadInfoByComponentRequest.java @@ -0,0 +1,52 @@ +package me.chanjar.weixin.channel.bean.lead.component.request; + + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 按时间获取留资信息详情 + * @author imyzt + * @date 2024/01/27 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@JsonInclude(JsonInclude.Include.NON_NULL) +public class GetLeadInfoByComponentRequest { + + /** + * 用于查询某个留资组件某段时间内收集的留资信息 + */ + @JsonProperty("leads_component_id") + private String leadsComponentId; + + /** + * 开始时间 + */ + @JsonProperty("start_time") + private Long startTime; + + /** + * 结束时间 + */ + @JsonProperty("end_time") + private Long endTime; + + /** + * 顺序翻页,传入上次请求返回的last_buffer, 会从上次返回的结果往后翻一页 + */ + @JsonProperty("last_buffer") + private String lastBuffer; + + /** + * 接口版本号,默认=1 + * =null和=1,微信返回的结构不一样,=1信息更全 + */ + @JsonProperty("version") + private Integer version; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/lead/component/request/GetLeadsComponentIdRequest.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/lead/component/request/GetLeadsComponentIdRequest.java new file mode 100644 index 0000000000..7bfb27f36d --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/lead/component/request/GetLeadsComponentIdRequest.java @@ -0,0 +1,27 @@ +package me.chanjar.weixin.channel.bean.lead.component.request; + + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 获取留资组件Id列表详情 + * @author imyzt + * @date 2024/01/27 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@JsonInclude(JsonInclude.Include.NON_NULL) +public class GetLeadsComponentIdRequest { + + /** + * 顺序翻页,传入上次请求返回的last_buffer, 会从上次返回的结果往后翻一页 + */ + @JsonProperty("last_buffer") + private String lastBuffer; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/lead/component/request/GetLeadsComponentPromoteRecordRequest.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/lead/component/request/GetLeadsComponentPromoteRecordRequest.java new file mode 100644 index 0000000000..c0a2a91418 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/lead/component/request/GetLeadsComponentPromoteRecordRequest.java @@ -0,0 +1,45 @@ +package me.chanjar.weixin.channel.bean.lead.component.request; + + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 获取留资组件直播推广记录信息详情 + * @author imyzt + * @date 2024/01/27 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@JsonInclude(JsonInclude.Include.NON_NULL) +public class GetLeadsComponentPromoteRecordRequest { + + /** + * 用于查询某个留资组件某段时间内收集的留资信息 + */ + @JsonProperty("leads_component_id") + private String leadsComponentId; + + /** + * 开始时间 + */ + @JsonProperty("start_time") + private Long startTime; + + /** + * 结束时间 + */ + @JsonProperty("end_time") + private Long endTime; + + /** + * 顺序翻页,传入上次请求返回的last_buffer, 会从上次返回的结果往后翻一页 + */ + @JsonProperty("last_buffer") + private String lastBuffer; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/lead/component/request/GetLeadsInfoByRequestIdRequest.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/lead/component/request/GetLeadsInfoByRequestIdRequest.java new file mode 100644 index 0000000000..7ac4d9c24f --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/lead/component/request/GetLeadsInfoByRequestIdRequest.java @@ -0,0 +1,40 @@ +package me.chanjar.weixin.channel.bean.lead.component.request; + + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 按直播场次获取留资信息详情 + * @author imyzt + * @date 2024/01/27 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@JsonInclude(JsonInclude.Include.NON_NULL) +public class GetLeadsInfoByRequestIdRequest { + + /** + * 用于查询某个留资组件某场直播收集的留资信息 + */ + @JsonProperty("request_id") + private String requestId; + + /** + * 顺序翻页,传入上次请求返回的last_buffer, 会从上次返回的结果往后翻一页 + */ + @JsonProperty("last_buffer") + private String lastBuffer; + + /** + * 接口版本号,默认=1 + * =null和=1,微信返回的结构不一样,=1信息更全 + */ + @JsonProperty("version") + private Integer version; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/lead/component/request/GetLeadsRequestIdRequest.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/lead/component/request/GetLeadsRequestIdRequest.java new file mode 100644 index 0000000000..bcd08ae08c --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/lead/component/request/GetLeadsRequestIdRequest.java @@ -0,0 +1,33 @@ +package me.chanjar.weixin.channel.bean.lead.component.request; + + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 获取留资request_id列表详情 + * @author imyzt + * @date 2024/01/27 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@JsonInclude(JsonInclude.Include.NON_NULL) +public class GetLeadsRequestIdRequest { + + /** + * 用于查询某个留资组件某段时间内收集的留资信息 + */ + @JsonProperty("leads_component_id") + private String leadsComponentId; + + /** + * 顺序翻页,传入上次请求返回的last_buffer, 会从上次返回的结果往后翻一页 + */ + @JsonProperty("last_buffer") + private String lastBuffer; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/lead/component/response/FinderAttrResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/lead/component/response/FinderAttrResponse.java new file mode 100644 index 0000000000..89b30bfdde --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/lead/component/response/FinderAttrResponse.java @@ -0,0 +1,51 @@ +package me.chanjar.weixin.channel.bean.lead.component.response; + + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; + +/** + * 视频号账号信息 + * @author imyzt + * @date 2024/01/27 + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class FinderAttrResponse extends WxChannelBaseResponse { + + /** + * 用户留资信息列表 + */ + @JsonProperty("finder_attr") + private FinderAttr finderAttr; + + /** + * 用户留资信息 + */ + @Data + @NoArgsConstructor + public static class FinderAttr { + + /** + * 视频号唯一标识 + */ + @JsonProperty("uniq_id") + private String uniqId; + + /** + * 视频号昵称 + */ + @JsonProperty("nickname") + private String nickname; + + /** + * 视频号的粉丝数 + */ + @JsonProperty("fans_count") + private int fansCount; + } +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/lead/component/response/GetFinderLiveDataListResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/lead/component/response/GetFinderLiveDataListResponse.java new file mode 100644 index 0000000000..34a176c7a7 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/lead/component/response/GetFinderLiveDataListResponse.java @@ -0,0 +1,112 @@ +package me.chanjar.weixin.channel.bean.lead.component.response; + + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; + +import java.util.List; + +/** + * 留资直播间数据详情 + * @author imyzt + * @date 2024/01/27 + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class GetFinderLiveDataListResponse extends WxChannelBaseResponse { + + /** + * 直播统计信息列表 + */ + @JsonProperty("item") + private List items; + + /** + * 本次翻页的上下文,用于顺序翻页请求 + */ + @JsonProperty("last_buffer") + private String lastBuffer; + + /** + * 是否还有直播 + */ + @JsonProperty("continue_flag") + private boolean continueFlag; + + /** + * 直播统计信息 + */ + @Data + @NoArgsConstructor + public static class LiveStatisticsItem { + /** + * 直播唯一id + */ + @JsonProperty("export_id") + private String exportId; + + /** + * 开播时间戳 + */ + @JsonProperty("live_start_time") + private Long liveStartTime; + + /** + * 直播时长 + */ + @JsonProperty("live_duration_in_seconds") + private Long liveDurationInSeconds; + + /** + * 观看人数 + */ + @JsonProperty("total_audience_count") + private Long totalAudienceCount; + + /** + * 喝彩次数 + */ + @JsonProperty("total_cheer_count") + private Long totalCheerCount; + + /** + * 分享次数 + */ + @JsonProperty("forward_count") + private Long forwardCount; + + /** + * 评论条数 + */ + @JsonProperty("total_comment_count") + private Long totalCommentCount; + + /** + * 人均观看时长 + */ + @JsonProperty("audiences_avg_seconds") + private Long audiencesAvgSeconds; + + /** + * 最高在线人数 + */ + @JsonProperty("max_online_count") + private Long maxOnlineCount; + + /** + * 新增粉丝 + */ + @JsonProperty("new_follow_count") + private Long newFollowCount; + + /** + * 公众号新增粉丝 + */ + @JsonProperty("new_follow_count_biz") + private Long newFollowCountBiz; + } +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/lead/component/response/GetFinderLiveLeadsDataResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/lead/component/response/GetFinderLiveLeadsDataResponse.java new file mode 100644 index 0000000000..ffd9242f16 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/lead/component/response/GetFinderLiveLeadsDataResponse.java @@ -0,0 +1,59 @@ +package me.chanjar.weixin.channel.bean.lead.component.response; + + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; + +import java.util.List; + +/** + * 获取账号收集的留资数据详情 + * @author imyzt + * @date 2024/01/27 + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class GetFinderLiveLeadsDataResponse extends WxChannelBaseResponse { + + /** + * 留资统计信息列表 + */ + @JsonProperty("item") + private List items; + + /** + * 留资统计信息 + */ + @Data + @NoArgsConstructor + public static class LeadCountItem { + + /** + * 组件类型 + * 0 表单 + * 1 企微名片 + * 2 企微客服 + */ + @JsonProperty("component_type") + private int componentType; + + /** + * 流量来源 + * 0 自然流量 + * 1 广告流量 + */ + @JsonProperty("traffic_type") + private int trafficType; + + /** + * 留资条数 + */ + @JsonProperty("leads_count") + private int leadsCount; + + } +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/lead/component/response/GetLeadsComponentIdResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/lead/component/response/GetLeadsComponentIdResponse.java new file mode 100644 index 0000000000..c71f08b8c1 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/lead/component/response/GetLeadsComponentIdResponse.java @@ -0,0 +1,65 @@ +package me.chanjar.weixin.channel.bean.lead.component.response; + + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; + +import java.util.List; + +/** + * 留资组件Id列表详情 + * @author imyzt + * @date 2024/01/27 + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class GetLeadsComponentIdResponse extends WxChannelBaseResponse { + + /** + * 留资组件信息 + */ + @JsonProperty("item") + private List item; + + /** + * 本次翻页的上下文,用于顺序翻页请求 + */ + @JsonProperty("last_buffer") + private String lastBuffer; + + /** + * 是否还有留资信息 + */ + @JsonProperty("continue_flag") + private boolean continueFlag; + + /** + * 留资组件信息 + */ + @Data + @NoArgsConstructor + public static class LeadComponentItem { + + /** + * 留资组件id + */ + @JsonProperty("leads_component_id") + private String leadsComponentId; + + /** + * 留资组件标题 + */ + @JsonProperty("leads_description") + private String leadsDescription; + + /** + * 留资组件状态码 + */ + @JsonProperty("status") + private int status; + } +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/lead/component/response/GetLeadsComponentPromoteRecordResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/lead/component/response/GetLeadsComponentPromoteRecordResponse.java new file mode 100644 index 0000000000..bc90514a83 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/lead/component/response/GetLeadsComponentPromoteRecordResponse.java @@ -0,0 +1,71 @@ +package me.chanjar.weixin.channel.bean.lead.component.response; + + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; + +import java.util.List; + +/** + * 获取留资组件直播推广记录信息详情 + * @author imyzt + * @date 2024/01/27 + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class GetLeadsComponentPromoteRecordResponse extends WxChannelBaseResponse { + + /** + * 留资组件直播推广记录列表 + */ + @JsonProperty("record_data") + private List recordData; + + /** + * 本次翻页的上下文,用于顺序翻页请求 + */ + @JsonProperty("last_buffer") + private String lastBuffer; + + /** + * 是否还有留资信息 + */ + @JsonProperty("continue_flag") + private boolean continueFlag; + + /** + * 留资组件直播推广记录列表 + */ + @Data + @NoArgsConstructor + public static class RecordData { + + @JsonProperty("anchor_nickname") + private String anchorNickname; + + @JsonProperty("live_description") + private String liveDescription; + + @JsonProperty("live_start_time") + private long liveStartTime; + + @JsonProperty("live_audience_count") + private String liveAudienceCount; + + @JsonProperty("exposure_uv") + private String exposureUV; + + @JsonProperty("click_uv") + private String clickUV; + + @JsonProperty("exposure_click_rate") + private double exposureClickRate; + + @JsonProperty("leads_num") + private String leadsNum; + } +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/lead/component/response/GetLeadsRequestIdResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/lead/component/response/GetLeadsRequestIdResponse.java new file mode 100644 index 0000000000..6e09ee3016 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/lead/component/response/GetLeadsRequestIdResponse.java @@ -0,0 +1,66 @@ +package me.chanjar.weixin.channel.bean.lead.component.response; + + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; + +import java.util.List; + +/** + * 获取留资request_id列表详情 + * @author imyzt + * @date 2024/01/27 + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class GetLeadsRequestIdResponse extends WxChannelBaseResponse { + + /** + * 某一场直播对应的留资信息请求id + */ + @JsonProperty("item") + private List items; + + /** + * 本次翻页的上下文,用于顺序翻页请求 + */ + @JsonProperty("last_buffer") + private String lastBuffer; + + /** + * 是否还有留资信息 + */ + @JsonProperty("continue_flag") + private boolean continueFlag; + + /** + * 直播对应的留资信息 + */ + @Data + @NoArgsConstructor + public static class LiveLeadItem { + + /** + * 某一场直播对应的留资信息请求id + */ + @JsonProperty("request_id") + private String requestId; + + /** + * 直播开始时间 + */ + @JsonProperty("live_start_time") + private Long liveStartTime; + + /** + * 直播描述 + */ + @JsonProperty("live_description") + private String liveDescription; + + } +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/lead/component/response/LeadInfoResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/lead/component/response/LeadInfoResponse.java new file mode 100644 index 0000000000..bcb6dfab46 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/lead/component/response/LeadInfoResponse.java @@ -0,0 +1,90 @@ +package me.chanjar.weixin.channel.bean.lead.component.response; + + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; + +import java.util.List; + +/** + * 留资信息详情 + * @author imyzt + * @date 2024/01/27 + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class LeadInfoResponse extends WxChannelBaseResponse { + + /** + * 用户留资信息列表 + */ + @JsonProperty("user_data") + private List userData; + + /** + * 本次翻页的上下文,用于顺序翻页请求 + */ + @JsonProperty("last_buffer") + private String lastBuffer; + + /** + * 是否还有留资信息 + */ + @JsonProperty("continue_flag") + private boolean continueFlag; + + /** + * 用户留资信息 + */ + @Data + @NoArgsConstructor + public static class UserData { + + /** + * 主播昵称 + */ + @JsonProperty("anchor_nickname") + private String anchorNickname; + + /** + * 直播开始时间 + */ + @JsonProperty("live_start_time") + private Long liveStartTime; + + /** + * 用户留资信息列表 + */ + @JsonProperty("leads_data") + private List leadsData; + + /** + * 用户留资时间 + */ + @JsonProperty("time") + private Long time; + + } + + @Data + @NoArgsConstructor + public static class LeadsData { + + /** + * 表单名称 + */ + @JsonProperty("title") + private String title; + + /** + * 手机号,文本框,单选框时, 均为字符串 + * 仅当title=城市 时, 微信返回字符串数组, eg: ["北京市","北京市","东城区"] + */ + @JsonProperty("value") + private Object value; + } +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/league/AddressInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/league/AddressInfo.java new file mode 100644 index 0000000000..1ffb01677c --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/league/AddressInfo.java @@ -0,0 +1,33 @@ +package me.chanjar.weixin.channel.bean.league; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 地址信息 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +public class AddressInfo implements Serializable { + + private static final long serialVersionUID = -5719456688033731919L; + /** 邮编 */ + @JsonProperty("postal_code") + private String postalCode; + + /** 国标收货地址第一级地址 */ + @JsonProperty("province_name") + private String provinceName; + + /** 国标收货地址第二级地址 */ + @JsonProperty("city_name") + private String cityName; + + /** 国标收货地址第三级地址 */ + @JsonProperty("county_name") + private String countyName; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/league/CatInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/league/CatInfo.java new file mode 100644 index 0000000000..4fc2cfc95b --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/league/CatInfo.java @@ -0,0 +1,22 @@ +package me.chanjar.weixin.channel.bean.league; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 商品分类信息 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +public class CatInfo implements Serializable { + + private static final long serialVersionUID = 8449223922139383888L; + /** 类目id */ + @JsonProperty("cat_id") + private String catId; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/league/DescInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/league/DescInfo.java new file mode 100644 index 0000000000..a29b07a294 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/league/DescInfo.java @@ -0,0 +1,26 @@ +package me.chanjar.weixin.channel.bean.league; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import java.util.List; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 商详信息 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +public class DescInfo implements Serializable { + + private static final long serialVersionUID = 5319244341160446531L; + /** 商品详情图片(最多20张)。如果添加时没录入,回包可能不包含该字段 */ + @JsonProperty("imgs") + private List imgs; + + /** 商品详情文字。如果添加时没录入,回包可能不包含该字 */ + @JsonProperty("desc") + private String desc; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/league/ExpressInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/league/ExpressInfo.java new file mode 100644 index 0000000000..6fbecac866 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/league/ExpressInfo.java @@ -0,0 +1,30 @@ +package me.chanjar.weixin.channel.bean.league; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 物流信息 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +public class ExpressInfo implements Serializable { + + private static final long serialVersionUID = -4604691645808459334L; + /** 发货时间期限 */ + @JsonProperty("send_time") + private String sendTime; + + /** 发货地址 */ + @JsonProperty("address_info") + private AddressInfo addressInfo; + + /** 计费方式:FREE:包邮CONDITION_FREE:条件包邮NO_FREE:不包邮 */ + @JsonProperty("shipping_method") + private String shippingMethod; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/league/SimpleProductInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/league/SimpleProductInfo.java new file mode 100644 index 0000000000..9de16b849e --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/league/SimpleProductInfo.java @@ -0,0 +1,39 @@ +package me.chanjar.weixin.channel.bean.league; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import java.util.List; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 商品信息 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +public class SimpleProductInfo implements Serializable { + + private static final long serialVersionUID = -2444641123422095497L; + /** 标题 */ + @JsonProperty("title") + protected String title; + + /** 副标题 */ + @JsonProperty("sub_title") + protected String subTitle; + + /** 主图,多张,列表,最多9张,每张不超过2MB */ + @JsonProperty("head_imgs") + protected List headImgs; + + /** 商详信息 */ + @JsonProperty("desc_info") + protected DescInfo descInfo; + + /** 类目信息 */ + @JsonProperty("cats") + protected List cats; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/league/product/BatchAddParam.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/league/product/BatchAddParam.java new file mode 100644 index 0000000000..c22563359a --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/league/product/BatchAddParam.java @@ -0,0 +1,63 @@ +package me.chanjar.weixin.channel.bean.league.product; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import java.util.List; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 批量添加商品参数 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@JsonInclude(JsonInclude.Include.NON_NULL) +public class BatchAddParam implements Serializable { + + private static final long serialVersionUID = -87989229725625901L; + /** 商品推广类别 */ + @JsonProperty("type") + private Integer type; + + /** 商品列表 */ + @JsonProperty("list") + private List list; + + /** 推广达人列表 */ + @JsonProperty("finder_ids") + private List finderIds; + + /** 推广开始时间戳 */ + @JsonProperty("begin_time") + private Long beginTime; + + /** 推广结束时间戳 */ + @JsonProperty("end_time") + private Long endTime; + + /** 是否永久推广 */ + @JsonProperty("is_forerver") + private Boolean forever; + + + @Data + @NoArgsConstructor + @AllArgsConstructor + @JsonInclude(JsonInclude.Include.NON_NULL) + public static class Product implements Serializable { + + private static final long serialVersionUID = 9025105293896488093L; + /** 商品id,不可重复数量不超过20 */ + @JsonProperty("product_id") + private String productId; + + /** 推广佣金[0, 90]% */ + @JsonProperty("ratio") + private Integer ratio; + + } +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/league/product/BatchAddResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/league/product/BatchAddResponse.java new file mode 100644 index 0000000000..7a2f4f6840 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/league/product/BatchAddResponse.java @@ -0,0 +1,44 @@ +package me.chanjar.weixin.channel.bean.league.product; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.List; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; + +/** + * 批量添加商品响应 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class BatchAddResponse extends WxChannelBaseResponse { + + private static final long serialVersionUID = 2686612709939873527L; + /** 商品id信息 */ + @JsonProperty("result_info_list") + private List resultInfoList; + + + @Data + @NoArgsConstructor + @EqualsAndHashCode(callSuper = true) + public static class ResultInfo extends WxChannelBaseResponse { + + private static final long serialVersionUID = -534890760974302155L; + /** 商品id */ + @JsonProperty("product_id") + private String productId; + + /** 特殊推广商品计划id */ + @JsonProperty("info_id") + private String infoId; + + /** 推广失败达人列表 */ + @JsonProperty("fail_finder_ids") + private List failFinderIds; + } +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/league/product/ProductDeleteParam.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/league/product/ProductDeleteParam.java new file mode 100644 index 0000000000..ccdf3ef0b4 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/league/product/ProductDeleteParam.java @@ -0,0 +1,33 @@ +package me.chanjar.weixin.channel.bean.league.product; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 商品删除请求 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@JsonInclude(JsonInclude.Include.NON_NULL) +public class ProductDeleteParam implements Serializable { + + private static final long serialVersionUID = 9129737170370664633L; + /** 获取商品推广类别 */ + @JsonProperty("type") + private Integer type; + + /** 商品id type为普通推广商品时必填 */ + @JsonProperty("product_id") + private String productId; + + /** 特殊推广商品计划id type为特殊推广商品时必填 */ + @JsonProperty("info_id") + private String infoId; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/league/product/ProductDetailParam.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/league/product/ProductDetailParam.java new file mode 100644 index 0000000000..7b2c6ae6f7 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/league/product/ProductDetailParam.java @@ -0,0 +1,48 @@ +package me.chanjar.weixin.channel.bean.league.product; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 商品详情请求 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@JsonInclude(JsonInclude.Include.NON_NULL) +public class ProductDetailParam implements Serializable { + + private static final long serialVersionUID = 7624234965127527565L; + /** 获取商品推广类别 */ + @JsonProperty("type") + private Integer type; + + /** 商品id type为普通推广商品时必填 */ + @JsonProperty("product_id") + private String productId; + + /** 特殊推广商品计划id type为特殊推广商品时必填 */ + @JsonProperty("info_id") + private String infoId; + + /** 是否获取特殊推广商品绑定的达人列表, type为特殊推广商品时有效 */ + @JsonProperty("need_relation") + private Boolean needRelation; + + /** 拉取达人数 need_relation为真时必填 不超过50 */ + @JsonProperty("page_size") + private Integer pageSize; + + /** need_relation为真时有效,页面下标,下标从1开始,默认为1 */ + @JsonProperty("page_index") + private Integer pageIndex; + + /** need_relation为真时有效,是否需要返回该计划绑定达人总数 */ + @JsonProperty("need_total_num") + private Boolean needTotalNum; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/league/product/ProductDetailResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/league/product/ProductDetailResponse.java new file mode 100644 index 0000000000..05ea00c055 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/league/product/ProductDetailResponse.java @@ -0,0 +1,96 @@ +package me.chanjar.weixin.channel.bean.league.product; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import java.util.List; +import lombok.Data; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; + +/** + * 商品详情响应 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +public class ProductDetailResponse extends WxChannelBaseResponse { + + private static final long serialVersionUID = 5306524707144232861L; + /** 推广商品信息 */ + @JsonProperty("item") + private Item item; + + + @Data + @NoArgsConstructor + public static class Item implements Serializable { + + private static final long serialVersionUID = 9112142704638318861L; + /** 商品id */ + @JsonProperty("product_id") + private String productId; + + /** 商品推广类别 1普通推广商品 2定向推广商品 3专属推广商品 */ + @JsonProperty("type") + private Integer type; + + /** 商品推广状态 1已上架推广 2已下架推广 4已删除 5未达到准入标准 10待生效 */ + @JsonProperty("status") + private Integer status; + + /** 推广佣金[0, 90]% */ + @JsonProperty("ratio") + private Integer ratio; + + /** 特殊推广信息 */ + @JsonProperty("exclusive_info") + private ExclusiveInfo exclusiveInfo; + + /** 扩展信息 */ + @JsonProperty("ext_info") + private ExtInfo extInfo; + } + + @Data + @NoArgsConstructor + public static class ExclusiveInfo implements Serializable { + + private static final long serialVersionUID = 6583124869090013797L; + /** 特殊推广商品计划id */ + @JsonProperty("info_id") + private String infoId; + + /** 推广开始时间戳 */ + @JsonProperty("begin_time") + private Long beginTime; + + /** 推广结束时间戳 */ + @JsonProperty("end_time") + private Long endTime; + + /** 是否永久推广 */ + @JsonProperty("is_forerver") + private Boolean forever; + + /** 推广达人视频号列表 */ + @JsonProperty("finder_ids") + private List finderIds; + + } + + @Data + @NoArgsConstructor + public static class ExtInfo implements Serializable { + + /** 是否类目禁售 */ + @JsonProperty("is_sale_forbidden") + private Boolean saleForbidden; + + /** 是否被官方封禁 */ + @JsonProperty("is_banned") + private Boolean banned; + + } + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/league/product/ProductListParam.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/league/product/ProductListParam.java new file mode 100644 index 0000000000..18d52c82d6 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/league/product/ProductListParam.java @@ -0,0 +1,47 @@ +package me.chanjar.weixin.channel.bean.league.product; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 商品列表请求 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@JsonInclude(JsonInclude.Include.NON_NULL) +public class ProductListParam implements Serializable { + + private static final long serialVersionUID = -1914139382459786057L; + /** 商品推广类别 */ + @JsonProperty("type") + private Integer type; + + /** 单页商品数(不超过100) */ + @JsonProperty("page_size") + private Integer pageSize; + + /** 页面下标,下标从1开始,默认为1 */ + @JsonProperty("page_index") + private Integer pageIndex; + + /** 商品id,拉取特殊推广商品时有效 */ + @JsonProperty("product_id") + private String productId; + + /** 视频号id,拉取特殊推广商品时有效 */ + @JsonProperty("finder_id") + private String finderId; + + /** 由上次请求返回,顺序翻页时需要传入, 会从上次返回的结果往后翻一页(填了该值后page_index不生效) */ + @JsonProperty("last_buffer") + private String lastBuffer; + + /** 是否需要返回满足筛选条件的商品总数(填last_buffer后该值无效) */ + @JsonProperty("need_total_num") + private Boolean needTotalNum; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/league/product/ProductListResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/league/product/ProductListResponse.java new file mode 100644 index 0000000000..642884ce63 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/league/product/ProductListResponse.java @@ -0,0 +1,52 @@ +package me.chanjar.weixin.channel.bean.league.product; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import java.util.List; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; + +/** + * 商品更新响应 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class ProductListResponse extends WxChannelBaseResponse { + + private static final long serialVersionUID = -6192518391380515045L; + /** 商品列表 */ + @JsonProperty("items") + private List items; + + /** 本次翻页的上下文,用于顺序翻页请求 */ + @JsonProperty("last_buffer") + private String lastBuffer; + + /** 商品总数 */ + @JsonProperty("total_num") + private Integer totalNum; + + /** 是否还有剩余商品 */ + @JsonProperty("has_more") + private Boolean hasMore; + + @Data + @NoArgsConstructor + public static class Item implements Serializable { + + private static final long serialVersionUID = 5094378518992196239L; + /** 商品id */ + @JsonProperty("product_id") + private String productId; + + /** 特殊推广商品计划id */ + @JsonProperty("info_id") + private String infoId; + + } +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/league/product/ProductUpdateParam.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/league/product/ProductUpdateParam.java new file mode 100644 index 0000000000..8188911519 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/league/product/ProductUpdateParam.java @@ -0,0 +1,72 @@ +package me.chanjar.weixin.channel.bean.league.product; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import java.util.List; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 商品更新请求 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@JsonInclude(JsonInclude.Include.NON_NULL) +public class ProductUpdateParam implements Serializable { + + private static final long serialVersionUID = -3519313269193693460L; + /** 获取商品推广类别 */ + @JsonProperty("type") + private Integer type; + + /** 商品id type为普通推广商品时必填 */ + @JsonProperty("product_id") + private String productId; + + /** 特殊推广商品计划id type为特殊推广商品时必填 */ + @JsonProperty("info_id") + private String infoId; + + /** 更新操作类别 */ + @JsonProperty("operate_type") + private Integer operateType; + + /** 推广佣金[0, 90]% */ + @JsonProperty("ratio") + private Integer ratio; + + /** 特殊推广信息 */ + @JsonProperty("exclusive_info") + private ExclusiveInfo exclusiveInfo; + + + /** 特殊推广信息 */ + @Data + @NoArgsConstructor + public static class ExclusiveInfo implements Serializable { + + private static final long serialVersionUID = -8120260214345369170L; + /** 推广开始时间戳 */ + @JsonProperty("begin_time") + private Long beginTime; + + /** 推广结束时间戳 */ + @JsonProperty("end_time") + private Long endTime; + + /** 是否永久推广 */ + @JsonProperty("is_forerver") + private Boolean forever; + + /** 新增推广达人视频号列表,不超过30个 */ + @JsonProperty("add_finder_ids") + private List addFinderIds; + + /** 删除推广达人视频号列表,不超过30个 */ + @JsonProperty("del_finder_ids") + private List delFinderIds; + } +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/league/product/ProductUpdateResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/league/product/ProductUpdateResponse.java new file mode 100644 index 0000000000..7dae90569e --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/league/product/ProductUpdateResponse.java @@ -0,0 +1,23 @@ +package me.chanjar.weixin.channel.bean.league.product; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; + +/** + * 商品更新响应 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class ProductUpdateResponse extends WxChannelBaseResponse { + + private static final long serialVersionUID = 2144233059960259829L; + /** 特殊推广商品计划id */ + @JsonProperty("info_id") + private String infoId; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/league/promoter/PromoterInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/league/promoter/PromoterInfo.java new file mode 100644 index 0000000000..7f817c6633 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/league/promoter/PromoterInfo.java @@ -0,0 +1,37 @@ +package me.chanjar.weixin.channel.bean.league.promoter; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 达人 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +public class PromoterInfo implements Serializable { + + private static final long serialVersionUID = -8851711325343107780L; + /** 视频号finder_id */ + @JsonProperty("finder_id") + private String finderId; + + /** 合作状态 0初始值 1邀请中 2达人已接受邀请 3达人已拒绝邀请 4已取消邀请 5已取消合作 10已删除 */ + @JsonProperty("status") + private Integer status; + + /** 达人邀请秒级时间戳 */ + @JsonProperty("invite_time") + private Long inviteTime; + + /** 累计合作商品数 */ + @JsonProperty("sale_product_number") + private Integer saleProductNumber; + + /** 合作动销GMV */ + @JsonProperty("sale_gmv") + private Integer saleGmv; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/league/promoter/PromoterInfoResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/league/promoter/PromoterInfoResponse.java new file mode 100644 index 0000000000..bebe6a6fcc --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/league/promoter/PromoterInfoResponse.java @@ -0,0 +1,23 @@ +package me.chanjar.weixin.channel.bean.league.promoter; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; + +/** + * 达人信息响应 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class PromoterInfoResponse extends WxChannelBaseResponse { + + private static final long serialVersionUID = 6475158486029216487L; + /** 达人信息 */ + @JsonProperty("promoter") + private PromoterInfo promoter; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/league/promoter/PromoterListParam.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/league/promoter/PromoterListParam.java new file mode 100644 index 0000000000..128797bda8 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/league/promoter/PromoterListParam.java @@ -0,0 +1,30 @@ +package me.chanjar.weixin.channel.bean.league.promoter; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 达人列表请求 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class PromoterListParam implements Serializable { + + private static final long serialVersionUID = -6179472484874537538L; + /** 页面下标,下标从1开始,默认为1 */ + @JsonProperty("page_index") + protected Integer pageIndex; + + /** 页面下标,下标从1开始,默认为1 */ + @JsonProperty("page_size") + protected Integer pageSize; + + /** 拉取该状态下的达人列表 */ + private Integer status; +} 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 new file mode 100644 index 0000000000..f7c7298c03 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/league/promoter/PromoterListResponse.java @@ -0,0 +1,36 @@ +package me.chanjar.weixin.channel.bean.league.promoter; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.List; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; + +/** + * 达人列表响应 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class PromoterListResponse extends WxChannelBaseResponse { + + private static final long serialVersionUID = 1411870432999885996L; + /** 达人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/league/supplier/BizBaseInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/league/supplier/BizBaseInfo.java new file mode 100644 index 0000000000..39b77daa32 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/league/supplier/BizBaseInfo.java @@ -0,0 +1,29 @@ +package me.chanjar.weixin.channel.bean.league.supplier; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 小店基础信息 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +public class BizBaseInfo implements Serializable { + + private static final long serialVersionUID = 3713638025924977002L; + /** 小店appid */ + @JsonProperty("appid") + private String appid; + + /** 小店头像 */ + @JsonProperty("headimg_url") + private String headimgUrl; + + /** 小店昵称 */ + @JsonProperty("nickname") + private String nickname; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/league/supplier/CommissionInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/league/supplier/CommissionInfo.java new file mode 100644 index 0000000000..356a058684 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/league/supplier/CommissionInfo.java @@ -0,0 +1,41 @@ +package me.chanjar.weixin.channel.bean.league.supplier; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 跟佣信息 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +public class CommissionInfo implements Serializable { + + private static final long serialVersionUID = 3027131215096984236L; + /** 商品带货状态 1已上架推广 2已下架推广 5已清退 */ + @JsonProperty("status") + private Integer status; + + /** 服务费率[0, 1000000] */ + @JsonProperty("service_ratio") + private Integer serviceRatio; + + /** 佣金费率[0, 1000000] */ + @JsonProperty("ratio") + private Integer ratio; + + /** unix时间戳,合作开始时间 */ + @JsonProperty("start_time") + private Long startTime; + + /** unix时间戳,合作结束时间 */ + @JsonProperty("end_time") + private Long endTime; + + /** 带货链接 */ + @JsonProperty("link") + private String link; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/league/supplier/CommissionOrderListParam.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/league/supplier/CommissionOrderListParam.java new file mode 100644 index 0000000000..2f8d27f52e --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/league/supplier/CommissionOrderListParam.java @@ -0,0 +1,53 @@ +package me.chanjar.weixin.channel.bean.league.supplier; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.Data; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.bean.base.TimeRange; + +/** + * 佣金单列表请求参数 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@JsonInclude(JsonInclude.Include.NON_NULL) +public class CommissionOrderListParam implements Serializable { + + private static final long serialVersionUID = 2805783646567362357L; + /** 佣金单所属小店appid */ + @JsonProperty("appid") + private String appid; + + /** 视频号finder_id */ + @JsonProperty("finder_id") + private String finderId; + + /** 视频号openfinderid */ + @JsonProperty("openfinderid") + private String openfinderid; + + /** 佣金单创建时间范围 */ + @JsonProperty("create_time_range") + private TimeRange createTimeRange; + + /** 佣金单更新时间范围 */ + @JsonProperty("update_time_range") + private TimeRange updateTimeRange; + + /** 订单ID,填此参数后其他过滤参数无效 */ + @JsonProperty("order_id") + private String orderId; + + /** 单页佣金单数(不超过30) */ + @JsonProperty("page_size") + private Integer pageSize; + + /** 由上次请求返回,顺序翻页时需要传入, 会从上次返回的结果往后翻一页 */ + @JsonProperty("next_key") + private String nextKey; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/league/supplier/CommissionOrderListResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/league/supplier/CommissionOrderListResponse.java new file mode 100644 index 0000000000..6d7e1be4f8 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/league/supplier/CommissionOrderListResponse.java @@ -0,0 +1,50 @@ +package me.chanjar.weixin.channel.bean.league.supplier; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import java.util.List; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; + + +/** + * 团长订单列表响应 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class CommissionOrderListResponse extends WxChannelBaseResponse { + + private static final long serialVersionUID = 1143184321517598592L; + /** 商品id信息 */ + @JsonProperty("list") + private List list; + + /** 本次翻页的上下文,用于顺序翻页请求 */ + @JsonProperty("next_key") + private String nextKey; + + /** 是否还有剩余商品 */ + @JsonProperty("has_more") + private Boolean hasMore; + + + @Data + @NoArgsConstructor + public static class ProductIdInfo implements Serializable { + + private static final long serialVersionUID = -691189837681217282L; + /** 商品id */ + @JsonProperty("order_id") + private String orderId; + + /** skuid */ + @JsonProperty("sku_id") + private String skuId; + + } +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/league/supplier/CommissionOrderResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/league/supplier/CommissionOrderResponse.java new file mode 100644 index 0000000000..d8c84e1473 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/league/supplier/CommissionOrderResponse.java @@ -0,0 +1,174 @@ +package me.chanjar.weixin.channel.bean.league.supplier; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; + +/** + * 佣金订单响应 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class CommissionOrderResponse extends WxChannelBaseResponse { + + private static final long serialVersionUID = 7004553990771819977L; + /** 佣金单 */ + @JsonProperty("commssion_order") + private CommissionOrder commissionOrder; + + + /** 佣金单 */ + @Data + @NoArgsConstructor + public static class CommissionOrder implements Serializable { + + private static final long serialVersionUID = 735931726521944716L; + /** 订单号 */ + @JsonProperty("order_id") + private String orderId; + + /** 商品skuid */ + @JsonProperty("sku_id") + private String skuId; + + /** 秒级时间戳 */ + @JsonProperty("create_time") + private Long createTime; + + /** 秒级时间戳 */ + @JsonProperty("update_time") + private Long updateTime; + + /** 佣金单状态,见{@link me.chanjar.weixin.channel.enums.CommissionOrderStatus} */ + @JsonProperty("status") + private Integer status; + + /** 订单详情 */ + @JsonProperty("order_detail") + private OrderDetail orderDetail; + } + + /** 订单详情 */ + @Data + @NoArgsConstructor + public static class OrderDetail implements Serializable { + + private static final long serialVersionUID = 8349635368396073000L; + /** 小店商家信息 */ + @JsonProperty("shop_info") + private BizInfo shopInfo; + + /** 佣金单商品信息 */ + @JsonProperty("product_info") + private ProductInfo productInfo; + + /** 订单信息 */ + @JsonProperty("order_info") + private OrderInfo orderInfo; + + /** 分佣信息 */ + @JsonProperty("commission_info") + private CommissionInfo commissionInfo; + + } + + /** 小店商家信息 */ + @Data + @NoArgsConstructor + public static class BizInfo implements Serializable { + + private static final long serialVersionUID = -8229584987720782974L; + /** 所属小店appid */ + @JsonProperty("appid") + private String appid; + + } + + /** 佣金单商品信息 */ + @Data + @NoArgsConstructor + public static class ProductInfo implements Serializable { + + private static final long serialVersionUID = -2790410903073956864L; + /** 商品id */ + @JsonProperty("product_id") + private String productId; + + /** sku小图 */ + @JsonProperty("thumb_img") + private String thumbImg; + + /** 可分佣金额 */ + @JsonProperty("actual_payment") + private Integer actualPayment; + + /** 商品标题 */ + @JsonProperty("title") + private String title; + + } + + /** 订单信息 */ + @Data + @NoArgsConstructor + public static class OrderInfo implements Serializable { + + private static final long serialVersionUID = 7610425518539999170L; + /** 订单状态,枚举值见OrderStatus */ + @JsonProperty("order_status") + private Integer status; + + } + + /** 分佣信息 */ + @Data + @NoArgsConstructor + public static class CommissionInfo implements Serializable { + + private static final long serialVersionUID = -2114290318872427720L; + /** 带货达人信息 */ + @JsonProperty("finder_info") + private FinderInfo finderInfo; + + /** 服务费率[0, 1000000] */ + @JsonProperty("service_ratio") + private Integer serviceRatio; + + /** 服务费金额 */ + @JsonProperty("service_amount") + private Integer serviceAmount; + + /** 服务费结算时间 */ + @JsonProperty("profit_sharding_suc_time") + private Long profitShardingSucTime; + + } + + /** 带货达人信息 */ + @Data + @NoArgsConstructor + public static class FinderInfo implements Serializable { + + private static final long serialVersionUID = 7383486670949864257L; + /** 达人昵称 */ + @JsonProperty("nickname") + private String nickname; + + /** 佣金率[0, 1000000] */ + @JsonProperty("ratio") + private Integer ratio; + /** 佣金 */ + @JsonProperty("amount") + private Integer amount; + + /** 视频号openfinderid */ + @JsonProperty("openfinderid") + private String openfinderid; + } +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/league/supplier/CoopProductDetailParam.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/league/supplier/CoopProductDetailParam.java new file mode 100644 index 0000000000..494e86f999 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/league/supplier/CoopProductDetailParam.java @@ -0,0 +1,29 @@ +package me.chanjar.weixin.channel.bean.league.supplier; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 合作商品详情请求 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@JsonInclude(JsonInclude.Include.NON_NULL) +public class CoopProductDetailParam implements Serializable { + + private static final long serialVersionUID = 3515221514742929207L; + /** 商品id */ + @JsonProperty("product_id") + private String productId; + + /** 团长商品 所属小店appid */ + @JsonProperty("appid") + private String appId; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/league/supplier/CoopProductListParam.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/league/supplier/CoopProductListParam.java new file mode 100644 index 0000000000..81743b51f2 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/league/supplier/CoopProductListParam.java @@ -0,0 +1,34 @@ +package me.chanjar.weixin.channel.bean.league.supplier; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 合作商品详情请求 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@JsonInclude(JsonInclude.Include.NON_NULL) +public class CoopProductListParam implements Serializable { + + private static final long serialVersionUID = -9023029707828535352L; + /** 团长商品 所属小店appid */ + @JsonProperty("appid") + private String appid; + + /** 单页商品数(不超过30) */ + @JsonProperty("page_size") + private Integer pageSize; + + /** 由上次请求返回,顺序翻页时需要传入, 会从上次返回的结果往后翻一页 */ + @JsonProperty("next_key") + private String nextKey; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/league/supplier/CoopProductListResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/league/supplier/CoopProductListResponse.java new file mode 100644 index 0000000000..39bf2bb96e --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/league/supplier/CoopProductListResponse.java @@ -0,0 +1,49 @@ +package me.chanjar.weixin.channel.bean.league.supplier; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import java.util.List; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; + +/** + * 团长商品列表响应 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class CoopProductListResponse extends WxChannelBaseResponse { + + private static final long serialVersionUID = -5440144076389135839L; + /** 商品id信息 */ + @JsonProperty("list") + private List list; + + /** 本次翻页的上下文,用于顺序翻页请求 */ + @JsonProperty("next_key") + private String nextKey; + + /** 是否还有剩余商品 */ + @JsonProperty("has_more") + private Boolean hasMore; + + + @Data + @NoArgsConstructor + public static class ProductIdInfo implements Serializable { + + private static final long serialVersionUID = -7136011408769169462L; + /** 商品id */ + @JsonProperty("product_id") + private String productId; + + /** 所属小店appid */ + @JsonProperty("appid") + private String appid; + + } +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/league/supplier/CoopProductResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/league/supplier/CoopProductResponse.java new file mode 100644 index 0000000000..eee78937b2 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/league/supplier/CoopProductResponse.java @@ -0,0 +1,47 @@ +package me.chanjar.weixin.channel.bean.league.supplier; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; + +/** + * 合作商品详情响应 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class CoopProductResponse extends WxChannelBaseResponse { + + private static final long serialVersionUID = -4066427847985394479L; + /** 商品信息 */ + @JsonProperty("item") + private Item item; + + + @Data + @NoArgsConstructor + public static class Item implements Serializable { + + private static final long serialVersionUID = 6123572874440025928L; + /** 所属小店appid */ + @JsonProperty("appid") + private String appid; + + /** 商品id */ + @JsonProperty("product_id") + private String productId; + + /** 商品信息 */ + @JsonProperty("product_info") + private ProductInfo productInfo; + + /** 跟佣信息 */ + @JsonProperty("commission_info") + private CommissionInfo commissionInfo; + } +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/league/supplier/FlowListParam.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/league/supplier/FlowListParam.java new file mode 100644 index 0000000000..99d2bf107f --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/league/supplier/FlowListParam.java @@ -0,0 +1,43 @@ +package me.chanjar.weixin.channel.bean.league.supplier; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 团长流水列表请求参数 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@JsonInclude(JsonInclude.Include.NON_NULL) +public class FlowListParam implements Serializable { + + private static final long serialVersionUID = 3128695806885851134L; + /** 页码,从1开始 */ + @JsonProperty("page") + private Integer page; + + /** 页数,不填默认为10 */ + @JsonProperty("page_size") + private Integer pageSize; + + /** 资金类型, 见 {@link me.chanjar.weixin.channel.enums.FundsType} */ + @JsonProperty("funds_type") + private Integer fundsType; + + /** 流水产生的开始时间,uinx时间戳 */ + @JsonProperty("start_time") + private Long startTime; + + /** 流水产生的结束时间,unix时间戳 */ + @JsonProperty("end_time") + private Long endTime; + + /** 分页参数,翻页时写入上一页返回的next_key(page为上一页加一,并且page_size与上一页相同的时候才生效),page * page_size >= 5000时必填 */ + @JsonProperty("next_key") + private String nextKey; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/league/supplier/FundsFlowInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/league/supplier/FundsFlowInfo.java new file mode 100644 index 0000000000..089c7048d5 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/league/supplier/FundsFlowInfo.java @@ -0,0 +1,51 @@ +package me.chanjar.weixin.channel.bean.league.supplier; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.Data; +import lombok.NoArgsConstructor; + + +/** + * 团长资金流水详情 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +public class FundsFlowInfo implements Serializable { + + private static final long serialVersionUID = 102705878551490327L; + /** 流水id */ + @JsonProperty("flow_id") + private String flowId; + + /** 资金类型, 1提现 2分账 */ + @JsonProperty("funds_type") + private Integer fundsType; + + /** 流水金额 单位:分 */ + @JsonProperty("amount") + private Integer amount; + + /** 余额 单位:分 */ + @JsonProperty("balance") + private Integer balance; + + /** 记账时间 */ + @JsonProperty("bookkeeping_time") + private String bookkeepingTime; + + /** 备注 */ + @JsonProperty("remark") + private String remark; + + /** 关联订单号 */ + @JsonProperty("order_id") + private String orderId; + + /** 关联提现单号 */ + @JsonProperty("withdraw_id") + private String withdrawId; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/league/supplier/ProductInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/league/supplier/ProductInfo.java new file mode 100644 index 0000000000..0c2dcae7a6 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/league/supplier/ProductInfo.java @@ -0,0 +1,29 @@ +package me.chanjar.weixin.channel.bean.league.supplier; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.List; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.bean.league.ExpressInfo; +import me.chanjar.weixin.channel.bean.league.SimpleProductInfo; + +/** + * 商品信息 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class ProductInfo extends SimpleProductInfo { + + private static final long serialVersionUID = 5352334936089828219L; + /** 快递信息 */ + @JsonProperty("express_info") + private ExpressInfo expressInfo; + + /** sku信息 */ + @JsonProperty("skus") + private List skus; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/league/supplier/ShopDetailResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/league/supplier/ShopDetailResponse.java new file mode 100644 index 0000000000..14eaacb567 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/league/supplier/ShopDetailResponse.java @@ -0,0 +1,89 @@ +package me.chanjar.weixin.channel.bean.league.supplier; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; + +/** + * 合作小店详情响应 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class ShopDetailResponse extends WxChannelBaseResponse { + + private static final long serialVersionUID = 188954608418544735L; + /** 小店详情 */ + @JsonProperty("shop_detail") + private ShopDetail shopDetail; + + /** 本次翻页的上下文,用于顺序翻页请求 */ + @JsonProperty("next_key") + private String nextKey; + + /** 是否还有剩余小店 */ + @JsonProperty("has_more") + private Boolean hasMore; + + /** 小店详情 */ + @Data + @NoArgsConstructor + public static class ShopDetail implements Serializable { + + private static final long serialVersionUID = -3454074422563804378L; + /** 小店基础信息 */ + @JsonProperty("base_info") + private BizBaseInfo baseInfo; + + /** 小店数据信息 */ + @JsonProperty("data_info") + private ShopDataInfo dataInfo; + + /** 合作状态Status 1邀请中 2已接受邀请 3已拒绝邀请 4取消邀请 5取消合作 */ + @JsonProperty("status") + private Integer status; + + /** 开始合作时间戳 */ + @JsonProperty("approved_time") + private Long approvedTime; + + } + + + /** 小店数据信息 */ + @Data + @NoArgsConstructor + public static class ShopDataInfo implements Serializable { + + private static final long serialVersionUID = 6603460255046252283L; + /** 合作动销GMV,单位:分 */ + @JsonProperty("gmv") + private Integer gmv; + + /** 历史合作商品数 */ + @JsonProperty("product_number") + private Integer productNumber; + + /** 已结算服务费,单位:分 */ + @JsonProperty("settle_amount") + private Integer settleAmount; + + /** 预计待结算服务费,单位:分 */ + @JsonProperty("unsettle_amount") + private Integer unsettleAmount; + + /** 今日新增合作商品数 */ + @JsonProperty("product_number_today") + private Integer productNumberToday; + + /** 今日动销商品数 */ + @JsonProperty("product_number_sold_today") + private Integer productNumberSoldToday; + } + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/league/supplier/ShopListResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/league/supplier/ShopListResponse.java new file mode 100644 index 0000000000..1c7de7c4a7 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/league/supplier/ShopListResponse.java @@ -0,0 +1,50 @@ +package me.chanjar.weixin.channel.bean.league.supplier; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import java.util.List; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; + +/** + * 合作小店列表响应 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class ShopListResponse extends WxChannelBaseResponse { + + private static final long serialVersionUID = 1736467471867767456L; + /** 小店详情 */ + @JsonProperty("shop_list") + private List shopList; + + /** 本次翻页的上下文,用于顺序翻页请求 */ + @JsonProperty("next_key") + private String nextKey; + + /** 是否还有剩余小店 */ + @JsonProperty("has_more") + private Boolean hasMore; + + /** 小店详情 */ + @Data + @NoArgsConstructor + public static class ShopDetail implements Serializable { + + private static final long serialVersionUID = 8421286426372052694L; + /** 小店基础信息 */ + @JsonProperty("base_info") + private BizBaseInfo baseInfo; + + /** 小店状态 */ + @JsonProperty("status") + private Integer status; + } + + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/league/supplier/SkuInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/league/supplier/SkuInfo.java new file mode 100644 index 0000000000..6c51910d47 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/league/supplier/SkuInfo.java @@ -0,0 +1,40 @@ +package me.chanjar.weixin.channel.bean.league.supplier; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import java.util.List; +import lombok.Data; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.bean.base.AttrInfo; + + +/** + * SkuInfo + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +public class SkuInfo implements Serializable { + + private static final long serialVersionUID = 197261426211990640L; + /** skuID */ + @JsonProperty("sku_id") + private String skuId; + + /** sku小图。如果添加时没录入,回包可能不包含该字段 */ + @JsonProperty("thumb_img") + private String thumbImg; + + /** 售卖价格,以分为单位 */ + @JsonProperty("sale_price") + private Integer salePrice; + + /** sku库存 */ + @JsonProperty("stock_num") + private Integer stockNum; + + /** sku属性 */ + @JsonProperty("sku_attrs") + private List skuAttrs; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/league/supplier/SupplierBalanceResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/league/supplier/SupplierBalanceResponse.java new file mode 100644 index 0000000000..eaa45027da --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/league/supplier/SupplierBalanceResponse.java @@ -0,0 +1,27 @@ +package me.chanjar.weixin.channel.bean.league.supplier; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; + +/** + * 团长余额响应 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class SupplierBalanceResponse extends WxChannelBaseResponse { + + private static final long serialVersionUID = 5584817726976222436L; + /** 可提现余额 */ + @JsonProperty("available_amount") + private Integer availableAmount; + + /** 待结算余额 */ + @JsonProperty("pending_amount") + private Integer pendingAmount; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/league/supplier/SupplierFlowDetailResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/league/supplier/SupplierFlowDetailResponse.java new file mode 100644 index 0000000000..5ab0745fab --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/league/supplier/SupplierFlowDetailResponse.java @@ -0,0 +1,23 @@ +package me.chanjar.weixin.channel.bean.league.supplier; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; + +/** + * 团长流水明细响应 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class SupplierFlowDetailResponse extends WxChannelBaseResponse { + + private static final long serialVersionUID = -3962482396458765234L; + /** 流水信息 */ + @JsonProperty("funds_flow") + private FundsFlowInfo fundsFlow; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/league/supplier/SupplierFlowListResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/league/supplier/SupplierFlowListResponse.java new file mode 100644 index 0000000000..d4a04af981 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/league/supplier/SupplierFlowListResponse.java @@ -0,0 +1,30 @@ +package me.chanjar.weixin.channel.bean.league.supplier; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.List; +import lombok.Data; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; + +/** + * 资金流水列表 响应 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +public class SupplierFlowListResponse extends WxChannelBaseResponse { + + private static final long serialVersionUID = -2954427554226407544L; + /** 流水单号列表 */ + @JsonProperty("funds_flow_ids") + private List ids; + + /** 是否还有下一页 */ + @JsonProperty("has_more") + private Boolean hasMore; + + /** 分页参数,深翻页时使用 */ + @JsonProperty("next_key") + private String nextKey; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/league/window/AuthInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/league/window/AuthInfo.java new file mode 100644 index 0000000000..22facd7a84 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/league/window/AuthInfo.java @@ -0,0 +1,33 @@ +package me.chanjar.weixin.channel.bean.league.window; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 授权信息 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +public class AuthInfo implements Serializable { + + private static final long serialVersionUID = 6265034296219892453L; + /** 授权链接 */ + @JsonProperty("auth_url") + private String authUrl; + + /** 授权路径 */ + @JsonProperty("auth_wxa_path") + private String authWxaPath; + + /** appid */ + @JsonProperty("auth_wxa_appid") + private String authWxaAppid; + + /** 小程序name */ + @JsonProperty("auth_wxa_username") + private String authWxaUsername; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/league/window/AuthInfoResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/league/window/AuthInfoResponse.java new file mode 100644 index 0000000000..941762aecd --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/league/window/AuthInfoResponse.java @@ -0,0 +1,27 @@ +package me.chanjar.weixin.channel.bean.league.window; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; + +/** + * 授权信息响应 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class AuthInfoResponse extends WxChannelBaseResponse { + + private static final long serialVersionUID = 8336998502585278489L; + /** 授权链接信息 */ + @JsonProperty("auth_info") + private AuthInfo authInfo; + + /** 视频号openfinderid */ + @JsonProperty("openfinderid") + private String openfinderid; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/league/window/AuthStatusResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/league/window/AuthStatusResponse.java new file mode 100644 index 0000000000..b42bb0d179 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/league/window/AuthStatusResponse.java @@ -0,0 +1,22 @@ +package me.chanjar.weixin.channel.bean.league.window; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; + +/** + * 授权状态响应 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class AuthStatusResponse extends WxChannelBaseResponse { + + /** 是否授权,0: 未授权, 1: 已授权 */ + @JsonProperty("window_auth_status") + private Integer windowAuthStatus; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/league/window/ProductSearchParam.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/league/window/ProductSearchParam.java new file mode 100644 index 0000000000..e43d450c6d --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/league/window/ProductSearchParam.java @@ -0,0 +1,40 @@ +package me.chanjar.weixin.channel.bean.league.window; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 团长商品搜索参数 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@JsonInclude(JsonInclude.Include.NON_NULL) +public class ProductSearchParam implements Serializable { + + private static final long serialVersionUID = -4771046746777827382L; + /** 团长appid */ + @JsonProperty("appid") + private String appid; + + /** 视频号openfinderid */ + @JsonProperty("openfinderid") + private String openfinderid; + + /** 起始位置(从0开始) */ + @JsonProperty("offset") + private Integer offset; + + /** page_size(默认100, 最大500) */ + @JsonProperty("page_size") + private Integer pageSize; + + /** 默认为false */ + @JsonProperty("need_total_num") + private Boolean needTotalNum; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/league/window/WindowProductListResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/league/window/WindowProductListResponse.java new file mode 100644 index 0000000000..60b5a4a44d --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/league/window/WindowProductListResponse.java @@ -0,0 +1,56 @@ +package me.chanjar.weixin.channel.bean.league.window; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import java.util.List; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; + +/** + * 商品列表响应 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class WindowProductListResponse extends WxChannelBaseResponse { + + private static final long serialVersionUID = -1160519267422259848L; + /** 商品概要列表 */ + @JsonProperty("list") + private List list; + + /** 下一页的位置 */ + @JsonProperty("next_offset") + private Integer nextOffset; + + /** 后面是否还有商品 */ + @JsonProperty("have_more") + private Boolean haveMore; + + /** 商品总数 */ + @JsonProperty("total_num") + private Integer totalNum; + + /** 商品概要列表 */ + @Data + @NoArgsConstructor + public static class ItemKey implements Serializable { + + /** 团长appid */ + @JsonProperty("appid") + private String appid; + + /** 团长商品ID */ + @JsonProperty("product_id") + private String productId; + + /** 团长ID */ + @JsonProperty("head_supplier_id") + private String headSupplierId; + } + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/league/window/WindowProductParam.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/league/window/WindowProductParam.java new file mode 100644 index 0000000000..c0e062fd2a --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/league/window/WindowProductParam.java @@ -0,0 +1,34 @@ +package me.chanjar.weixin.channel.bean.league.window; + + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 团长商品 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@JsonInclude(JsonInclude.Include.NON_NULL) +public class WindowProductParam implements Serializable { + + private static final long serialVersionUID = 363738166094927337L; + /** 团长appid */ + @JsonProperty("appid") + private String appid; + + /** 视频号openfinderid */ + @JsonProperty("openfinderid") + private String openfinderid; + + /** 团长商品ID */ + @JsonProperty("product_id") + private String productId; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/league/window/WindowProductResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/league/window/WindowProductResponse.java new file mode 100644 index 0000000000..071e0b2350 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/league/window/WindowProductResponse.java @@ -0,0 +1,49 @@ +package me.chanjar.weixin.channel.bean.league.window; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; +import me.chanjar.weixin.channel.bean.league.SimpleProductInfo; + +/** + * 商品详情响应 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class WindowProductResponse extends WxChannelBaseResponse { + + private static final long serialVersionUID = -4671578350314241014L; + /** 商品详情 */ + @JsonProperty("product_detail") + private ProductDetail productDetail; + + + /** + * 商品详情 + */ + @Data + @NoArgsConstructor + public static class ProductDetail implements Serializable { + + private static final long serialVersionUID = -6574563870972328273L; + /** 所属小店appid */ + @JsonProperty("appid") + private String appid; + + /** 商品id */ + @JsonProperty("product_id") + private String productId; + + /** 商品信息 */ + @JsonProperty("product_info") + private SimpleProductInfo productInfo; + + + } +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/limit/LimitSku.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/limit/LimitSku.java new file mode 100644 index 0000000000..29ffbf921e --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/limit/LimitSku.java @@ -0,0 +1,30 @@ +package me.chanjar.weixin.channel.bean.limit; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * @author Zeyes + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class LimitSku implements Serializable { + + private static final long serialVersionUID = -1819737633227427482L; + + /** 参与抢购的商品 ID 下,不同规格(SKU)的商品信息 */ + @JsonProperty("sku_id") + private String skuId; + + /** SKU的抢购价格,必须小于原价(原价为1分钱的商品无法创建抢购任务) */ + @JsonProperty("sale_price") + private Integer salePrice; + + /** 参与抢购的商品库存,必须小于等于现有库存 */ + @JsonProperty("sale_stock") + private Integer saleStock; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/limit/LimitTaskAddResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/limit/LimitTaskAddResponse.java new file mode 100644 index 0000000000..35ea00d68d --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/limit/LimitTaskAddResponse.java @@ -0,0 +1,23 @@ +package me.chanjar.weixin.channel.bean.limit; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; + +/** + * @author Zeyes + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class LimitTaskAddResponse extends WxChannelBaseResponse { + + private static final long serialVersionUID = -4742165348862157618L; + + /** 限时抢购任务ID 创建成功后返回 */ + @JsonProperty("task_id") + private String taskId; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/limit/LimitTaskInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/limit/LimitTaskInfo.java new file mode 100644 index 0000000000..aefc4b8136 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/limit/LimitTaskInfo.java @@ -0,0 +1,45 @@ +package me.chanjar.weixin.channel.bean.limit; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import java.util.List; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * @author Zeyes + */ +@Data +@NoArgsConstructor +public class LimitTaskInfo implements Serializable { + + private static final long serialVersionUID = 3032226931637189351L; + + /** 限时抢购任务ID */ + @JsonProperty("task_id") + private String taskId; + + /** 抢购商品ID */ + @JsonProperty("product_id") + private String productId; + + /** 限时抢购任务状态 */ + @JsonProperty("status") + private Integer status; + + /** 限时抢购任务创建时间(秒级时间戳) */ + @JsonProperty("create_time") + private Long createTime; + + /** 限时抢购任务开始时间(秒级时间戳) */ + @JsonProperty("start_time") + private Long startTime; + + /** 限时抢购任务结束时间(秒级时间戳) */ + @JsonProperty("end_time") + private Long endTime; + + /** sku列表 */ + @JsonProperty("limited_discount_skus") + private List skus; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/limit/LimitTaskListParam.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/limit/LimitTaskListParam.java new file mode 100644 index 0000000000..d608c8231e --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/limit/LimitTaskListParam.java @@ -0,0 +1,28 @@ +package me.chanjar.weixin.channel.bean.limit; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import me.chanjar.weixin.channel.bean.base.StreamPageParam; + +/** + * @author Zeyes + */ +@Data +public class LimitTaskListParam extends StreamPageParam { + + private static final long serialVersionUID = -7227161890365102302L; + + + /** 抢购活动状态 */ + @JsonProperty("status") + private Integer status; + + public LimitTaskListParam() { + } + + public LimitTaskListParam(Integer pageSize, String nextKey, Integer status) { + this.pageSize = pageSize; + this.nextKey = nextKey; + this.status = status; + } +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/limit/LimitTaskListResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/limit/LimitTaskListResponse.java new file mode 100644 index 0000000000..688fd158dc --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/limit/LimitTaskListResponse.java @@ -0,0 +1,32 @@ +package me.chanjar.weixin.channel.bean.limit; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.List; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; + +/** + * @author Zeyes + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class LimitTaskListResponse extends WxChannelBaseResponse { + + private static final long serialVersionUID = 3604657299385130217L; + + + /** 限时抢购任务 */ + @JsonProperty("limited_discount_tasks") + private List tasks; + + /** 本次翻页的上下文,用于请求下一页 */ + @JsonProperty("next_key") + private String nextKey; + + /** 商品总数 */ + @JsonProperty("total_num") + private Integer totalNum; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/limit/LimitTaskParam.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/limit/LimitTaskParam.java new file mode 100644 index 0000000000..b89c072944 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/limit/LimitTaskParam.java @@ -0,0 +1,36 @@ +package me.chanjar.weixin.channel.bean.limit; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import java.util.Date; +import java.util.List; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * @author Zeyes + */ +@Data +@NoArgsConstructor +@JsonInclude(JsonInclude.Include.NON_NULL) +public class LimitTaskParam implements Serializable { + + private static final long serialVersionUID = 3885409806249022528L; + + /** 抢购商品ID */ + @JsonProperty("product_id") + private String productId; + + /** 限时抢购任务开始时间(秒级时间戳) */ + @JsonProperty("start_time") + private Date startTime; + + /** 限时抢购任务结束时间(秒级时间戳) */ + @JsonProperty("end_time") + private Date endTime; + + /** sku列表 */ + @JsonProperty("limited_discount_skus") + private List skus; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/live/dashboard/ConversionMetric.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/live/dashboard/ConversionMetric.java new file mode 100644 index 0000000000..96a708be89 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/live/dashboard/ConversionMetric.java @@ -0,0 +1,61 @@ +package me.chanjar.weixin.channel.bean.live.dashboard; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * 转化率 + * + * @author Winnie + */ +@Data +@NoArgsConstructor +public class ConversionMetric implements Serializable { + + private static final long serialVersionUID = - 3411290344181494863L; + + /** + * 商品曝光-点击转化率 + */ + @JsonProperty("product_view_click_conversion_ratio") + private ItemConversionMetric productViewClickConversionRatio; + + /** + * 气泡曝光-点击转化率 + */ + @JsonProperty("bubble_view_click_conversion_ratio") + private ItemConversionMetric bubbleViewClickConversionRatio; + + /** + * 成交转化率 + */ + @JsonProperty("pay_conversion_ratio") + private ItemConversionMetric payConversionRatio; + + /** + * 千次观看成交金额(单位:分) + */ + @JsonProperty("k_view_pay_conversion_ratio") + private ItemConversionMetric kViewPayConversionRatio; + + /** + * 更新时间 + */ + @JsonProperty("update_time") + private Long updateTime; + + /** + * 购物袋商品点击率 + */ + @JsonProperty("product_list_click_conversion_ratio") + private ItemConversionMetric productListClickConversionRatio; + + /** + * 挂车时间(>0 则为挂车) + */ + @JsonProperty("shelftime") + private Long shelftime; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/live/dashboard/DataNode.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/live/dashboard/DataNode.java new file mode 100644 index 0000000000..ab749e0f82 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/live/dashboard/DataNode.java @@ -0,0 +1,26 @@ +package me.chanjar.weixin.channel.bean.live.dashboard; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * 统计数值 + * + * @author Winnie + */ +@Data +@NoArgsConstructor +public class DataNode implements Serializable { + + private static final long serialVersionUID = 3192158546911682577L; + + /** + * 统计数值维度指标 + */ + @JsonProperty("fields") + private Fields fields; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/live/dashboard/DataNodeList.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/live/dashboard/DataNodeList.java new file mode 100644 index 0000000000..6469e48e6e --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/live/dashboard/DataNodeList.java @@ -0,0 +1,32 @@ +package me.chanjar.weixin.channel.bean.live.dashboard; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * 分类下的数据 + * + * @author Winnie + */ +@Data +@NoArgsConstructor +public class DataNodeList implements Serializable { + + private static final long serialVersionUID = - 497502210938812386L; + + /** + * 细分类别的名称,如 "女"、"30-39岁"、"天津市" + */ + @JsonProperty("key") + private String key; + + /** + * 包含具体的统计数值 + */ + @JsonProperty("row") + private DataNode row; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/live/dashboard/DataNodeSecondList.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/live/dashboard/DataNodeSecondList.java new file mode 100644 index 0000000000..7a033378c0 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/live/dashboard/DataNodeSecondList.java @@ -0,0 +1,33 @@ +package me.chanjar.weixin.channel.bean.live.dashboard; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; +import java.util.List; + +/** + * 用户群体下不同分类的统计数据 + * + * @author Winnie + */ +@Data +@NoArgsConstructor +public class DataNodeSecondList implements Serializable { + + private static final long serialVersionUID = 42973481125049275L; + + /** + * 每个分类对象都有一个 key,表示分类的名称,例如 "sex_distribution"、"age_distribution" {@link me.chanjar.weixin.channel.enums.EcProfileDataNodeKey} + */ + @JsonProperty("key") + private String key; + + /** + * 进一步细分该分类下的数据 + */ + @JsonProperty("row") + private List row; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/live/dashboard/DataNodeThirdList.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/live/dashboard/DataNodeThirdList.java new file mode 100644 index 0000000000..7c6424b63f --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/live/dashboard/DataNodeThirdList.java @@ -0,0 +1,33 @@ +package me.chanjar.weixin.channel.bean.live.dashboard; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; +import java.util.List; + +/** + * 不同用户群体的统计数据 + * + * @author Winnie + */ +@Data +@NoArgsConstructor +public class DataNodeThirdList implements Serializable { + + private static final long serialVersionUID = - 7534433586440870881L; + + /** + * 每个对象包含一个 key,表示用户群体的名称,例如 "已成交用户"、"首次购买用户"、"复购用户" + */ + @JsonProperty("key") + private String key; + + /** + * 包含该用户群体下不同分类的统计数据 + */ + @JsonProperty("row") + private List row; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/live/dashboard/Dimension.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/live/dashboard/Dimension.java new file mode 100644 index 0000000000..a07e6670b4 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/live/dashboard/Dimension.java @@ -0,0 +1,38 @@ +package me.chanjar.weixin.channel.bean.live.dashboard; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * 描述时间序列的维度标签 + * + * @author Winnie + */ +@Data +@NoArgsConstructor +public class Dimension implements Serializable { + + private static final long serialVersionUID = - 1879006149576217182L; + + /** + * 维度的类型 {@link me.chanjar.weixin.channel.enums.DimensionType} + */ + @JsonProperty("type") + private Integer type; + + /** + * 维度标签 + */ + @JsonProperty("ux_label") + private String uxLabel; + + /** + * 维度值 + */ + @JsonProperty("dimension_value") + private Long dimensionValue; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/live/dashboard/Ended.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/live/dashboard/Ended.java new file mode 100644 index 0000000000..361a52663b --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/live/dashboard/Ended.java @@ -0,0 +1,44 @@ +package me.chanjar.weixin.channel.bean.live.dashboard; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * 关播内容力数据 + * + * @author Winnie + */ +@Data +@NoArgsConstructor +public class Ended implements Serializable { + + private static final long serialVersionUID = 576815272236922652L; + + /** + * 曝光有效CTR(万分比) + */ + @JsonProperty("recommend_effective_new_watch_2_uv_over_impression_uv") + private EndedIndexItem recommendEffectiveNewWatch2UvOverImpressionUv; + + /** + * 人均看播时长 + */ + @JsonProperty("average_watch_seconds") + private EndedIndexItem averageWatchSeconds; + + /** + * 评论率(万分比) + */ + @JsonProperty("comment_uv_over_new_watch_uv") + private EndedIndexItem commentUvOverNewWatchUv; + + /** + * 点赞率(万分比) + */ + @JsonProperty("like_uv_over_new_watch_uv") + private EndedIndexItem likeUvOverNewWatchUv; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/live/dashboard/EndedIndexItem.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/live/dashboard/EndedIndexItem.java new file mode 100644 index 0000000000..0823819491 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/live/dashboard/EndedIndexItem.java @@ -0,0 +1,38 @@ +package me.chanjar.weixin.channel.bean.live.dashboard; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * 关播内容力指标数据 + * + * @author Winnie + */ +@Data +@NoArgsConstructor +public class EndedIndexItem implements Serializable { + + private static final long serialVersionUID = 7529336638744298238L; + + /** + * 整场直播的指标值 + */ + @JsonProperty("value") + private Long value; + + /** + * 整场直播该指标值打败了 xx% 的主播 + */ + @JsonProperty("percentile") + private Integer percentile; + + /** + * 该指标 7 天中位数 + */ + @JsonProperty("median_7_days") + private Long median7Days; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/live/dashboard/Fields.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/live/dashboard/Fields.java new file mode 100644 index 0000000000..b199f255a5 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/live/dashboard/Fields.java @@ -0,0 +1,38 @@ +package me.chanjar.weixin.channel.bean.live.dashboard; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * 统计数值维度指标数据 + * + * @author Winnie + */ +@Data +@NoArgsConstructor +public class Fields implements Serializable { + + private static final long serialVersionUID = 228387216076265877L; + + /** + * 维度值 + */ + @JsonProperty("dim_key") + private String dimKey; + + /** + * 指标值 + */ + @JsonProperty("dim_val") + private String dimVal; + + /** + * 指标值比例 + */ + @JsonProperty("dim_val_ratio") + private String dimValRatio; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/live/dashboard/ItemConversionMetric.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/live/dashboard/ItemConversionMetric.java new file mode 100644 index 0000000000..6d5142b52f --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/live/dashboard/ItemConversionMetric.java @@ -0,0 +1,44 @@ +package me.chanjar.weixin.channel.bean.live.dashboard; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * 转化率数据 + * + * @author Winnie + */ +@Data +@NoArgsConstructor +public class ItemConversionMetric implements Serializable { + + private static final long serialVersionUID = - 8317027740221390754L; + + /** + * 指标值 + */ + @JsonProperty("metric_value") + private Double metricValue; + + /** + * 较近7天中位数 + */ + @JsonProperty("median_to_recent_7_days") + private Double medianToRecent7Days; + + /** + * 同行对比 + */ + @JsonProperty("within_industry_percentage") + private Double withinIndustryPercentage; + + /** + * 环比数据 + */ + @JsonProperty("quarterly_growth_rate") + private QuarterlyGrowthRate quarterlyGrowthRate; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/live/dashboard/LiveComparisonIndex.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/live/dashboard/LiveComparisonIndex.java new file mode 100644 index 0000000000..84bfd6c226 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/live/dashboard/LiveComparisonIndex.java @@ -0,0 +1,38 @@ +package me.chanjar.weixin.channel.bean.live.dashboard; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * 内容力数据 + * + * @author Winnie + */ +@Data +@NoArgsConstructor +public class LiveComparisonIndex implements Serializable { + + private static final long serialVersionUID = 525214144965479881L; + + /** + * 是否正在直播 + */ + @JsonProperty("is_living") + private Boolean isLiving; + + /** + * 在播数据 + */ + @JsonProperty("on_air") + private OnAir onAir; + + /** + * 关播数据 + */ + @JsonProperty("ended") + private Ended ended; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/live/dashboard/LiveDashboardData.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/live/dashboard/LiveDashboardData.java new file mode 100644 index 0000000000..2568593f6b --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/live/dashboard/LiveDashboardData.java @@ -0,0 +1,38 @@ +package me.chanjar.weixin.channel.bean.live.dashboard; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * 直播大屏数据 + * + * @author Winnie + */ +@Data +@NoArgsConstructor +public class LiveDashboardData implements Serializable { + + private static final long serialVersionUID = 7917049411269553153L; + + /** + * 直播大屏数据实体 + */ + @JsonProperty("live_dashboard_data") + private LiveDashboardData2 liveDashboardData; + + /** + * 直播时长 + */ + @JsonProperty("live_duration") + private Long liveDuration; + + /** + * 直播开始时间 + */ + @JsonProperty("start_time") + private Long startTime; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/live/dashboard/LiveDashboardData2.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/live/dashboard/LiveDashboardData2.java new file mode 100644 index 0000000000..7a66f9ed1f --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/live/dashboard/LiveDashboardData2.java @@ -0,0 +1,38 @@ +package me.chanjar.weixin.channel.bean.live.dashboard; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * 直播大屏实体 + * + * @author Winnie + */ +@Data +@NoArgsConstructor +public class LiveDashboardData2 implements Serializable { + + private static final long serialVersionUID = 3657714024563123097L; + + /** + * 直播基础数据 + */ + @JsonProperty("summary") + private LiveDashboardData2Summary summary; + + /** + * 直播流量渠道 + */ + @JsonProperty("source") + private LiveDashboardData2Source source; + + /** + * 直播观众画像 + */ + @JsonProperty("portrait") + private LiveDashboardData2Portrait portrait; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/live/dashboard/LiveDashboardData2Portrait.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/live/dashboard/LiveDashboardData2Portrait.java new file mode 100644 index 0000000000..964a6936fc --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/live/dashboard/LiveDashboardData2Portrait.java @@ -0,0 +1,33 @@ +package me.chanjar.weixin.channel.bean.live.dashboard; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; +import java.util.List; + +/** + * 直播观众画像 + * + * @author Winnie + */ +@Data +@NoArgsConstructor +public class LiveDashboardData2Portrait implements Serializable { + + private static final long serialVersionUID = - 5603781471063785276L; + + /** + * 在线人数的画像 + */ + @JsonProperty("online_watch_uv") + private List onlineWatchUv; + + /** + * 观看人数的画像 + */ + @JsonProperty("new_watch_uv") + private List newWatchUv; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/live/dashboard/LiveDashboardData2Source.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/live/dashboard/LiveDashboardData2Source.java new file mode 100644 index 0000000000..12c6121bc7 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/live/dashboard/LiveDashboardData2Source.java @@ -0,0 +1,27 @@ +package me.chanjar.weixin.channel.bean.live.dashboard; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; +import java.util.List; + +/** + * 直播流量渠道 + * + * @author Winnie + */ +@Data +@NoArgsConstructor +public class LiveDashboardData2Source implements Serializable { + + private static final long serialVersionUID = 7347276250944913612L; + + /** + * 观看人数的渠道分布 + */ + @JsonProperty("new_watch_uv") + private List newWatchUv; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/live/dashboard/LiveDashboardData2Summary.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/live/dashboard/LiveDashboardData2Summary.java new file mode 100644 index 0000000000..a9c46ea6e3 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/live/dashboard/LiveDashboardData2Summary.java @@ -0,0 +1,80 @@ +package me.chanjar.weixin.channel.bean.live.dashboard; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * 直播基础数据 + * + * @author Winnie + */ +@Data +@NoArgsConstructor +public class LiveDashboardData2Summary implements Serializable { + + private static final long serialVersionUID = - 9029702302333930066L; + + /** + * 观看人数 + */ + @JsonProperty("new_watch_uv") + private Long newWatchUv; + + /** + * 最大在线人数 + */ + @JsonProperty("max_online_watch_uv") + private Long maxOnlineWatchUv; + + /** + * 曝光人数 + */ + @JsonProperty("impression_uv") + private Long impressionUv; + + /** + * 平均观看时长(秒) + */ + @JsonProperty("average_watch_seconds_per_audience") + private Long averageWatchSecondsPerAudience; + + /** + * 新增关注人数 + */ + @JsonProperty("new_follow_uv") + private Long newFollowUv; + + /** + * 新增粉丝团人数 + */ + @JsonProperty("new_fans_club_uv") + private Long newFansClubUv; + + /** + * 评论人数 + */ + @JsonProperty("comment_uv") + private Long commentUv; + + /** + * 打赏人数 + */ + @JsonProperty("reward_uv") + private Long rewardUv; + + /** + * 分享直播间人数 + */ + @JsonProperty("sharing_uv") + private Long sharingUv; + + /** + * 热度 + */ + @JsonProperty("hot_quota") + private Long hotQuota; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/live/dashboard/LiveDataParam.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/live/dashboard/LiveDataParam.java new file mode 100644 index 0000000000..965ed6c8c3 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/live/dashboard/LiveDataParam.java @@ -0,0 +1,30 @@ +package me.chanjar.weixin.channel.bean.live.dashboard; + +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; + +/** + * 获取直播大屏数据请求参数 + * + * @author Winnie + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +@JsonInclude(JsonInclude.Include.NON_NULL) +public class LiveDataParam implements Serializable { + + private static final long serialVersionUID = 6346941931704153857L; + + /** + * 直播唯一ID + */ + @JsonProperty("export_id") + private String exportId; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/live/dashboard/LiveDataResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/live/dashboard/LiveDataResponse.java new file mode 100644 index 0000000000..4a5f4673bd --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/live/dashboard/LiveDataResponse.java @@ -0,0 +1,69 @@ +package me.chanjar.weixin.channel.bean.live.dashboard; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; + +/** + * 获取直播大屏数据响应 + * + * @author Winnie + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class LiveDataResponse extends WxChannelBaseResponse { + + private static final long serialVersionUID = - 8416743234527598719L; + + /** + * 追踪ID,报bug带 + */ + @JsonProperty("trace_id") + private String traceId; + + /** + * 直播大屏基础数据 + */ + @JsonProperty("live_dashboard_data") + private LiveDashboardData liveDashboardData; + + /** + * 内容力数据 + */ + @JsonProperty("live_comparison_index") + private LiveComparisonIndex liveComparisonIndex; + + /** + * 电商数据概要数据 + */ + @JsonProperty("live_ec_data_summary") + private LiveEcDataSummary liveEcDataSummary; + + /** + * 电商转化力数据 + */ + @JsonProperty("live_ec_conversion_metric") + private LiveEcConversionMetric liveEcConversionMetric; + + /** + * 电商画像数据 + */ + @JsonProperty("live_ec_profile") + private LiveEcProfile liveEcProfile; + + /** + * 电商渠道分布 + */ + @JsonProperty("live_distribution_channel") + private LiveDistributionChannel liveDistributionChannel; + + /** + * 电商商品数据 + */ + @JsonProperty("single_live_ec_spu_data_page_v2") + private SingleLiveEcSpuDataPageV2 singleLiveEcSpuDataPageV2; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/live/dashboard/LiveDistChannelSourceStats.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/live/dashboard/LiveDistChannelSourceStats.java new file mode 100644 index 0000000000..9f4d876992 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/live/dashboard/LiveDistChannelSourceStats.java @@ -0,0 +1,81 @@ +package me.chanjar.weixin.channel.bean.live.dashboard; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; +import java.util.List; + +/** + * 流量来源渠道指标数据 + * + * @author Winnie + */ +@Data +@NoArgsConstructor +public class LiveDistChannelSourceStats implements Serializable { + + private static final long serialVersionUID = - 6802106934852140579L; + + /** + * 渠道层级 + */ + @JsonProperty("level") + private Integer level; + + /** + * 来源渠道ID + */ + @JsonProperty("source_channel_id") + private Long sourceChannelId; + + /** + * 流量来源子渠道指标数据统计值 + */ + @JsonProperty("sub_channel_source_stats") + private List subChannelSourceStats; + + /** + * GMV总值(单位:分) + */ + @JsonProperty("gmv") + private Long gmv; + + /** + * UV总值 + */ + @JsonProperty("uv") + private Long uv; + + /** + * 千次看播成交(单位: 分)(GPV) + */ + @JsonProperty("gmv_per_uv") + private Long gmvPerUv; + + /** + * gmv占比 + */ + @JsonProperty("gmv_ratio") + private Double gmvRatio; + + /** + * uv占比 + */ + @JsonProperty("uv_ratio") + private Double uvRatio; + + /** + * 渠道名称 + */ + @JsonProperty("source_channel_name") + private String sourceChannelName; + + /** + * 当前层级pv占总pv的比例 + */ + @JsonProperty("pv_ratio") + private Double pvRatio; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/live/dashboard/LiveDistributionByFlowTypeStat.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/live/dashboard/LiveDistributionByFlowTypeStat.java new file mode 100644 index 0000000000..7b9765f955 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/live/dashboard/LiveDistributionByFlowTypeStat.java @@ -0,0 +1,81 @@ +package me.chanjar.weixin.channel.bean.live.dashboard; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; +import java.util.List; + +/** + * 流量类型、渠道层级的渠道分析统计数据 + * + * @author Winnie + */ +@Data +@NoArgsConstructor +public class LiveDistributionByFlowTypeStat implements Serializable { + + private static final long serialVersionUID = 5885014384803438677L; + + /** + * 渠道流量类型 {@link me.chanjar.weixin.channel.enums.LiveDistributionFlowType} + */ + @JsonProperty("live_dst_channel_type") + private Integer liveDstChannelType; + + /** + * 一级类目渠道来源指标划分 + */ + @JsonProperty("channel_source_stats") + private List channelSourceStats; + + /** + * 在该渠道下的统计值 + */ + @JsonProperty("metric_value") + private Long metricValue; + + /** + * GMV总值(单位:分) + */ + @JsonProperty("gmv") + private Long gmv; + + /** + * UV总值 + */ + @JsonProperty("uv") + private Long uv; + + /** + * 千次看播成交(单位: 分)(GPV) + */ + @JsonProperty("gmv_per_uv") + private Long gmvPerUv; + + /** + * PV总值 + */ + @JsonProperty("pv") + private Long pv; + + /** + * 当前层级pv占总pv的比例 + */ + @JsonProperty("pv_ratio") + private Double pvRatio; + + /** + * uv占比 + */ + @JsonProperty("uv_ratio") + private Double uvRatio; + + /** + * 在该渠道下的统计值比率 + */ + @JsonProperty("metric_value_ratio") + private Double metricValueRatio; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/live/dashboard/LiveDistributionChannel.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/live/dashboard/LiveDistributionChannel.java new file mode 100644 index 0000000000..24eb64d4a2 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/live/dashboard/LiveDistributionChannel.java @@ -0,0 +1,51 @@ +package me.chanjar.weixin.channel.bean.live.dashboard; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; +import java.util.List; + +/** + * 电商渠道分布 + * + * @author Winnie + */ +@Data +@NoArgsConstructor +public class LiveDistributionChannel implements Serializable { + + private static final long serialVersionUID = - 5898886208775573377L; + + /** + * 客户数 + */ + @JsonProperty("audience_count") + private Long audienceCount; + + /** + * 总进入直播数 + */ + @JsonProperty("total_joinlive_count") + private Long totalJoinliveCount; + + /** + * 按场景划分的渠道分析统计值 + */ + @JsonProperty("live_dist_channel_source_by_scene_stats") + private List liveDistChannelSourceBySceneStats; + + /** + * 按照流量类型、渠道层级划分的渠道分析统计数据 + */ + @JsonProperty("live_dist_channel_source_stats") + private List liveDistChannelSourceStats; + + /** + * 数据版本(无实际意义) + */ + @JsonProperty("data_key") + private List dataKey; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/live/dashboard/LiveDistributionSceneStat.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/live/dashboard/LiveDistributionSceneStat.java new file mode 100644 index 0000000000..425d69cca0 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/live/dashboard/LiveDistributionSceneStat.java @@ -0,0 +1,75 @@ +package me.chanjar.weixin.channel.bean.live.dashboard; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; +import java.util.List; + +/** + * 场景的渠道分析统计值 + * + * @author Winnie + */ +@Data +@NoArgsConstructor +public class LiveDistributionSceneStat implements Serializable { + + private static final long serialVersionUID = 4261140121141859416L; + + /** + * 场景类型 {@link me.chanjar.weixin.channel.enums.LiveDistributionSceneType} + */ + @JsonProperty("scene_type") + private Integer sceneType; + + /** + * 该场景下的渠道分析统计值 + */ + @JsonProperty("dist_flow_type_stats") + private List distFlowTypeStats; + + /** + * 指标值总数 + */ + @JsonProperty("metric_value_total") + private Long metricValueTotal; + + /** + * GMV总值(单位:分) + */ + @JsonProperty("gmv") + private Long gmv; + + /** + * UV总值 + */ + @JsonProperty("uv") + private Long uv; + + /** + * 千次看播成交(单位: 分) + */ + @JsonProperty("gmv_per_uv") + private Long gmvPerUv; + + /** + * 指标值 + */ + @JsonProperty("metric_value") + private Long metricValue; + + /** + * 在该渠道下的统计值比率 + */ + @JsonProperty("metric_value_ratio") + private Double metricValueRatio; + + /** + * pv + */ + @JsonProperty("pv") + private Long pv; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/live/dashboard/LiveEcConversionMetric.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/live/dashboard/LiveEcConversionMetric.java new file mode 100644 index 0000000000..e9b92821fe --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/live/dashboard/LiveEcConversionMetric.java @@ -0,0 +1,32 @@ +package me.chanjar.weixin.channel.bean.live.dashboard; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * 电商转化力数据 + * + * @author Winnie + */ +@Data +@NoArgsConstructor +public class LiveEcConversionMetric implements Serializable { + + private static final long serialVersionUID = - 7332281175637902883L; + + /** + * 近10分钟转化率数据 + */ + @JsonProperty("recent_10_min_conversion") + private ConversionMetric recent10MinConversion; + + /** + * 整场直播 + */ + @JsonProperty("whole_live_conversion") + private ConversionMetric wholeLiveConversion; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/live/dashboard/LiveEcDataSummary.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/live/dashboard/LiveEcDataSummary.java new file mode 100644 index 0000000000..d77209da56 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/live/dashboard/LiveEcDataSummary.java @@ -0,0 +1,212 @@ +package me.chanjar.weixin.channel.bean.live.dashboard; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * 电商数据概要数据 + * + * @author Winnie + */ +@Data +@NoArgsConstructor +public class LiveEcDataSummary implements Serializable { + + private static final long serialVersionUID = - 6634047980552575196L; + + /** + * 成交金额(单位:分) + */ + @JsonProperty("total_gmv") + private Long totalGmv; + + /** + * 成交次数 + */ + @JsonProperty("total_pay_pv") + private Long totalPayPv; + + /** + * 成交人数 + */ + @JsonProperty("total_pay_uv") + private Long totalPayUv; + + /** + * 订单创建次数 + */ + @JsonProperty("total_create_pv") + private Long totalCreatePv; + + /** + * 订单创建人数 + */ + @JsonProperty("total_create_uv") + private Long totalCreateUv; + + /** + * 总点击次数 + */ + @JsonProperty("total_clk_pv") + private Long totalClkPv; + + /** + * 总点击人数 + */ + @JsonProperty("total_clk_uv") + private Long totalClkUv; + + /** + * 总曝光次数 + */ + @JsonProperty("total_exp_pv") + private Long totalExpPv; + + /** + * 总曝光人数 + */ + @JsonProperty("total_exp_uv") + private Long totalExpUv; + + /** + * 在线观众数 + */ + @JsonProperty("online_audience_count") + private Long onlineAudienceCount; + + /** + * 累计观众数 + */ + @JsonProperty("cumulative_audience_count") + private Long cumulativeAudienceCount; + + /** + * 新增观众数 + */ + @JsonProperty("new_audience_count") + private Long newAudienceCount; + + /** + * 剩余观众数 + */ + @JsonProperty("leaved_audience_count") + private Long leavedAudienceCount; + + /** + * 观众平均观看秒数 + */ + @JsonProperty("average_watch_seconds_per_audience") + private Long averageWatchSecondsPerAudience; + + /** + * 新增关注数 + */ + @JsonProperty("new_follow_count") + private Long newFollowCount; + + /** + * 新增评论数 + */ + @JsonProperty("new_comment_count") + private Long newCommentCount; + + /** + * 分享直播观众数 + */ + @JsonProperty("share_live_audience_count") + private Long shareLiveAudienceCount; + + /** + * 新粉丝俱乐部数 + */ + @JsonProperty("new_fans_club_count") + private Long newFansClubCount; + + /** + * 退费次数 + */ + @JsonProperty("refund_pv") + private Long refundPv; + + /** + * 退费人数 + */ + @JsonProperty("refund_uv") + private Long refundUv; + + /** + * 退费率 + */ + @JsonProperty("refund_rate") + private Double refundRate; + + /** + * 退款金额(单位:分) + */ + @JsonProperty("refund_amount") + private Long refundAmount; + + /** + * 退费商品件数 + */ + @JsonProperty("refund_product_cnt") + private Long refundProductCnt; + + /** + * 广告累计观众数 + */ + @JsonProperty("ads_cumulative_audience_count") + private Long adsCumulativeAudienceCount; + + /** + * 广告累计观看数 + */ + @JsonProperty("ads_cumulative_watch_count") + private Long adsCumulativeWatchCount; + + /** + * 促销累计观看数 + */ + @JsonProperty("promotion_cumulative_watch_count") + private Long promotionCumulativeWatchCount; + + /** + * 千次看播成交总额 + */ + @JsonProperty("gmv_per_thousand_cumulative_watch_pv") + private Double gmvPerThousandCumulativeWatchPv; + + /** + * 观众成交率 + */ + @JsonProperty("audience_pay_ratio") + private Double audiencePayRatio; + + /** + * 点击成交率 + */ + @JsonProperty("clk_pay_ratio") + private Double clkPayRatio; + + /** + * 新买家人数 + */ + @JsonProperty("new_buyer_uv") + private Long newBuyerUv; + + /** + * 老买家人数 + */ + @JsonProperty("old_buyer_uv") + private Long oldBuyerUv; + + /** + * 客户价格 + */ + @JsonProperty("customer_price") + private Long customerPrice; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/live/dashboard/LiveEcProfile.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/live/dashboard/LiveEcProfile.java new file mode 100644 index 0000000000..76f90c9942 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/live/dashboard/LiveEcProfile.java @@ -0,0 +1,33 @@ +package me.chanjar.weixin.channel.bean.live.dashboard; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; +import java.util.List; + +/** + * 电商画像数据 + * + * @author Winnie + */ +@Data +@NoArgsConstructor +public class LiveEcProfile implements Serializable { + + private static final long serialVersionUID = 1996741772652344438L; + + /** + * 包含不同用户群体的统计数据 + */ + @JsonProperty("profiles") + private List profiles; + + /** + * 总体数据统计信息 + */ + @JsonProperty("totals") + private List totals; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/live/dashboard/LiveItem.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/live/dashboard/LiveItem.java new file mode 100644 index 0000000000..acea012018 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/live/dashboard/LiveItem.java @@ -0,0 +1,32 @@ +package me.chanjar.weixin.channel.bean.live.dashboard; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * 直播列表数据 + * + * @author Winnie + */ +@Data +@NoArgsConstructor +public class LiveItem implements Serializable { + + private static final long serialVersionUID = 6693176992531666035L; + + /** + * 直播唯一ID + */ + @JsonProperty("export_id") + private String exportId; + + /** + * 直播创建时间 + */ + @JsonProperty("create_time") + private Long createTime; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/live/dashboard/LiveListParam.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/live/dashboard/LiveListParam.java new file mode 100644 index 0000000000..3072e990ef --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/live/dashboard/LiveListParam.java @@ -0,0 +1,30 @@ +package me.chanjar.weixin.channel.bean.live.dashboard; + +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; + +/** + * 获取直播大屏直播列表请求参数 + * + * @author Winnie + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +@JsonInclude(JsonInclude.Include.NON_NULL) +public class LiveListParam implements Serializable { + + private static final long serialVersionUID = - 8451283214646387030L; + + /** + * 日期,格式 yyyyMMdd + */ + @JsonProperty("ds") + private Long ds; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/live/dashboard/LiveListResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/live/dashboard/LiveListResponse.java new file mode 100644 index 0000000000..f9b1981dfa --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/live/dashboard/LiveListResponse.java @@ -0,0 +1,41 @@ +package me.chanjar.weixin.channel.bean.live.dashboard; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; + +import java.util.List; + +/** + * 获取直播大屏直播列表响应 + * + * @author Winnie + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class LiveListResponse extends WxChannelBaseResponse { + + private static final long serialVersionUID = - 5062337147636715367L; + + /** + * 追踪ID,报bug带 + */ + @JsonProperty("trace_id") + private String traceId; + + /** + * 直播列表 + */ + @JsonProperty("live_items") + private List liveItems; + + /** + * 是否还有更多的直播 + */ + @JsonProperty("has_more") + private Boolean hasMore; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/live/dashboard/OnAir.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/live/dashboard/OnAir.java new file mode 100644 index 0000000000..29aef35236 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/live/dashboard/OnAir.java @@ -0,0 +1,44 @@ +package me.chanjar.weixin.channel.bean.live.dashboard; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * 在播内容力数据 + * + * @author Winnie + */ +@Data +@NoArgsConstructor +public class OnAir implements Serializable { + + private static final long serialVersionUID = 6354207471314033499L; + + /** + * 曝光有效CTR(万分比) + */ + @JsonProperty("recommend_effective_new_watch_2_uv_over_impression_uv") + private OnAirIndexItem recommendEffectiveNewWatch2UvOverImpressionUv; + + /** + * 人均看播时长 + */ + @JsonProperty("average_watch_seconds") + private OnAirIndexItem averageWatchSeconds; + + /** + * 评论率(万分比) + */ + @JsonProperty("comment_uv_over_new_watch_uv") + private OnAirIndexItem commentUvOverNewWatchUv; + + /** + * 点赞率(万分比) + */ + @JsonProperty("like_uv_over_new_watch_uv") + private OnAirIndexItem likeUvOverNewWatchUv; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/live/dashboard/OnAirIndexItem.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/live/dashboard/OnAirIndexItem.java new file mode 100644 index 0000000000..ebad794338 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/live/dashboard/OnAirIndexItem.java @@ -0,0 +1,56 @@ +package me.chanjar.weixin.channel.bean.live.dashboard; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * 在播内容力指标数据 + * + * @author Winnie + */ +@Data +@NoArgsConstructor +public class OnAirIndexItem implements Serializable { + + private static final long serialVersionUID = - 2988342521964183666L; + + /** + * 描述最近多少分钟的指标值 + */ + @JsonProperty("n") + private Integer n; + + /** + * 最近 n 分钟该指标的值 + */ + @JsonProperty("last_n_mins_value") + private Integer lastNMinsValue; + + /** + * 最近 2n 到 n 分钟该指标的值(用于环比) + */ + @JsonProperty("last_2n_to_n_mins_value") + private Integer last2nToNMinsValue; + + /** + * 最近 n 分钟该指标值打败了 xx% 的在播主播 + */ + @JsonProperty("last_n_mins_percentile") + private Integer lastNMinsPercentile; + + /** + * 整场直播的指标值 + */ + @JsonProperty("value") + private Long value; + + /** + * 整场直播该指标值打败了 xx% 的主播 + */ + @JsonProperty("percentile") + private Integer percentile; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/live/dashboard/Point.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/live/dashboard/Point.java new file mode 100644 index 0000000000..7f11086442 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/live/dashboard/Point.java @@ -0,0 +1,32 @@ +package me.chanjar.weixin.channel.bean.live.dashboard; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * 数据点 + * + * @author Winnie + */ +@Data +@NoArgsConstructor +public class Point implements Serializable { + + private static final long serialVersionUID = 3332256418933163389L; + + /** + * 时间戳 + */ + @JsonProperty("ts") + private Long ts; + + /** + * 指标值 + */ + @JsonProperty("value") + private Long value; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/live/dashboard/QuarterlyGrowthRate.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/live/dashboard/QuarterlyGrowthRate.java new file mode 100644 index 0000000000..0acfdd7d18 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/live/dashboard/QuarterlyGrowthRate.java @@ -0,0 +1,32 @@ +package me.chanjar.weixin.channel.bean.live.dashboard; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * 转化率环比数据 + * + * @author Winnie + */ +@Data +@NoArgsConstructor +public class QuarterlyGrowthRate implements Serializable { + + private static final long serialVersionUID = 1683118806978367016L; + + /** + * 环比(近10分钟转化率数据才有) + */ + @JsonProperty("value") + private Long value; + + /** + * 环比是否是有效值(如果是false说明分母是0) + */ + @JsonProperty("is_valid") + private Boolean isValid; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/live/dashboard/Series.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/live/dashboard/Series.java new file mode 100644 index 0000000000..570c1b1b0d --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/live/dashboard/Series.java @@ -0,0 +1,51 @@ +package me.chanjar.weixin.channel.bean.live.dashboard; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; +import java.util.List; + +/** + * 维度标签的时间序列(与指标的类型无关) + * + * @author Winnie + */ +@Data +@NoArgsConstructor +public class Series implements Serializable { + + private static final long serialVersionUID = 507937573085880287L; + + /** + * 数据点 + */ + @JsonProperty("points") + private List points; + + /** + * 描述时间序列的维度标签 + */ + @JsonProperty("dimensions") + private List dimensions; + + /** + * 每个数据点描述的时间长度(秒) + */ + @JsonProperty("step") + private Long step; + + /** + * 该时间序列的起始时间戳 + */ + @JsonProperty("begin_ts") + private Long beginTs; + + /** + * 该时间序列的结束时间戳 + */ + @JsonProperty("end_ts") + private Long endTs; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/live/dashboard/SingleLiveEcSpuDataPageV2.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/live/dashboard/SingleLiveEcSpuDataPageV2.java new file mode 100644 index 0000000000..5cdb3a97d0 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/live/dashboard/SingleLiveEcSpuDataPageV2.java @@ -0,0 +1,39 @@ +package me.chanjar.weixin.channel.bean.live.dashboard; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; +import java.util.List; + +/** + * 电商商品数据 + * + * @author Winnie + */ +@Data +@NoArgsConstructor +public class SingleLiveEcSpuDataPageV2 implements Serializable { + + private static final long serialVersionUID = - 761977668198342583L; + + /** + * 商品明细数据列表 + */ + @JsonProperty("spu_data_list") + private List spuDataList; + + /** + * spu_data_list 的总长度 + */ + @JsonProperty("total_cnt") + private Integer totalCnt; + + /** + * 数据版本(无实际意义) + */ + @JsonProperty("data_key") + private List dataKey; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/live/dashboard/SpuBaseData.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/live/dashboard/SpuBaseData.java new file mode 100644 index 0000000000..b86279bdab --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/live/dashboard/SpuBaseData.java @@ -0,0 +1,68 @@ +package me.chanjar.weixin.channel.bean.live.dashboard; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * 商品基础数据 + * + * @author Winnie + */ +@Data +@NoArgsConstructor +public class SpuBaseData implements Serializable { + + private static final long serialVersionUID = 3170611962212344198L; + + /** + * 店铺商品id + */ + @JsonProperty("src_spu_id") + private String srcSpuId; + + /** + * 店铺id + */ + @JsonProperty("src") + private Long src; + + /** + * 商品名称 + */ + @JsonProperty("spu_name") + private String spuName; + + /** + * 商品id + */ + @JsonProperty("spu_id") + private Long spuId; + + /** + * 商品小图 + */ + @JsonProperty("thumb_url") + private String thumbUrl; + + /** + * 商品价格 + */ + @JsonProperty("price") + private Long price; + + /** + * 店铺名称 + */ + @JsonProperty("src_name") + private String srcName; + + /** + * 库存 + */ + @JsonProperty("stock") + private Long stock; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/live/dashboard/SpuData.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/live/dashboard/SpuData.java new file mode 100644 index 0000000000..41a44a926d --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/live/dashboard/SpuData.java @@ -0,0 +1,350 @@ +package me.chanjar.weixin.channel.bean.live.dashboard; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * 商品明细数据 + * + * @author Winnie + */ +@Data +@NoArgsConstructor +public class SpuData implements Serializable { + + private static final long serialVersionUID = 7409791549917863816L; + + /** + * 商品基础数据 + */ + @JsonProperty("base_data") + private SpuBaseData baseData; + + /** + * 商品曝光人数 + */ + @JsonProperty("exp_uv") + private Long expUv; + + /** + * 商品曝光次数 + */ + @JsonProperty("exp_pv") + private Long expPv; + + /** + * 商品粉丝曝光人数 + */ + @JsonProperty("fans_exp_uv") + private Long fansExpUv; + + /** + * 商品粉丝曝光次数 + */ + @JsonProperty("fans_exp_pv") + private Long fansExpPv; + + /** + * 商品非粉丝曝光人数 + */ + @JsonProperty("non_fans_exp_uv") + private Long nonFansExpUv; + + /** + * 商品非粉丝曝光次数 + */ + @JsonProperty("non_fans_exp_pv") + private Long nonFansExpPv; + + /** + * 商品新客户曝光人数 + */ + @JsonProperty("new_customer_exp_uv") + private Long newCustomerExpUv; + + /** + * 商品新客户曝光次数 + */ + @JsonProperty("new_customer_exp_pv") + private Long newCustomerExpPv; + + /** + * 商品老客户曝光人数 + */ + @JsonProperty("repeated_customer_exp_uv") + private Long repeatedCustomerExpUv; + + /** + * 商品老客户曝光次数 + */ + @JsonProperty("repeated_customer_exp_pv") + private Long repeatedCustomerExpPv; + + /** + * 商品点击人数 + */ + @JsonProperty("clk_uv") + private Long clkUv; + + /** + * 商品点击次数 + */ + @JsonProperty("clk_pv") + private Long clkPv; + + /** + * 商品新客户点击人数 + */ + @JsonProperty("new_customer_clk_uv") + private Long newCustomerClkUv; + + /** + * 商品新客户点击次数 + */ + @JsonProperty("new_customer_clk_pv") + private Long newCustomerClkPv; + + /** + * 商品老客户点击人数 + */ + @JsonProperty("repeated_customer_clk_uv") + private Long repeatedCustomerClkUv; + + /** + * 商品老客户点击次数 + */ + @JsonProperty("repeated_customer_clk_pv") + private Long repeatedCustomerClkPv; + + /** + * 商品粉丝点击人数 + */ + @JsonProperty("fans_clk_uv") + private Long fansClkUv; + + /** + * 商品粉丝点击次数 + */ + @JsonProperty("fans_clk_pv") + private Long fansClkPv; + + /** + * 商品非粉丝点击人数 + */ + @JsonProperty("non_fans_clk_uv") + private Long nonFansClkUv; + + /** + * 商品非粉丝点击次数 + */ + @JsonProperty("non_fans_clk_pv") + private Long nonFansClkPv; + + /** + * 商品分享人数 + */ + @JsonProperty("share_uv") + private Long shareUv; + + /** + * 商品分享次数 + */ + @JsonProperty("share_pv") + private Long sharePv; + + /** + * 商品曝光点击率 + */ + @JsonProperty("exp_clk_ratio") + private Double expClkRatio; + + /** + * 商品点击成交率 + */ + @JsonProperty("clk_pay_ratio") + private Double clkPayRatio; + + /** + * 商品成交金额(单位:分) + */ + @JsonProperty("gmv") + private Long gmv; + + /** + * 商品成交订单数 + */ + @JsonProperty("pay_pv") + private Long payPv; + + /** + * 商品成交人数 + */ + @JsonProperty("pay_uv") + private Long payUv; + + /** + * 商品粉丝成交订单数 + */ + @JsonProperty("fans_pay_pv") + private Long fansPayPv; + + /** + * 商品粉丝成交人数 + */ + @JsonProperty("fans_pay_uv") + private Long fansPayUv; + + /** + * 商品非粉丝成交订单数 + */ + @JsonProperty("non_fans_pay_pv") + private Long nonFansPayPv; + + /** + * 商品非粉丝成交人数 + */ + @JsonProperty("non_fans_pay_uv") + private Long nonFansPayUv; + + /** + * 商品新客户成交次数 + */ + @JsonProperty("new_customer_pay_pv") + private Long newCustomerPayPv; + + /** + * 商品新客户成交人数 + */ + @JsonProperty("new_customer_pay_uv") + private Long newCustomerPayUv; + + /** + * 商品老客户成交次数 + */ + @JsonProperty("repeated_customer_pay_pv") + private Long repeatedCustomerPayPv; + + /** + * 商品老客户成交人数 + */ + @JsonProperty("repeated_customer_pay_uv") + private Long repeatedCustomerPayUv; + + /** + * 商品退款人数 + */ + @JsonProperty("refund_uv") + private Long refundUv; + + /** + * 商品退款订单数 + */ + @JsonProperty("refund_pv") + private Long refundPv; + + /** + * 商品退款金额(单位:分) + */ + @JsonProperty("refund_amount") + private Long refundAmount; + + /** + * 商品订单创建人数 + */ + @JsonProperty("create_uv") + private Long createUv; + + /** + * 商品订单创建次数 + */ + @JsonProperty("create_pv") + private Long createPv; + + /** + * 商品粉丝订单创建人数 + */ + @JsonProperty("fans_create_uv") + private Long fansCreateUv; + + /** + * 商品粉丝订单创建次数 + */ + @JsonProperty("fans_create_pv") + private Long fansCreatePv; + + /** + * 商品非粉丝订单创建人数 + */ + @JsonProperty("non_fans_create_uv") + private Long nonFansCreateUv; + + /** + * 商品非粉丝订单创建次数 + */ + @JsonProperty("non_fans_create_pv") + private Long nonFansCreatePv; + + /** + * 商品新客户订单创建人数 + */ + @JsonProperty("new_customer_create_uv") + private Long newCustomerCreateUv; + + /** + * 商品新客户订单创建次数 + */ + @JsonProperty("new_customer_create_pv") + private Long newCustomerCreatePv; + + /** + * 商品老客户订单创建人数 + */ + @JsonProperty("repeated_customer_create_uv") + private Long repeatedCustomerCreateUv; + + /** + * 商品老客户订单创建次数 + */ + @JsonProperty("repeated_customer_create_pv") + private Long repeatedCustomerCreatePv; + + /** + * 商品库存 + */ + @JsonProperty("stock") + private Long stock; + + /** + * 商品退费率 + */ + @JsonProperty("refund_rate") + private Double refundRate; + + /** + * 商品完成订单数 + */ + @JsonProperty("finish_pv") + private Long finishPv; + + /** + * 商品未完成订单数 + */ + @JsonProperty("no_finish_pv") + private Long noFinishPv; + + /** + * 商品新客户转换率 + */ + @JsonProperty("new_customer_conversion_rate") + private Double newCustomerConversionRate; + + /** + * 商品讲解数 + */ + @JsonProperty("explanation_count") + private Long explanationCount; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/live/dashboard/SubLiveDistChannelSourceStats.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/live/dashboard/SubLiveDistChannelSourceStats.java new file mode 100644 index 0000000000..7d183b738b --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/live/dashboard/SubLiveDistChannelSourceStats.java @@ -0,0 +1,92 @@ +package me.chanjar.weixin.channel.bean.live.dashboard; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * 流量来源子渠道指标数据 + * + * @author Winnie + */ +@Data +@NoArgsConstructor +public class SubLiveDistChannelSourceStats implements Serializable { + + private static final long serialVersionUID = - 5279814435684116105L; + + /** + * 渠道层级 + */ + @JsonProperty("level") + private Integer level; + + /** + * 来源渠道ID + */ + @JsonProperty("source_channel_id") + private Long sourceChannelId; + + /** + * 在该渠道下的统计值 + */ + @JsonProperty("metric_value") + private Long metricValue; + + /** + * GMV总值(单位:分) + */ + @JsonProperty("gmv") + private Long gmv; + + /** + * UV总值 + */ + @JsonProperty("uv") + private Long uv; + + /** + * 千次看播成交(单位: 分) + */ + @JsonProperty("gmv_per_uv") + private Long gmvPerUv; + + /** + * gmv占比 + */ + @JsonProperty("gmv_ratio") + private Double gmvRatio; + + /** + * uv占比 + */ + @JsonProperty("uv_ratio") + private Double uvRatio; + + /** + * 在该渠道下的统计值比率 + */ + @JsonProperty("metric_value_ratio") + private Double metricValueRatio; + + /** + * 渠道名称 + */ + @JsonProperty("source_channel_name") + private String sourceChannelName; + + /** + * pv + */ + @JsonProperty("pv") + private Long pv; + + /** + * 当前层级pv占总pv的比例 + */ + @JsonProperty("pv_ratio") + private Double pvRatio; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/message/SessionMessage.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/message/SessionMessage.java new file mode 100644 index 0000000000..9b97cbf09d --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/message/SessionMessage.java @@ -0,0 +1,27 @@ +package me.chanjar.weixin.channel.bean.message; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.message.WxChannelMessage; + +/** + * 会话消息 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +@JacksonXmlRootElement(localName = "xml") +public class SessionMessage extends WxChannelMessage { + + private static final long serialVersionUID = -429381568555605309L; + + @JsonProperty("SessionFrom") + @JacksonXmlProperty(localName = "SessionFrom") + private String from; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/message/after/AfterSaleMessage.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/message/after/AfterSaleMessage.java new file mode 100644 index 0000000000..52beec7932 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/message/after/AfterSaleMessage.java @@ -0,0 +1,27 @@ +package me.chanjar.weixin.channel.bean.message.after; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.message.WxChannelMessage; + +/** + * 售后消息 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +@JacksonXmlRootElement(localName = "xml") +public class AfterSaleMessage extends WxChannelMessage { + + private static final long serialVersionUID = -7263404451639198126L; + /** 状态信息 */ + @JsonProperty("finder_shop_aftersale_status_update") + @JacksonXmlProperty(localName = "finder_shop_aftersale_status_update") + private AfterSaleStatusInfo info; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/message/after/AfterSaleStatusInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/message/after/AfterSaleStatusInfo.java new file mode 100644 index 0000000000..06fd349da8 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/message/after/AfterSaleStatusInfo.java @@ -0,0 +1,33 @@ +package me.chanjar.weixin.channel.bean.message.after; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; +import java.io.Serializable; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 售后信息 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +public class AfterSaleStatusInfo implements Serializable { + + private static final long serialVersionUID = -7309656340583314591L; + /** 售后单号 */ + @JsonProperty("after_sale_order_id") + @JacksonXmlProperty(localName = "after_sale_order_id") + private String afterSaleOrderId; + + /** 售后单状态 */ + @JsonProperty("status") + @JacksonXmlProperty(localName = "status") + private String status; + + /** 订单id */ + @JsonProperty("order_id") + @JacksonXmlProperty(localName = "order_id") + private String orderId; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/message/after/ComplaintInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/message/after/ComplaintInfo.java new file mode 100644 index 0000000000..adb0b7b392 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/message/after/ComplaintInfo.java @@ -0,0 +1,33 @@ +package me.chanjar.weixin.channel.bean.message.after; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; +import java.io.Serializable; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 纠纷信息 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +public class ComplaintInfo implements Serializable { + + private static final long serialVersionUID = 3988395560953978239L; + /** 纠纷单号 */ + @JsonProperty("complaint_id") + @JacksonXmlProperty(localName = "complaint_id") + private String complaintId; + + /** 小店售后单号 */ + @JsonProperty("after_sale_order_id") + @JacksonXmlProperty(localName = "after_sale_order_id") + private String afterSaleOrderId; + + /** 纠纷单状态 */ + @JsonProperty("status") + @JacksonXmlProperty(localName = "status") + private Integer status; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/message/after/ComplaintMessage.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/message/after/ComplaintMessage.java new file mode 100644 index 0000000000..e10a9b365a --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/message/after/ComplaintMessage.java @@ -0,0 +1,28 @@ +package me.chanjar.weixin.channel.bean.message.after; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.message.WxChannelMessage; + +/** + * 纠纷消息 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +@JacksonXmlRootElement(localName = "xml") +public class ComplaintMessage extends WxChannelMessage { + + private static final long serialVersionUID = 5358093415172409157L; + /** 状态信息 */ + @JsonProperty("finder_shop_complaint") + @JacksonXmlProperty(localName = "finder_shop_complaint") + private ComplaintInfo info; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/message/coupon/CouponActionInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/message/coupon/CouponActionInfo.java new file mode 100644 index 0000000000..f7a55ce0fb --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/message/coupon/CouponActionInfo.java @@ -0,0 +1,48 @@ +package me.chanjar.weixin.channel.bean.message.coupon; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; +import java.io.Serializable; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 优惠券操作消息 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +public class CouponActionInfo implements Serializable { + + private static final long serialVersionUID = -4456716511656569552L; + /** 优惠券ID */ + @JsonProperty("coupon_id") + @JacksonXmlProperty(localName = "coupon_id") + private String couponId; + + /** 领券时间 */ + @JsonProperty("create_time") + @JacksonXmlProperty(localName = "create_time") + private String createTime; + + /** 删除时间 */ + @JsonProperty("delete_time") + @JacksonXmlProperty(localName = "delete_time") + private String deleteTime; + + /** 过期时间 */ + @JsonProperty("expire_time") + @JacksonXmlProperty(localName = "expire_time") + private String expireTime; + + /** 更新时间 */ + @JsonProperty("change_time") + @JacksonXmlProperty(localName = "change_time") + private String changeTime; + + /** 作废时间 */ + @JsonProperty("invalid_time") + @JacksonXmlProperty(localName = "invalid_time") + private String invalidTime; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/message/coupon/CouponActionMessage.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/message/coupon/CouponActionMessage.java new file mode 100644 index 0000000000..7433b7a6c2 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/message/coupon/CouponActionMessage.java @@ -0,0 +1,29 @@ +package me.chanjar.weixin.channel.bean.message.coupon; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.message.WxChannelMessage; + + +/** + * 卡券操作 消息 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +@JacksonXmlRootElement(localName = "xml") +public class CouponActionMessage extends WxChannelMessage { + + private static final long serialVersionUID = 4910461800721504462L; + /** 优惠券信息 */ + @JsonProperty("coupon_info") + @JacksonXmlProperty(localName = "coupon_info") + private CouponActionInfo couponInfo; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/message/coupon/CouponReceiveMessage.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/message/coupon/CouponReceiveMessage.java new file mode 100644 index 0000000000..448d815a58 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/message/coupon/CouponReceiveMessage.java @@ -0,0 +1,60 @@ +package me.chanjar.weixin.channel.bean.message.coupon; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement; +import java.util.Map; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.message.WxChannelMessage; + + +/** + * 用户领券 消息 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +@JacksonXmlRootElement(localName = "xml") +public class CouponReceiveMessage extends WxChannelMessage { + + private static final long serialVersionUID = 5121347165246528730L; + /** 领取的优惠券ID */ + @JsonProperty("coupon_id") + @JacksonXmlProperty(localName = "coupon_id") + private String couponId; + + /** 生成的用户券ID */ + @JsonProperty("user_coupon_id") + @JacksonXmlProperty(localName = "user_coupon_id") + private String userCouponId; + + /** 领券时间 */ + @JsonProperty("receive_time") + @JacksonXmlProperty(localName = "receive_time") + private String receiveTime; + + @JsonProperty("receive_info") + @JacksonXmlProperty(localName = "receive_info") + private void unpackNameFromNestedObject(Map map) { + if (map == null) { + return; + } + Object obj = null; + obj = map.get("coupon_id"); + if (obj != null) { + this.couponId = (obj instanceof String ? (String) obj : String.valueOf(obj)); + } + obj = map.get("user_coupon_id"); + if (obj != null) { + this.userCouponId = (obj instanceof String ? (String) obj : String.valueOf(obj)); + } + obj = map.get("receive_time"); + if (obj != null) { + this.receiveTime = (obj instanceof String ? (String) obj : String.valueOf(obj)); + } + } +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/message/coupon/UserCouponActionInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/message/coupon/UserCouponActionInfo.java new file mode 100644 index 0000000000..1356c47fca --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/message/coupon/UserCouponActionInfo.java @@ -0,0 +1,45 @@ +package me.chanjar.weixin.channel.bean.message.coupon; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; +import java.io.Serializable; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 用户优惠券操作消息 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +public class UserCouponActionInfo implements Serializable { + + private static final long serialVersionUID = -5948836918972669529L; + /** 优惠券ID */ + @JsonProperty("coupon_id") + @JacksonXmlProperty(localName = "coupon_id") + private String couponId; + + /** 用户券ID */ + @JsonProperty("user_coupon_id") + @JacksonXmlProperty(localName = "user_coupon_id") + private String userCouponId; + + /** 过期时间 */ + @JsonProperty("expire_time") + @JacksonXmlProperty(localName = "expire_time") + private String expireTime; + + /** 使用时间 */ + @JsonProperty("use_time") + @JacksonXmlProperty(localName = "use_time") + private String useTime; + + /** 返还时间 */ + @JsonProperty("unuse_time") + @JacksonXmlProperty(localName = "unuse_time") + private String unuseTime; + + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/message/coupon/UserCouponExpireMessage.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/message/coupon/UserCouponExpireMessage.java new file mode 100644 index 0000000000..26370e5142 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/message/coupon/UserCouponExpireMessage.java @@ -0,0 +1,29 @@ +package me.chanjar.weixin.channel.bean.message.coupon; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.message.WxChannelMessage; + + +/** + * 用户卡券过期 消息 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +@JacksonXmlRootElement(localName = "xml") +public class UserCouponExpireMessage extends WxChannelMessage { + + private static final long serialVersionUID = -2557475297107588372L; + /** 用户优惠券信息 */ + @JsonProperty("user_coupon_info") + @JacksonXmlProperty(localName = "user_coupon_info") + private UserCouponActionInfo userCouponInfo; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/message/coupon/UserCouponUseMessage.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/message/coupon/UserCouponUseMessage.java new file mode 100644 index 0000000000..7b436743c3 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/message/coupon/UserCouponUseMessage.java @@ -0,0 +1,28 @@ +package me.chanjar.weixin.channel.bean.message.coupon; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.message.WxChannelMessage; + + +/** + * 用户卡券使用 消息 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +@JacksonXmlRootElement(localName = "xml") +public class UserCouponUseMessage extends WxChannelMessage { + + private static final long serialVersionUID = -1051142666438578628L; + /** 用户优惠券信息 */ + @JsonProperty("user_info") + @JacksonXmlProperty(localName = "user_info") + private UserCouponActionInfo userCouponInfo; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/message/fund/AccountNotifyMessage.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/message/fund/AccountNotifyMessage.java new file mode 100644 index 0000000000..b5a02ac834 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/message/fund/AccountNotifyMessage.java @@ -0,0 +1,27 @@ +package me.chanjar.weixin.channel.bean.message.fund; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.message.WxChannelMessage; + +/** + * 账户变更通知 消息 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +@JacksonXmlRootElement(localName = "xml") +public class AccountNotifyMessage extends WxChannelMessage { + + private static final long serialVersionUID = 3846692537729725664L; + /** 账户信息 */ + @JsonProperty("account_info") + @JacksonXmlProperty(localName = "account_info") + private BankNotifyInfo accountInfo; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/message/fund/BankNotifyInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/message/fund/BankNotifyInfo.java new file mode 100644 index 0000000000..44ef398f8b --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/message/fund/BankNotifyInfo.java @@ -0,0 +1,24 @@ +package me.chanjar.weixin.channel.bean.message.fund; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; +import java.io.Serializable; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 账户信息 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +public class BankNotifyInfo implements Serializable { + + private static final long serialVersionUID = 4192569196686180014L; + /** 结算账户变更事件, 1.修改结算账户 */ + @JsonProperty("event") + @JacksonXmlProperty(localName = "event") + private Integer event; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/message/fund/QrNotifyInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/message/fund/QrNotifyInfo.java new file mode 100644 index 0000000000..83b466f07e --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/message/fund/QrNotifyInfo.java @@ -0,0 +1,34 @@ +package me.chanjar.weixin.channel.bean.message.fund; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; +import java.io.Serializable; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 提现二维码回调 消息 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +public class QrNotifyInfo implements Serializable { + + private static final long serialVersionUID = 2470016408300157273L; + /** 二维码ticket */ + @JsonProperty("ticket") + @JacksonXmlProperty(localName = "ticket") + private String ticket; + + /** 二维码状态,1.已确认 2.已取消 3.已失效 4.已扫码 */ + @JsonProperty("status") + @JacksonXmlProperty(localName = "status") + private Integer status; + + /** 扫码者身份, 0.非管理员 1.管理员 */ + @JsonProperty("scan_user_type") + @JacksonXmlProperty(localName = "scan_user_type") + private Integer scanUserType; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/message/fund/QrNotifyMessage.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/message/fund/QrNotifyMessage.java new file mode 100644 index 0000000000..56e906a641 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/message/fund/QrNotifyMessage.java @@ -0,0 +1,27 @@ +package me.chanjar.weixin.channel.bean.message.fund; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.message.WxChannelMessage; + +/** + * 提现二维码回调 消息 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +@JacksonXmlRootElement(localName = "xml") +public class QrNotifyMessage extends WxChannelMessage { + + private static final long serialVersionUID = -4705790895359679423L; + /** 账户信息 */ + @JsonProperty("qrcode_info") + @JacksonXmlProperty(localName = "qrcode_info") + private QrNotifyInfo qrcodeInfo; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/message/fund/WithdrawNotifyInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/message/fund/WithdrawNotifyInfo.java new file mode 100644 index 0000000000..810f40c95c --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/message/fund/WithdrawNotifyInfo.java @@ -0,0 +1,29 @@ +package me.chanjar.weixin.channel.bean.message.fund; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; +import java.io.Serializable; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 提现通知信息 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +public class WithdrawNotifyInfo implements Serializable { + + private static final long serialVersionUID = 2987401114254821956L; + /** 1.发起提现,生成二维码 2.扫码验证成功,申请提现 3.提现成功 4.提现失败 */ + @JsonProperty("event") + @JacksonXmlProperty(localName = "event") + private Integer event; + + /** 提现单号 */ + @JsonProperty("withdraw_id") + @JacksonXmlProperty(localName = "withdraw_id") + private String withdrawId; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/message/fund/WithdrawNotifyMessage.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/message/fund/WithdrawNotifyMessage.java new file mode 100644 index 0000000000..ff45e73ec6 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/message/fund/WithdrawNotifyMessage.java @@ -0,0 +1,27 @@ +package me.chanjar.weixin.channel.bean.message.fund; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.message.WxChannelMessage; + +/** + * 账户变更通知 消息 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +@JacksonXmlRootElement(localName = "xml") +public class WithdrawNotifyMessage extends WxChannelMessage { + + private static final long serialVersionUID = -2504086242143523430L; + /** 账户信息 */ + @JsonProperty("withdraw_info") + @JacksonXmlProperty(localName = "withdraw_info") + private WithdrawNotifyInfo withdrawInfo; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/message/order/OrderCancelInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/message/order/OrderCancelInfo.java new file mode 100644 index 0000000000..8ff3ead54e --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/message/order/OrderCancelInfo.java @@ -0,0 +1,24 @@ +package me.chanjar.weixin.channel.bean.message.order; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +/** + * 订单取消信息 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class OrderCancelInfo extends OrderIdInfo { + + private static final long serialVersionUID = -8022876997578127873L; + /** 1:用户取消;2:超时取消;3:全部商品售后完成,订单取消;4:超卖商家取消订单 */ + @JsonProperty("cancel_type") + @JacksonXmlProperty(localName = "cancel_type") + private Integer cancelType; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/message/order/OrderCancelMessage.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/message/order/OrderCancelMessage.java new file mode 100644 index 0000000000..8e6b33c2ee --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/message/order/OrderCancelMessage.java @@ -0,0 +1,27 @@ +package me.chanjar.weixin.channel.bean.message.order; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.message.WxChannelMessage; + +/** + * 订单取消消息 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +@JacksonXmlRootElement(localName = "xml") +public class OrderCancelMessage extends WxChannelMessage { + + private static final long serialVersionUID = 5389546516473919310L; + /** 订单信息 */ + @JsonProperty("order_info") + @JacksonXmlProperty(localName = "order_info") + private OrderCancelInfo orderInfo; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/message/order/OrderConfirmInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/message/order/OrderConfirmInfo.java new file mode 100644 index 0000000000..bd212092a5 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/message/order/OrderConfirmInfo.java @@ -0,0 +1,24 @@ +package me.chanjar.weixin.channel.bean.message.order; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +/** + * 订单确认收货信息 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class OrderConfirmInfo extends OrderIdInfo { + + private static final long serialVersionUID = -2569494642832261346L; + /** 1:用户确认收货;2:超时自动确认收货 */ + @JsonProperty("confirm_type") + @JacksonXmlProperty(localName = "confirm_type") + private Integer confirmType; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/message/order/OrderConfirmMessage.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/message/order/OrderConfirmMessage.java new file mode 100644 index 0000000000..dda35041b2 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/message/order/OrderConfirmMessage.java @@ -0,0 +1,27 @@ +package me.chanjar.weixin.channel.bean.message.order; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.message.WxChannelMessage; + +/** + * 订单确认收货消息 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +@JacksonXmlRootElement(localName = "xml") +public class OrderConfirmMessage extends WxChannelMessage { + + private static final long serialVersionUID = 4219477394934480425L; + /** 订单信息 */ + @JsonProperty("order_info") + @JacksonXmlProperty(localName = "order_info") + private OrderConfirmInfo orderInfo; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/message/order/OrderDeliveryInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/message/order/OrderDeliveryInfo.java new file mode 100644 index 0000000000..ca3d26736a --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/message/order/OrderDeliveryInfo.java @@ -0,0 +1,25 @@ +package me.chanjar.weixin.channel.bean.message.order; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +/** + * 订单发货信息 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class OrderDeliveryInfo extends OrderIdInfo { + + private static final long serialVersionUID = 117962754344887556L; + /** 0:尚未全部发货;1:全部商品发货完成 */ + @JsonProperty("finish_delivery") + @JacksonXmlProperty(localName = "finish_delivery") + private Integer finishDelivery; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/message/order/OrderDeliveryMessage.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/message/order/OrderDeliveryMessage.java new file mode 100644 index 0000000000..25d79e2c4d --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/message/order/OrderDeliveryMessage.java @@ -0,0 +1,27 @@ +package me.chanjar.weixin.channel.bean.message.order; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.message.WxChannelMessage; + +/** + * 订单发货消息 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +@JacksonXmlRootElement(localName = "xml") +public class OrderDeliveryMessage extends WxChannelMessage { + + private static final long serialVersionUID = -1440834047566984402L; + /** 订单信息 */ + @JsonProperty("order_info") + @JacksonXmlProperty(localName = "order_info") + private OrderDeliveryInfo orderInfo; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/message/order/OrderExtInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/message/order/OrderExtInfo.java new file mode 100644 index 0000000000..b4986f35c4 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/message/order/OrderExtInfo.java @@ -0,0 +1,24 @@ +package me.chanjar.weixin.channel.bean.message.order; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +/** + * 订单其他信息 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class OrderExtInfo extends OrderIdInfo { + + private static final long serialVersionUID = 4723533858047219828L; + /** 类型 1:联盟佣金信息 */ + @JsonProperty("type") + @JacksonXmlProperty(localName = "type") + private Integer type; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/message/order/OrderExtMessage.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/message/order/OrderExtMessage.java new file mode 100644 index 0000000000..c5ede6c6bd --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/message/order/OrderExtMessage.java @@ -0,0 +1,27 @@ +package me.chanjar.weixin.channel.bean.message.order; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.message.WxChannelMessage; + +/** + * 订单状态消息 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +@JacksonXmlRootElement(localName = "xml") +public class OrderExtMessage extends WxChannelMessage { + + private static final long serialVersionUID = -3183077256476798756L; + /** 订单信息 */ + @JsonProperty("order_info") + @JacksonXmlProperty(localName = "order_info") + private OrderExtInfo orderInfo; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/message/order/OrderIdInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/message/order/OrderIdInfo.java new file mode 100644 index 0000000000..b9ac33b376 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/message/order/OrderIdInfo.java @@ -0,0 +1,23 @@ +package me.chanjar.weixin.channel.bean.message.order; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; +import java.io.Serializable; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 订单id信息 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +public class OrderIdInfo implements Serializable { + + private static final long serialVersionUID = 5547544436235032051L; + /** 订单ID */ + @JsonProperty("order_id") + @JacksonXmlProperty(localName = "order_id") + private String orderId; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/message/order/OrderIdMessage.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/message/order/OrderIdMessage.java new file mode 100644 index 0000000000..398c29bde4 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/message/order/OrderIdMessage.java @@ -0,0 +1,27 @@ +package me.chanjar.weixin.channel.bean.message.order; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.message.WxChannelMessage; + +/** + * 订单id消息 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +@JacksonXmlRootElement(localName = "xml") +public class OrderIdMessage extends WxChannelMessage { + + private static final long serialVersionUID = 3793987364799712798L; + /** 订单信息 */ + @JsonProperty("order_info") + @JacksonXmlProperty(localName = "order_info") + private OrderIdInfo orderInfo; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/message/order/OrderPayInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/message/order/OrderPayInfo.java new file mode 100644 index 0000000000..d916c14a21 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/message/order/OrderPayInfo.java @@ -0,0 +1,24 @@ +package me.chanjar.weixin.channel.bean.message.order; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +/** + * 订单支付信息 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class OrderPayInfo extends OrderIdInfo { + + private static final long serialVersionUID = -3502786073769735831L; + /** 支付时间,秒级时间戳 */ + @JsonProperty("pay_time") + @JacksonXmlProperty(localName = "pay_time") + private Long payTime; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/message/order/OrderPayMessage.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/message/order/OrderPayMessage.java new file mode 100644 index 0000000000..ee1f458aba --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/message/order/OrderPayMessage.java @@ -0,0 +1,27 @@ +package me.chanjar.weixin.channel.bean.message.order; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.message.WxChannelMessage; + +/** + * 订单支付成功消息 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +@JacksonXmlRootElement(localName = "xml") +public class OrderPayMessage extends WxChannelMessage { + + private static final long serialVersionUID = 1083018549119427808L; + /** 订单信息 */ + @JsonProperty("order_info") + @JacksonXmlProperty(localName = "order_info") + private OrderPayInfo orderInfo; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/message/order/OrderSettleInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/message/order/OrderSettleInfo.java new file mode 100644 index 0000000000..b4f48b6fb8 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/message/order/OrderSettleInfo.java @@ -0,0 +1,24 @@ +package me.chanjar.weixin.channel.bean.message.order; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +/** + * 订单结算信息 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class OrderSettleInfo extends OrderIdInfo { + + private static final long serialVersionUID = -1817955568383872053L; + /** 结算时间 */ + @JsonProperty("settle_time") + @JacksonXmlProperty(localName = "settle_time") + private Long settleTime; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/message/order/OrderSettleMessage.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/message/order/OrderSettleMessage.java new file mode 100644 index 0000000000..2d3d1d96d6 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/message/order/OrderSettleMessage.java @@ -0,0 +1,27 @@ +package me.chanjar.weixin.channel.bean.message.order; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.message.WxChannelMessage; + +/** + * 订单结算消息 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +@JacksonXmlRootElement(localName = "xml") +public class OrderSettleMessage extends WxChannelMessage { + + private static final long serialVersionUID = -4001189226630840548L; + /** 订单信息 */ + @JsonProperty("order_info") + @JacksonXmlProperty(localName = "order_info") + private OrderSettleInfo orderInfo; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/message/order/OrderStatusMessage.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/message/order/OrderStatusMessage.java new file mode 100644 index 0000000000..4a06ccc99c --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/message/order/OrderStatusMessage.java @@ -0,0 +1,54 @@ +package me.chanjar.weixin.channel.bean.message.order; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement; +import java.util.Map; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.message.WxChannelMessage; + +/** + * 订单状态消息 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +@JacksonXmlRootElement(localName = "xml") +public class OrderStatusMessage extends WxChannelMessage { + + private static final long serialVersionUID = -356717038344749283L; + /** 订单ID */ + @JsonProperty("order_id") + @JacksonXmlProperty(localName = "order_id") + private String orderId; + + /** 订单状态 {@link me.chanjar.weixin.channel.enums.WxOrderStatus} */ + @JsonProperty("status") + @JacksonXmlProperty(localName = "status") + private Integer status; + + @JsonProperty("ProductOrderStatusUpdate") + @JacksonXmlProperty(localName = "ProductOrderStatusUpdate") + private void unpackNameFromNestedObject(Map map) { + if (map == null) { + return; + } + Object obj = null; + obj = map.get("order_id"); + if (obj != null) { + this.orderId = obj.toString(); + } + obj = map.get("status"); + if (obj != null) { + if (obj instanceof Integer) { + this.status = (Integer) obj; + } else if (obj instanceof String) { + this.status = Integer.parseInt((String) obj); + } + } + } +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/message/product/BrandMessage.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/message/product/BrandMessage.java new file mode 100644 index 0000000000..9a7c021c9d --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/message/product/BrandMessage.java @@ -0,0 +1,72 @@ +package me.chanjar.weixin.channel.bean.message.product; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement; +import java.util.Map; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.message.WxChannelMessage; + +/** + * 品牌消息 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +@JacksonXmlRootElement(localName = "xml") +public class BrandMessage extends WxChannelMessage { + + private static final long serialVersionUID = -3773902704930003105L; + /** 品牌库中的品牌编号 */ + @JsonProperty("brand_id") + @JacksonXmlProperty(localName = "brand_id") + private String brandId; + + /** 审核id */ + @JsonProperty("audit_id") + @JacksonXmlProperty(localName = "audit_id") + private String auditId; + + /** 审核状态, 1新增品牌 2更新品牌 3撤回品牌审核 4审核成功 5审核失败 6删除品牌 7品牌资质被系统撤销 */ + @JsonProperty("status") + @JacksonXmlProperty(localName = "status") + private Integer status; + + /** 相关信息 */ + @JsonProperty("reason") + @JacksonXmlProperty(localName = "reason") + private String reason; + + @JsonProperty("BrandEvent") + @JacksonXmlProperty(localName = "BrandEvent") + private void unpackNameFromNestedObject(Map map) { + if (map == null) { + return; + } + Object obj = null; + obj = map.get("brand_id"); + if (obj != null) { + this.brandId = (obj instanceof String ? (String) obj : String.valueOf(obj)); + } + obj = map.get("audit_id"); + if (obj != null) { + this.auditId = (obj instanceof String ? (String) obj : String.valueOf(obj)); + } + obj = map.get("status"); + if (obj != null) { + if (obj instanceof Integer) { + this.status = (Integer) obj; + } else if (obj instanceof String) { + this.status = Integer.parseInt((String) obj); + } + } + obj = map.get("reason"); + if (obj != null) { + this.reason = (obj instanceof String ? (String) obj : String.valueOf(obj)); + } + } +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/message/product/CategoryAuditMessage.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/message/product/CategoryAuditMessage.java new file mode 100644 index 0000000000..f6d696d5c1 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/message/product/CategoryAuditMessage.java @@ -0,0 +1,63 @@ +package me.chanjar.weixin.channel.bean.message.product; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement; +import java.util.Map; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.message.WxChannelMessage; + +/** + * 类目审核消息 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +@JacksonXmlRootElement(localName = "xml") +public class CategoryAuditMessage extends WxChannelMessage { + + private static final long serialVersionUID = 3192582751919917223L; + /** 审核id */ + @JsonProperty("audit_id") + @JacksonXmlProperty(localName = "audit_id") + private String auditId; + + /** 审核状态, 1:审核中, 2:审核拒绝, 3:审核通过, 12:主动取消申请单 */ + @JsonProperty("status") + @JacksonXmlProperty(localName = "status") + private Integer status; + + /** 相关信息 */ + @JsonProperty("reason") + @JacksonXmlProperty(localName = "reason") + private String reason; + + @JsonProperty("ProductCategoryAudit") + @JacksonXmlProperty(localName = "ProductCategoryAudit") + private void unpackNameFromNestedObject(Map map) { + if (map == null) { + return; + } + Object obj = null; + obj = map.get("audit_id"); + if (obj != null) { + this.auditId = (obj instanceof String ? (String) obj : String.valueOf(obj)); + } + obj = map.get("status"); + if (obj != null) { + if (obj instanceof Integer) { + this.status = (Integer) obj; + } else if (obj instanceof String) { + this.status = Integer.parseInt((String) obj); + } + } + obj = map.get("reason"); + if (obj != null) { + this.reason = (obj instanceof String ? (String) obj : String.valueOf(obj)); + } + } +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/message/product/SpuAuditMessage.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/message/product/SpuAuditMessage.java new file mode 100644 index 0000000000..569b53781e --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/message/product/SpuAuditMessage.java @@ -0,0 +1,83 @@ +package me.chanjar.weixin.channel.bean.message.product; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement; +import java.util.Map; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.message.WxChannelMessage; + +/** + * SPU审核消息 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +@JacksonXmlRootElement(localName = "xml") +public class SpuAuditMessage extends WxChannelMessage { + + private static final long serialVersionUID = 1763291928383078102L; + /** 商品id */ + @JsonProperty("product_id") + @JacksonXmlProperty(localName = "product_id") + private String productId; + + /** + * 审核状态, 2:审核不通过;3:审核通过 商品状态, 5:上架;11:自主下架;13:系统下架 + */ + @JsonProperty("status") + @JacksonXmlProperty(localName = "status") + private Integer status; + + /** 审核/下架原因,非必填字段 */ + @JsonProperty("reason") + @JacksonXmlProperty(localName = "reason") + private String reason; + + + + @JsonProperty("ProductSpuAudit") + @JacksonXmlProperty(localName = "ProductSpuAudit") + public void ProductSpuAudit(Map map) { + this.unpackNameFromNestedObject(map); + } + + @JsonProperty("ProductSpuUpdate") + @JacksonXmlProperty(localName = "ProductSpuUpdate") + public void ProductSpuUpdate(Map map) { + this.unpackNameFromNestedObject(map); + } + + @JsonProperty("ProductSpuListing") + @JacksonXmlProperty(localName = "ProductSpuListing") + public void ProductSpuListing(Map map) { + this.unpackNameFromNestedObject(map); + } + + private void unpackNameFromNestedObject(Map map) { + if (map == null) { + return; + } + Object obj = null; + obj = map.get("product_id"); + if (obj != null) { + this.productId = (obj instanceof String ? (String) obj : String.valueOf(obj)); + } + obj = map.get("status"); + if (obj != null) { + if (obj instanceof Integer) { + this.status = (Integer) obj; + } else if (obj instanceof String) { + this.status = Integer.parseInt((String) obj); + } + } + obj = map.get("reason"); + if (obj != null) { + this.reason = (obj instanceof String ? (String) obj : String.valueOf(obj)); + } + } +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/message/product/SpuStatusMessage.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/message/product/SpuStatusMessage.java new file mode 100644 index 0000000000..7fb9f272e8 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/message/product/SpuStatusMessage.java @@ -0,0 +1,72 @@ +package me.chanjar.weixin.channel.bean.message.product; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement; +import java.util.Map; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.message.WxChannelMessage; + +/** + * SPU状态消息 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +@JacksonXmlRootElement(localName = "xml") +public class SpuStatusMessage extends WxChannelMessage { + + private static final long serialVersionUID = 6872830451279856492L; + /** 商家自定义商品id */ + @JsonProperty("out_product_id") + @JacksonXmlProperty(localName = "out_product_id") + private String outProductId; + + /** 平台商品id */ + @JsonProperty("product_id") + @JacksonXmlProperty(localName = "product_id") + private String productId; + + /** 当前商品上下架状态 参考 {@link me.chanjar.weixin.channel.enums.SpuStatus } */ + @JsonProperty("status") + @JacksonXmlProperty(localName = "status") + private Integer status; + + /** 相关信息 */ + @JsonProperty("reason") + @JacksonXmlProperty(localName = "reason") + private String reason; + + @JsonProperty("OpenProductSpuStatusUpdate") + @JacksonXmlProperty(localName = "OpenProductSpuStatusUpdate") + private void unpackNameFromNestedObject(Map map) { + if (map == null) { + return; + } + Object obj = null; + obj = map.get("out_product_id"); + if (obj != null) { + this.outProductId = (obj instanceof String ? (String) obj : String.valueOf(obj)); + } + obj = map.get("product_id"); + if (obj != null) { + this.productId = (obj instanceof String ? (String) obj : String.valueOf(obj)); + } + obj = map.get("status"); + if (obj != null) { + if (obj instanceof Integer) { + this.status = (Integer) obj; + } else if (obj instanceof String) { + this.status = Integer.parseInt((String) obj); + } + } + obj = map.get("reason"); + if (obj != null) { + this.reason = (obj instanceof String ? (String) obj : String.valueOf(obj)); + } + } +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/message/product/SpuStockMessage.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/message/product/SpuStockMessage.java new file mode 100644 index 0000000000..96feac5a4a --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/message/product/SpuStockMessage.java @@ -0,0 +1,88 @@ +package me.chanjar.weixin.channel.bean.message.product; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement; +import java.util.Map; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.message.WxChannelMessage; + +/** + * SPU库存不足消息 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +@JacksonXmlRootElement(localName = "xml") +public class SpuStockMessage extends WxChannelMessage { + + private static final long serialVersionUID = 2250860804161527363L; + + /** 商品id */ + @JsonProperty("product_id") + @JacksonXmlProperty(localName = "product_id") + private String productId; + + /** 平台商品id */ + @JsonProperty("sku_id") + @JacksonXmlProperty(localName = "sku_id") + private String skuId; + + /** 剩余库存:当前实时库存数量 */ + @JsonProperty("remaining_stock_amount") + @JacksonXmlProperty(localName = "remaining_stock_amount") + private Long remainingStockAmount; + + /** 未发放的预存code数【该字段对code_source_type=2的团购优惠生效,其他类型该字段值为0】 */ + @JsonProperty("remaining_code_amount") + @JacksonXmlProperty(localName = "remaining_code_amount") + private Long remainingCodeAmount; + + /** ChannelsEcStockNoEnough */ + @JsonProperty("channels_ec_stock_no_enough") + @JacksonXmlProperty(localName = "channels_ec_stock_no_enough") + private void stockNoEnough(Map map) { + this.unpackNameFromNestedObject(map); + } + + /** + * 从嵌套对象中解析字段 + * + * @param map 嵌套对象 + */ + protected void unpackNameFromNestedObject(Map map) { + if (map == null) { + return; + } + Object obj = null; + obj = map.get("product_id"); + if (obj != null) { + this.productId = (obj instanceof String ? (String) obj : String.valueOf(obj)); + } + obj = map.get("sku_id"); + if (obj != null) { + this.skuId = (obj instanceof String ? (String) obj : String.valueOf(obj)); + } + + obj = map.get("remaining_stock_amount"); + if (obj != null) { + if (obj instanceof Number) { + this.remainingStockAmount = ((Number) obj).longValue(); + } else if (obj instanceof String) { + this.remainingStockAmount = Long.parseLong((String) obj); + } + } + obj = map.get("remaining_code_amount"); + if (obj != null) { + if (obj instanceof Number) { + this.remainingCodeAmount = ((Number) obj).longValue(); + } else if (obj instanceof String) { + this.remainingCodeAmount = Long.parseLong((String) obj); + } + } + } +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/message/sharer/SharerChangeMessage.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/message/sharer/SharerChangeMessage.java new file mode 100644 index 0000000000..8b2036693e --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/message/sharer/SharerChangeMessage.java @@ -0,0 +1,48 @@ +package me.chanjar.weixin.channel.bean.message.sharer; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.message.WxChannelMessage; + +/** + * 分享员变更消息 + * https://developers.weixin.qq.com/doc/channels/API/sharer/callback/channels_ec_sharer_change.html + * + * @author sd-hxf + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +@JacksonXmlRootElement(localName = "xml") +public class SharerChangeMessage extends WxChannelMessage { + + private static final long serialVersionUID = 4219477394934480421L; + + /** + * 分享员OpenID + */ + @JsonProperty("openid") + @JacksonXmlProperty(localName = "openid") + private String openid; + + /** + * 分享员类型:0-普通分享员,1-店铺分享员 + */ + @JsonProperty("sharer_type") + @JacksonXmlProperty(localName = "sharer_type") + private Integer sharerType; + + /** + * 分享员绑定状态:1-绑定,2-解绑 + */ + @JsonProperty("bind_status") + @JacksonXmlProperty(localName = "bind_status") + private Integer bindStatus; + + + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/message/store/CloseStoreMessage.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/message/store/CloseStoreMessage.java new file mode 100644 index 0000000000..2a43483354 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/message/store/CloseStoreMessage.java @@ -0,0 +1,38 @@ +package me.chanjar.weixin.channel.bean.message.store; + +/** + * @author Zeyes + */ + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.message.WxChannelMessage; + +/** + * 小店注销消息 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +@JacksonXmlRootElement(localName = "xml") +public class CloseStoreMessage extends WxChannelMessage { + + private static final long serialVersionUID = 7619787772418774020L; + + /** appid */ + @JsonProperty("appid") + @JacksonXmlProperty(localName = "appid") + private String appid; + + /** Unix时间戳,即格林威治时间1970年01月01日00时00分00秒(北京时间1970年01月01日08时00分00秒)起至现在的总秒数 */ + @JsonProperty("close_timestamp") + @JacksonXmlProperty(localName = "close_timestamp") + private Long closeTimestamp; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/message/store/NicknameUpdateMessage.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/message/store/NicknameUpdateMessage.java new file mode 100644 index 0000000000..e6665497e0 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/message/store/NicknameUpdateMessage.java @@ -0,0 +1,43 @@ +package me.chanjar.weixin.channel.bean.message.store; + +/** + * @author Zeyes + */ + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.message.WxChannelMessage; + +/** + * 小店修改名称消息 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +@JacksonXmlRootElement(localName = "xml") +public class NicknameUpdateMessage extends WxChannelMessage { + + private static final long serialVersionUID = 7619787772418774020L; + + /** appid */ + @JsonProperty("appid") + @JacksonXmlProperty(localName = "appid") + private String appid; + + /** 小店旧昵称 */ + @JsonProperty("old_nickname") + @JacksonXmlProperty(localName = "old_nickname") + private String oldNickname; + + /** 小店新昵称 */ + @JsonProperty("new_nickname") + @JacksonXmlProperty(localName = "new_nickname") + private String newNickname; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/message/supplier/SupplierItemInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/message/supplier/SupplierItemInfo.java new file mode 100644 index 0000000000..49bbb0548b --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/message/supplier/SupplierItemInfo.java @@ -0,0 +1,44 @@ +package me.chanjar.weixin.channel.bean.message.supplier; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; +import java.io.Serializable; +import java.util.List; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 团长商品变更信息 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +public class SupplierItemInfo implements Serializable { + + private static final long serialVersionUID = -1971161027976024360L; + /** 商品变更类型,1:新增商品;2:更新商品 */ + @JsonProperty("event_type") + @JacksonXmlProperty(localName = "event_type") + private Integer eventType; + + /** 团长商品所属小店appid */ + @JsonProperty("appid") + @JacksonXmlProperty(localName = "appid") + private String appid; + + /** 商品id */ + @JsonProperty("product_id") + @JacksonXmlProperty(localName = "product_id") + private String productId; + + /** 商品版本号 */ + @JsonProperty("version") + @JacksonXmlProperty(localName = "version") + private String version; + + /** 商品更新字段,当event_type = 2时有值。commission_ratio、service_ratio、status、active_time分别表示佣金、服务费、商品状态和合作生效时间有变更 */ + @JsonProperty("update_fields") + @JacksonXmlProperty(localName = "update_fields") + private List updateFields; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/message/supplier/SupplierItemMessage.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/message/supplier/SupplierItemMessage.java new file mode 100644 index 0000000000..2403aa0c60 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/message/supplier/SupplierItemMessage.java @@ -0,0 +1,27 @@ +package me.chanjar.weixin.channel.bean.message.supplier; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.message.WxChannelMessage; + +/** + * 团长商品变更 消息 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +@JacksonXmlRootElement(localName = "xml") +public class SupplierItemMessage extends WxChannelMessage { + + private static final long serialVersionUID = -4520611382070764349L; + /** 账户信息 */ + @JsonProperty("item_info") + @JacksonXmlProperty(localName = "item_info") + private SupplierItemInfo itemInfo; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/message/vip/CouponInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/message/vip/CouponInfo.java new file mode 100644 index 0000000000..4305d4738d --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/message/vip/CouponInfo.java @@ -0,0 +1,27 @@ +package me.chanjar.weixin.channel.bean.message.vip; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * 优惠券信息 + * + * @author asushiye + */ + +@Data +@JsonInclude(JsonInclude.Include.NON_NULL) +@NoArgsConstructor +public class CouponInfo implements Serializable { + + private static final long serialVersionUID = -3659710836197413932L; + /** 兑换的优惠券ID**/ + @JsonProperty("related_coupon_id") + @JacksonXmlProperty(localName = "related_coupon_id") + private Long relatedCouponId; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/message/vip/ExchangeInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/message/vip/ExchangeInfo.java new file mode 100644 index 0000000000..4cec52af02 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/message/vip/ExchangeInfo.java @@ -0,0 +1,42 @@ +package me.chanjar.weixin.channel.bean.message.vip; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * 积分兑换 + * + * @author asushiye + */ + +@Data +@JsonInclude(JsonInclude.Include.NON_NULL) +@NoArgsConstructor +public class ExchangeInfo implements Serializable { + + private static final long serialVersionUID = -5692646625631036694L; + /** 入会时间 **/ + @JsonProperty("pay_score") + @JacksonXmlProperty(localName = "pay_score") + private Long pay_score; + + /** 兑换类型 1.优惠券 2商品 **/ + @JsonProperty("score_item_type") + @JacksonXmlProperty(localName = "score_item_type") + private Long score_item_type; + + /** 优惠券信息 **/ + @JsonProperty("coupon_info") + @JacksonXmlProperty(localName = "coupon_info") + private CouponInfo couponInfo; + + /** 商品信息 **/ + @JsonProperty("product_info") + @JacksonXmlProperty(localName = "product_info") + private ProductInfo productInfo; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/message/vip/ExchangeInfoMessage.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/message/vip/ExchangeInfoMessage.java new file mode 100644 index 0000000000..6cb98225bd --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/message/vip/ExchangeInfoMessage.java @@ -0,0 +1,28 @@ +package me.chanjar.weixin.channel.bean.message.vip; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.message.WxChannelMessage; + +/** + * 积分兑换消息 + * + * @author asushiye + */ + +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +@JacksonXmlRootElement(localName = "xml") +public class ExchangeInfoMessage extends WxChannelMessage { + + private static final long serialVersionUID = 2926346100146724110L; + /** 积分兑换信息 */ + @JsonProperty("exchange_info") + @JacksonXmlProperty(localName = "exchange_info") + private ExchangeInfo exchangeInfo; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/message/vip/ProductInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/message/vip/ProductInfo.java new file mode 100644 index 0000000000..451a1e19b5 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/message/vip/ProductInfo.java @@ -0,0 +1,27 @@ +package me.chanjar.weixin.channel.bean.message.vip; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * 商品信息 + * + * @author asushiye + */ + +@Data +@JsonInclude(JsonInclude.Include.NON_NULL) +@NoArgsConstructor +public class ProductInfo implements Serializable { + + private static final long serialVersionUID = -3037180342360944232L; + /** 兑换的商品ID**/ + @JsonProperty("related_product_id") + @JacksonXmlProperty(localName = "related_product_id") + private Long relatedProductId; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/message/vip/UserInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/message/vip/UserInfo.java new file mode 100644 index 0000000000..f21c83c168 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/message/vip/UserInfo.java @@ -0,0 +1,59 @@ +package me.chanjar.weixin.channel.bean.message.vip; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * 用户信息 + * + * @author asushiye + */ + +@Data +@JsonInclude(JsonInclude.Include.NON_NULL) +@NoArgsConstructor +public class UserInfo implements Serializable { + + private static final long serialVersionUID = 1239486732464880985L; + /** 入会时间 **/ + @JsonProperty("join_time") + @JacksonXmlProperty(localName = "join_time") + private Long joinTime; + + /** 注销时间 **/ + @JsonProperty("close_time") + @JacksonXmlProperty(localName = "close_time") + private Long closeTime; + + /** 手机号 **/ + @JsonProperty("phone_number") + @JacksonXmlProperty(localName = "phone_number") + private String phoneNumber; + + /** 等级 **/ + @JsonProperty("grade") + @JacksonXmlProperty(localName = "grade") + private Integer grade; + + /** 当前等级经验值 **/ + @JsonProperty("experience_value") + @JacksonXmlProperty(localName = "experience_value") + private Long experienceValue; + + /** 当前积分 **/ + @JsonProperty("score") + @JacksonXmlProperty(localName = "score") + private Long score; + + /** 本次改动积分,负数减少,正数新增 **/ + @JsonProperty("delta_score") + @JacksonXmlProperty(localName = "delta_score") + private Long deltaScore; + + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/message/vip/UserInfoMessage.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/message/vip/UserInfoMessage.java new file mode 100644 index 0000000000..439edd0951 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/message/vip/UserInfoMessage.java @@ -0,0 +1,28 @@ +package me.chanjar.weixin.channel.bean.message.vip; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.message.WxChannelMessage; + +/** + * 用户信息消息 + * + * @author asushiye + */ + +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +@JacksonXmlRootElement(localName = "xml") +public class UserInfoMessage extends WxChannelMessage { + + private static final long serialVersionUID = 6926608689621530622L; + /** 用户信息 */ + @JsonProperty("user_info") + @JacksonXmlProperty(localName = "order_info") + private UserInfo userInfo; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/message/voucher/VoucherInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/message/voucher/VoucherInfo.java new file mode 100644 index 0000000000..1b5a926205 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/message/voucher/VoucherInfo.java @@ -0,0 +1,106 @@ +package me.chanjar.weixin.channel.bean.message.voucher; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; +import java.io.Serializable; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * @author Zeyes + */ +@Data +@NoArgsConstructor +public class VoucherInfo implements Serializable { + private static final long serialVersionUID = 6007964849358969438L; + + /** 券code */ + @JsonProperty("code") + @JacksonXmlProperty(localName = "code") + private String code; + + /** 劵码类型,1商户实时code 2户预存 3平台生成 */ + @JsonProperty("code_type") + @JacksonXmlProperty(localName = "code_type") + private Integer codeType; + + /** 券状态 */ + @JsonProperty("status") + @JacksonXmlProperty(localName = "status") + private Integer status; + + /** 发放时间,时间戳 */ + @JsonProperty("send_time") + @JacksonXmlProperty(localName = "send_time") + private Long sendTime; + + /** 最近更新时间,时间戳 */ + @JsonProperty("update_time") + @JacksonXmlProperty(localName = "update_time") + private Long updateTime; + + /** 核销生效时间,时间戳 */ + @JsonProperty("start_time") + @JacksonXmlProperty(localName = "start_time") + private Long startTime; + + /** 核销结束时间,时间戳 */ + @JsonProperty("end_time") + @JacksonXmlProperty(localName = "end_time") + private Long endTime; + + /** 核销时间,时间戳。次卡时不返回此字段 */ + @JsonProperty("consume_time") + @JacksonXmlProperty(localName = "consume_time") + private Long consumeTime; + + /** 退券时间,时间戳。次卡时不返回此字段 */ + @JsonProperty("refund_time") + @JacksonXmlProperty(localName = "refund_time") + private Long refundTime; + + /** 核销门店名称 */ + @JsonProperty("consume_store_name") + @JacksonXmlProperty(localName = "consume_store_name") + private String consumeStoreName; + + /** */ + @JsonProperty("voucher_type") + @JacksonXmlProperty(localName = "voucher_type") + private Integer voucherType; + + /** 券的售卖价格(分) */ + @JsonProperty("voucher_buy_amount") + @JacksonXmlProperty(localName = "voucher_buy_amount") + private Integer voucherBuyAmount; + + /** 券市场金额(分) */ + @JsonProperty("voucher_actual_amount") + @JacksonXmlProperty(localName = "voucher_actual_amount") + private Integer voucherActualAmount; + + /** 用户手机号 */ + @JsonProperty("telphone_no") + @JacksonXmlProperty(localName = "telphone_no") + private String telPhoneNo; + + /** 商品id */ + @JsonProperty("product_id") + @JacksonXmlProperty(localName = "product_id") + private String productId; + + /** 商品下的skuId */ + @JsonProperty("sku_id") + @JacksonXmlProperty(localName = "sku_id") + private String skuId; + + /** 购买券的订单id */ + @JsonProperty("order_id") + @JacksonXmlProperty(localName = "order_id") + private String orderId; + + /** 用户在商家品牌appid下的openid */ + @JsonProperty("openid") + @JacksonXmlProperty(localName = "openid") + private String openId; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/message/voucher/VoucherMessage.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/message/voucher/VoucherMessage.java new file mode 100644 index 0000000000..941828969d --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/message/voucher/VoucherMessage.java @@ -0,0 +1,29 @@ +package me.chanjar.weixin.channel.bean.message.voucher; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement; +import java.util.List; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.message.WxChannelMessage; + +/** + * 发放团购优惠成功消息 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +@JacksonXmlRootElement(localName = "xml") +public class VoucherMessage extends WxChannelMessage { + + private static final long serialVersionUID = 975858675917036089L; + + /** 发放团购优惠成功消息 */ + @JsonProperty("voucher_list") + @JacksonXmlProperty(localName = "voucher_list") + private List voucherInfo; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/AfterSaleDetail.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/AfterSaleDetail.java new file mode 100644 index 0000000000..5401a588bf --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/AfterSaleDetail.java @@ -0,0 +1,27 @@ +package me.chanjar.weixin.channel.bean.order; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import java.util.List; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 售后信息详情 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +public class AfterSaleDetail implements Serializable { + + private static final long serialVersionUID = -3786573982841041144L; + + /** 正在售后流程的售后单数 */ + @JsonProperty("on_aftersale_order_cnt") + private Integer onAfterSaleOrderCnt; + + /** 售后单列表 */ + @JsonProperty("aftersale_order_list") + private List afterSaleOrderList; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/AfterSaleOrderInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/AfterSaleOrderInfo.java new file mode 100644 index 0000000000..118feba35b --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/AfterSaleOrderInfo.java @@ -0,0 +1,30 @@ +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 AfterSaleOrderInfo implements Serializable { + + private static final long serialVersionUID = 3938545222231426455L; + + /** 售后单ID */ + @JsonProperty("aftersale_order_id") + private String afterSaleOrderId; + + public String getAfterSaleOrderId() { + return afterSaleOrderId; + } + + public void setAfterSaleOrderId(String afterSaleOrderId) { + this.afterSaleOrderId = afterSaleOrderId; + } +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/ChangeOrderInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/ChangeOrderInfo.java new file mode 100644 index 0000000000..f6485085bb --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/ChangeOrderInfo.java @@ -0,0 +1,31 @@ +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 ChangeOrderInfo implements Serializable { + + private static final long serialVersionUID = 4932726847720452340L; + + /** 商品id */ + @JsonProperty("product_id") + private String productId; + + /** 商品sku */ + @JsonProperty("sku_id") + private String skuId; + + /** 订单中该商品修改后的总价,以分为单位 */ + @JsonProperty("change_price") + private String changePrice; + +} 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/DecodeAddressInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/DecodeAddressInfo.java new file mode 100644 index 0000000000..3aa6622eeb --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/DecodeAddressInfo.java @@ -0,0 +1,22 @@ +package me.chanjar.weixin.channel.bean.order; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.bean.base.AddressInfo; + +/** + * 解码地址数据 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class DecodeAddressInfo extends AddressInfo { + + /** 虚拟发货订单联系方式,在发货方式为无需快递(deliver_method=1)时返回 */ + @JsonProperty("virtual_order_tel_number") + private String virtualOrderTelNumber; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/DecodeSensitiveInfoResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/DecodeSensitiveInfoResponse.java new file mode 100644 index 0000000000..c0431a8fd6 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/DecodeSensitiveInfoResponse.java @@ -0,0 +1,28 @@ +package me.chanjar.weixin.channel.bean.order; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; + +/** + * 解码订单包含的敏感数据响应 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class DecodeSensitiveInfoResponse extends WxChannelBaseResponse { + + private static final long serialVersionUID = 935829924760021624L; + + /** 收货信息 */ + @JsonProperty("address_info") + private DecodeAddressInfo addressInfo; + + /** 虚拟号信息 */ + @JsonProperty("virtual_number_info") + private VirtualNumberInfo virtualNumberInfo; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/DeliveryProductInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/DeliveryProductInfo.java new file mode 100644 index 0000000000..5427a49839 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/DeliveryProductInfo.java @@ -0,0 +1,48 @@ +package me.chanjar.weixin.channel.bean.order; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import java.util.List; +import lombok.Data; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.bean.delivery.FreightProductInfo; + +/** + * 发货物流信息 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +public class DeliveryProductInfo implements Serializable { + + private static final long serialVersionUID = -8110532854439612471L; + /** 快递单号 */ + @JsonProperty("waybill_id") + private String waybillId; + + /** 快递公司编码 */ + @JsonProperty("delivery_id") + private String deliveryId; + + /** 包裹中商品信息 */ + @JsonProperty("product_infos") + private List productInfos; + + /** 快递公司名称 */ + @JsonProperty("delivery_name") + private String deliveryName; + + /** 发货时间,秒级时间戳 */ + @JsonProperty("delivery_time") + private Long deliveryTime; + + /** 配送方式,枚举值见DeliveryType {@link me.chanjar.weixin.channel.enums.DeliveryType} */ + @JsonProperty("deliver_type") + private Integer deliverType; + + /** 发货地址 */ + @JsonProperty("delivery_address") + private OrderAddressInfo deliveryAddress; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/DeliveryUpdateParam.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/DeliveryUpdateParam.java new file mode 100644 index 0000000000..6aca6feed4 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/DeliveryUpdateParam.java @@ -0,0 +1,51 @@ +package me.chanjar.weixin.channel.bean.order; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import java.util.List; +import lombok.Data; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.bean.delivery.FreightProductInfo; + +/** + * 修改物流参数 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@JsonInclude(JsonInclude.Include.NON_NULL) +public class DeliveryUpdateParam implements Serializable { + + /** 订单ID */ + @JsonProperty("order_id") + private String orderId; + + /** 物流公司ID */ + @JsonProperty("delivery_list") + private List deliveryList; + + @Data + @NoArgsConstructor + @JsonInclude(JsonInclude.Include.NON_NULL) + public static class DeliveryInfo implements Serializable { + + private static final long serialVersionUID = 1348000697768633889L; + /** 快递单号 */ + @JsonProperty("waybill_id") + private String waybillId; + + /** 快递公司编码 */ + @JsonProperty("delivery_id") + private String deliveryId; + + /** 配送方式,枚举值见DeliveryType {@link me.chanjar.weixin.channel.enums.DeliveryType} */ + @JsonProperty("deliver_type") + private Integer deliverType; + + /** 包裹中商品信息 */ + @JsonProperty("product_infos") + private List productInfos; + } +} 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/OrderAddressInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/OrderAddressInfo.java new file mode 100644 index 0000000000..1af5aee49e --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/OrderAddressInfo.java @@ -0,0 +1,41 @@ +package me.chanjar.weixin.channel.bean.order; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.bean.base.AddressInfo; + +/** + * 地址信息 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class OrderAddressInfo extends AddressInfo { + + private static final long serialVersionUID = 3062707865189774795L; + /** 虚拟发货订单联系方式(deliver_method=1时返回) */ + @JsonProperty("virtual_order_tel_number") + private String virtualOrderTelNumber; + + /** + * 额外的联系方式信息(虚拟号码相关),具体结构请参考TelNumberExtInfo结构体 + */ + @JsonProperty("tel_number_ext_info") + private TelNumberExtInfo telNumberExtInfo; + + /** + * 0:不使用虚拟号码,1:使用虚拟号码 + */ + @JsonProperty("use_tel_number") + private Integer useTelNumber; + + /** + * 标识当前店铺下一个唯一的用户收货地址 + */ + @JsonProperty("hash_code") + private String hashCode; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/OrderAddressParam.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/OrderAddressParam.java new file mode 100644 index 0000000000..55eb6a8655 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/OrderAddressParam.java @@ -0,0 +1,32 @@ +package me.chanjar.weixin.channel.bean.order; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.bean.base.AddressInfo; + +/** + * 订单地址参数 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@JsonInclude(Include.NON_NULL) +public class OrderAddressParam implements Serializable { + + private static final long serialVersionUID = 2277618297276466650L; + + /** 订单id */ + @JsonProperty("order_id") + private String orderId; + + /** 地址信息 */ + @JsonProperty("user_address") + private AddressInfo userAddress; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/OrderAgentInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/OrderAgentInfo.java new file mode 100644 index 0000000000..548e36dd49 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/OrderAgentInfo.java @@ -0,0 +1,30 @@ +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 OrderAgentInfo implements Serializable { + + private static final long serialVersionUID = 6396067079343033841L; + + /** + * 授权视频号id + */ + @JsonProperty("agent_finder_id") + private String agentFinderId; + + /** + * 授权视频号昵称 + */ + @JsonProperty("agent_finder_nickname") + private String agentFinderNickname; +} 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 new file mode 100644 index 0000000000..f3cab1f4bf --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/OrderCommissionInfo.java @@ -0,0 +1,49 @@ +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 OrderCommissionInfo implements Serializable { + + private static final long serialVersionUID = -3046852309683467272L; + /** 商品skuid */ + @JsonProperty("sku_id") + private String skuId; + + /** 分账方昵称 */ + @JsonProperty("nickname") + private String nickname; + + /** 分账方类型,0:达人,1:团长 */ + @JsonProperty("type") + private Integer type; + + /** 分账状态, 1:未结算,2:已结算 */ + @JsonProperty("status") + private Integer status; + + /** 分账金额 */ + @JsonProperty("amount") + private Integer amount; + + /** 达人视频号id */ + @JsonProperty("finder_id") + private String finderId; + + /** 达人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 new file mode 100644 index 0000000000..34f2d670d0 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/OrderCouponInfo.java @@ -0,0 +1,41 @@ +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 OrderCouponInfo implements Serializable { + + private static final long serialVersionUID = -2033350505767196339L; + /** 用户优惠券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/OrderDeliveryInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/OrderDeliveryInfo.java new file mode 100644 index 0000000000..ebe6bb8dc2 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/OrderDeliveryInfo.java @@ -0,0 +1,59 @@ +package me.chanjar.weixin.channel.bean.order; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import java.util.List; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 物流信息 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +public class OrderDeliveryInfo implements Serializable { + + private static final long serialVersionUID = -5348922760017557397L; + /** 地址信息 */ + @JsonProperty("address_info") + private OrderAddressInfo addressInfo; + + /** 发货物流信息 */ + @JsonProperty("delivery_product_info") + private List deliveryProductInfos; + + /** 发货完成时间,秒级时间戳 */ + @JsonProperty("ship_done_time") + private Long shipDoneTime; + + /** 订单发货方式,0普通物流 1虚拟发货,由商品的同名字段决定 */ + @JsonProperty("deliver_method") + private Integer deliverMethod; + + /** 用户下单后申请修改收货地址,商家同意后该字段会覆盖订单地址信息 */ + @JsonProperty("address_under_review") + private OrderAddressInfo addressUnderReview; + + /** 修改地址申请时间,秒级时间戳 */ + @JsonProperty("address_apply_time") + private Long addressApplyTime; + + /** 电子面单代发时的订单密文 */ + @JsonProperty("ewaybill_order_code") + private String ewaybillOrderCode; + + /** 订单质检类型 2生鲜类质检 1珠宝玉石类质检 0不需要;不传递本字段表示不需要 */ + @JsonProperty("quality_inspect_type") + private String qualityInspectType; + + /** 质检信息 */ + @JsonProperty("quality_inspect_info") + private QualityInsepctInfo qualityInspectInfo; + + /** 虚拟商品充值账户信息 */ + @JsonProperty("recharge_info") + private RechargeInfo rechargeInfo; + +} 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 new file mode 100644 index 0000000000..4d96023be1 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/OrderDetailInfo.java @@ -0,0 +1,78 @@ +package me.chanjar.weixin.channel.bean.order; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import java.util.List; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 订单详细数据 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +public class OrderDetailInfo implements Serializable { + + private static final long serialVersionUID = 3916307299998005676L; + /** 商品列表 */ + @JsonProperty("product_infos") + private List productInfos; + + /** 支付信息 */ + @JsonProperty("pay_info") + private OrderPayInfo payInfo; + + /** 价格信息 */ + @JsonProperty("price_info") + private OrderPriceInfo priceInfo; + + /** 配送信息 */ + @JsonProperty("delivery_info") + private OrderDeliveryInfo deliveryInfo; + + /** 优惠券信息 */ + @JsonProperty("coupon_info") + private OrderCouponInfo couponInfo; + + /** 额外信息 */ + @JsonProperty("ext_info") + private OrderExtInfo extInfo; + + /** 分佣信息 */ + @JsonProperty("commission_infos") + private List commissionInfos; + + /** 分享信息 */ + @JsonProperty("sharer_info") + private OrderSharerInfo sharerInfo; + + /** 结算信息 */ + @JsonProperty("settle_info") + private OrderSettleInfo settleInfo; + + /** 分享员信息 */ + @JsonProperty("sku_sharer_infos") + private List skuSharerInfos; + + /** 授权账号信息 */ + @JsonProperty("agent_info") + private OrderAgentInfo agentInfo; + + /** 订单来源信息 */ + @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/OrderExtInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/OrderExtInfo.java new file mode 100644 index 0000000000..a846311c61 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/OrderExtInfo.java @@ -0,0 +1,54 @@ +package me.chanjar.weixin.channel.bean.order; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * 订单备注信息 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +public class OrderExtInfo implements Serializable { + + private static final long serialVersionUID = 4568097877621455429L; + /** + * 用户备注 + */ + @JsonProperty("customer_notes") + private String customerNotes; + + /** + * 商家备注 + */ + @JsonProperty("merchant_notes") + private String merchantNotes; + + /** + * 确认收货时间,包括用户主动确认收货和超时自动确认收货 + */ + @JsonProperty("confirm_receipt_time") + private Long confirmReceiptTime; + + /** + * 视频号id + */ + @JsonProperty("finder_id") + private String finderId; + + /** + * 直播id + */ + @JsonProperty("live_id") + private String liveId; + + /** + * 下单场景,枚举值见 {@link me.chanjar.weixin.channel.enums.OrderScene} + */ + @JsonProperty("order_scene") + private Integer orderScene; +} 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/OrderIdParam.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/OrderIdParam.java new file mode 100644 index 0000000000..f1e92e1339 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/OrderIdParam.java @@ -0,0 +1,27 @@ +package me.chanjar.weixin.channel.bean.order; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 订单id参数 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@JsonInclude(Include.NON_NULL) +public class OrderIdParam implements Serializable { + + private static final long serialVersionUID = -8616582197963359789L; + /** 订单ID */ + @JsonProperty("order_id") + private String orderId; + +} 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 new file mode 100644 index 0000000000..00222d8487 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/OrderInfo.java @@ -0,0 +1,69 @@ +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 OrderInfo implements Serializable { + + private static final long serialVersionUID = -4562618835611282016L; + /** 订单号 */ + @JsonProperty("order_id") + protected String orderId; + + /** 订单状态,枚举值见 {@link me.chanjar.weixin.channel.enums.WxOrderStatus} */ + @JsonProperty("status") + protected Integer status; + + /** 买家身份标识 */ + @JsonProperty("openid") + protected String openid; + + /** union id */ + @JsonProperty("unionid") + protected String unionid; + + /** 订单详细数据信息 */ + @JsonProperty("order_detail") + protected OrderDetailInfo orderDetail; + + /** 售后信息 */ + @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; + + /** 更新时间 秒级时间戳 */ + @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/OrderInfoResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/OrderInfoResponse.java new file mode 100644 index 0000000000..0b6fd53c17 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/OrderInfoResponse.java @@ -0,0 +1,23 @@ +package me.chanjar.weixin.channel.bean.order; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; + +/** + * 订单信息响应 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class OrderInfoResponse extends WxChannelBaseResponse { + + private static final long serialVersionUID = 935829924760021624L; + /** 订单信息 */ + @JsonProperty("order") + private OrderInfo order; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/OrderListParam.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/OrderListParam.java new file mode 100644 index 0000000000..a84da3d2e8 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/OrderListParam.java @@ -0,0 +1,39 @@ +package me.chanjar.weixin.channel.bean.order; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.bean.base.StreamPageParam; +import me.chanjar.weixin.channel.bean.base.TimeRange; + +/** + * 获取订单列表参数 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +@JsonInclude(Include.NON_NULL) +public class OrderListParam extends StreamPageParam { + + private static final long serialVersionUID = 3780097459964746890L; + /** 订单创建时间范围 */ + @JsonProperty("create_time_range") + private TimeRange createTimeRange; + + /** 订单更新时间范围 */ + @JsonProperty("update_time_range") + private TimeRange updateTimeRange; + + /** 订单状态,枚举值见 {@link me.chanjar.weixin.channel.enums.WxOrderStatus} */ + @JsonProperty("status") + private Integer status; + + /** 买家身份标识 */ + @JsonProperty("openid") + private Integer openid; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/OrderListResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/OrderListResponse.java new file mode 100644 index 0000000000..454abc59d9 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/OrderListResponse.java @@ -0,0 +1,32 @@ +package me.chanjar.weixin.channel.bean.order; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.List; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; + +/** + * 订单列表 响应 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class OrderListResponse extends WxChannelBaseResponse { + + private static final long serialVersionUID = -6198624448684807852L; + /** 订单id列表 */ + @JsonProperty("order_id_list") + private List ids; + + /** 分页参数,下一页请求回传 */ + @JsonProperty("next_key") + private String nextKey; + + /** 是否还有下一页,true:有下一页;false:已经结束,没有下一页。 */ + @JsonProperty("has_more") + private Boolean hasMore; +} 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 new file mode 100644 index 0000000000..7a9f367d76 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/OrderPayInfo.java @@ -0,0 +1,30 @@ +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 OrderPayInfo implements Serializable { + + private static final long serialVersionUID = -5085386252699113948L; + /** 预支付id */ + @JsonProperty("payment_method") + private Integer paymentMethod; + + /** 支付时间,秒级时间戳 */ + @JsonProperty("pay_time") + private Long payTime; + + /** 支付单号 */ + @JsonProperty("transaction_id") + private String transactionId; + +} 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 new file mode 100644 index 0000000000..50eac04e50 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/OrderPriceInfo.java @@ -0,0 +1,140 @@ +package me.chanjar.weixin.channel.bean.order; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * 商店订单价格信息 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +public class OrderPriceInfo implements Serializable { + private static final long serialVersionUID = 5216506688949493432L; + + /** + * 商品总价,单位为分 + */ + @JsonProperty("product_price") + private Integer productPrice; + + /** + * 订单金额,单位为分 + */ + @JsonProperty("order_price") + private Integer orderPrice; + + /** + * 运费,单位为分 + */ + @JsonProperty("freight") + private Integer freight; + + /** + * 优惠金额,单位为分 + */ + @JsonProperty("discounted_price") + private Integer discountedPrice; + + /** + * 是否有优惠 + */ + @JsonProperty("is_discounted") + private Boolean isDiscounted; + + /** + * 订单原始价格,单位为分 + */ + @JsonProperty("original_order_price") + private Integer originalOrderPrice; + + /** + * 商品预估价格,单位为分 + */ + @JsonProperty("estimate_product_price") + private Integer estimateProductPrice; + + /** + * 改价后降低金额,单位为分 + */ + @JsonProperty("change_down_price") + private Integer changeDownPrice; + + /** + * 改价后运费,单位为分 + */ + @JsonProperty("change_freight") + private Integer changeFreight; + + /** + * 是否修改运费 + */ + @JsonProperty("is_change_freight") + private Boolean changeFreighted; + + /** + * 是否使用了会员积分抵扣 + */ + @JsonProperty("use_deduction") + private Boolean useDeduction; + + /** + * 会员积分抵扣金额,单位为分 + */ + @JsonProperty("deduction_price") + private Integer deductionPrice; + + /** + * 商家实收金额,单位为分 + * merchant_receieve_price=original_order_price-discounted_price-deduction_price-change_down_price + */ + @JsonProperty("merchant_receieve_price") + private Integer merchantReceivePrice; + + /** + * 商家优惠金额,单位为分,含义同discounted_price,必填 + */ + @JsonProperty("merchant_discounted_price") + private Integer merchantDiscountedPrice; + + /** + * 达人优惠金额,单位为分 + */ + @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/OrderPriceParam.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/OrderPriceParam.java new file mode 100644 index 0000000000..30f74501c4 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/OrderPriceParam.java @@ -0,0 +1,46 @@ +package me.chanjar.weixin.channel.bean.order; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import java.util.List; +import lombok.Data; + +/** + * 订单价格参数 + * + * @author Zeyes + */ +@Data +@JsonInclude(Include.NON_NULL) +public class OrderPriceParam implements Serializable { + + private static final long serialVersionUID = -7925819981481556218L; + /** 订单id */ + @JsonProperty("order_id") + private String orderId; + + /** 是否修改运费 */ + @JsonProperty("change_express") + private Boolean changeExpress; + + /** 修改后的运费价格(change_express=true时必填),以分为单位 */ + @JsonProperty("express_fee") + private Integer expressFee; + + /** 改价列表 */ + @JsonProperty("change_order_infos") + private List changeOrderInfos; + + public OrderPriceParam() { + } + + public OrderPriceParam(String orderId, Integer expressFee, List changeOrderInfos) { + this.orderId = orderId; + // expressFee不为空时,表示修改运费 + this.changeExpress = (expressFee != null); + this.expressFee = expressFee; + this.changeOrderInfos = changeOrderInfos; + } +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/OrderProductExtraService.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/OrderProductExtraService.java new file mode 100644 index 0000000000..ff413a9646 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/OrderProductExtraService.java @@ -0,0 +1,28 @@ +package me.chanjar.weixin.channel.bean.order; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * 商品额外服务信息 + * + * @author 北鹤M + */ +@Data +@NoArgsConstructor +public class OrderProductExtraService implements Serializable { + + private static final long serialVersionUID = -8752053507170277156L; + + /** 7天无理由:0:不支持,1:支持 */ + @JsonProperty("seven_day_return") + private Integer sevenDayReturn; + + /** 商家运费险:0:不支持,1:支持 */ + @JsonProperty("freight_insurance") + private Integer freightInsurance; + +} 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 new file mode 100644 index 0000000000..e5c37e3cba --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/OrderProductInfo.java @@ -0,0 +1,256 @@ +package me.chanjar.weixin.channel.bean.order; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.io.Serializable; +import java.util.List; + +import lombok.Data; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.bean.base.AttrInfo; + +/** + * 订单商品信息 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +public class OrderProductInfo implements Serializable { + + private static final long serialVersionUID = -2193536732955185928L; + /** + * 商品spu id + */ + @JsonProperty("product_id") + private String productId; + + /** + * sku_id + */ + @JsonProperty("sku_id") + private String skuId; + + /** + * sku小图 + */ + @JsonProperty("thumb_img") + private String thumbImg; + + /** + * sku数量 + */ + @JsonProperty("sku_cnt") + private Integer skuCnt; + + /** + * 售卖价格(单位:分) + */ + @JsonProperty("sale_price") + private Integer salePrice; + + /** + * 商品标题 + */ + @JsonProperty("title") + private String title; + + /** + * 正在售后/退款流程中的 sku 数量 + */ + @JsonProperty("on_aftersale_sku_cnt") + private Integer onAfterSaleSkuCnt; + + /** + * 完成售后/退款的 sku 数量 + */ + @JsonProperty("finish_aftersale_sku_cnt") + private Integer finishAfterSaleSkuCnt; + + /** + * 商品编码 + */ + @JsonProperty("sku_code") + private String skuCode; + + /** + * 市场价格(单位:分) + */ + @JsonProperty("market_price") + private Integer marketPrice; + + /** + * sku属性 + */ + @JsonProperty("sku_attrs") + private List skuAttrs; + + /** + * sku实付价格 + */ + @JsonProperty("real_price") + private Integer realPrice; + + /** + * 商品外部spu id + */ + @JsonProperty("out_product_id") + private String outProductId; + + /** + * 商品外部sku id + */ + @JsonProperty("out_sku_id") + private String outSkuId; + + /** + * 是否有优惠金额,非必填,默认为false + */ + @JsonProperty("is_discounted") + private Boolean isDiscounted; + + /** + * 优惠后 sku 价格,非必填,is_discounted为 true 时有值 + */ + @JsonProperty("estimate_price") + private Integer estimatePrice; + + /** + * 是否修改过价格,非必填,默认为false + */ + @JsonProperty("is_change_price") + private Boolean changePriced; + + /** + * 改价后 sku 价格,非必填,is_change_price为 true 时有值 + */ + @JsonProperty("change_price") + private Integer changePrice; + + /** + * 区域库存id + */ + @JsonProperty("out_warehouse_id") + private String outWarehouseId; + + /** + * 商品发货信息 + */ + @JsonProperty("sku_deliver_info") + private OrderSkuDeliverInfo skuDeliverInfo; + + /** + * 商品额外服务信息 + */ + @JsonProperty("extra_service") + private OrderProductExtraService extraService; + + /** + * 是否使用了会员积分抵扣 + */ + @JsonProperty("use_deduction") + private Boolean useDeduction; + + /** + * 会员积分抵扣金额,单位为分 + */ + @JsonProperty("deduction_price") + private Integer deductionPrice; + + /** + * 商品优惠券信息,具体结构请参考OrderProductCouponInfo结构体,逐步替换 order.order_detail.coupon_info + */ + @JsonProperty("order_product_coupon_info_list") + private List orderProductCouponInfoList; + + /** + * 商品发货时效,超时此时间未发货即为发货超时 + */ + @JsonProperty("delivery_deadline") + private Long deliveryDeadline; + + /** + * 商家优惠金额,单位为分 + */ + @JsonProperty("merchant_discounted_price") + private Integer merchantDiscountedPrice; + + /** + * 达人优惠金额,单位为分 + */ + @JsonProperty("finder_discounted_price") + private Integer finderDiscountedPrice; + + /** + * 是否赠品,非必填,赠品商品返回,1:是赠品 + */ + @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/OrderRemarkParam.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/OrderRemarkParam.java new file mode 100644 index 0000000000..707ec0d96b --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/OrderRemarkParam.java @@ -0,0 +1,28 @@ +package me.chanjar.weixin.channel.bean.order; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 订单备注 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class OrderRemarkParam implements Serializable { + + private static final long serialVersionUID = 2285714780419948468L; + /** 订单id */ + @JsonProperty("order_id") + private String orderId; + + /** 备注内容 */ + @JsonProperty("merchant_notes") + private String merchantNotes; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/OrderSearchCondition.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/OrderSearchCondition.java new file mode 100644 index 0000000000..a4c8373cec --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/OrderSearchCondition.java @@ -0,0 +1,62 @@ +package me.chanjar.weixin.channel.bean.order; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 订单 搜索条件 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@JsonInclude(Include.NON_EMPTY) +public class OrderSearchCondition implements Serializable { + + private static final long serialVersionUID = 5492584333971883140L; + /** 商品标题关键词 */ + @JsonProperty("title") + private String title; + + /** 商品编码 */ + @JsonProperty("sku_code") + private String skuCode; + + /** 收件人 */ + @JsonProperty("user_name") + private String userName; + + /** + * 收件人电话 + * @deprecated 当前字段已经废弃,请勿使用,如果原本填手机后四位,可正常使用,否则接口报错 + */ + @JsonProperty("tel_number") + @Deprecated + private String telNumber; + + /** + * 收件人电话后四位 + */ + @JsonProperty("tel_number_last4") + private String telNumberLast4; + + /** 选填,只搜一个订单时使用 */ + @JsonProperty("order_id") + private String orderId; + + /** 商家备注 */ + @JsonProperty("merchant_notes") + private String merchantNotes; + + /** 买家备注 */ + @JsonProperty("customer_notes") + private String customerNotes; + + /** 申请修改地址审核中 */ + @JsonProperty("address_under_review") + private Boolean addressUnderReview; +} 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 new file mode 100644 index 0000000000..2f56747d19 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/OrderSearchParam.java @@ -0,0 +1,30 @@ +package me.chanjar.weixin.channel.bean.order; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.bean.base.StreamPageParam; + +/** + * @author Zeyes + */ +@Data +@NoArgsConstructor +@JsonInclude(Include.NON_EMPTY) +public class OrderSearchParam extends StreamPageParam { + + private static final long serialVersionUID = 5737520097455135218L; + /** 商品标题关键词 */ + @JsonProperty("search_condition") + private OrderSearchCondition searchCondition; + + /** 不填该参数:全部订单 0:没有正在售后的订单, 1:正在售后单数量大于等于1的订单 */ + @JsonProperty("on_aftersale_order_exist") + private Integer onAfterSaleOrderExist; + + /** 订单状态 {@link me.chanjar.weixin.channel.enums.WxOrderStatus} */ + @JsonProperty("status") + private Integer status; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/OrderSettleInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/OrderSettleInfo.java new file mode 100644 index 0000000000..bd31931444 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/OrderSettleInfo.java @@ -0,0 +1,49 @@ +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 OrderSettleInfo implements Serializable { + + private static final long serialVersionUID = 2140632631448343656L; + /** + * 预计技术服务费(单位为分) + */ + @JsonProperty("predict_commission_fee") + private Integer predictCommissionFee; + + /** + * 实际技术服务费(单位为分)(未结算时本字段为空) + */ + @JsonProperty("commission_fee") + private Integer commissionFee; + + /** + * 预计人气卡返佣金额,单位为分(未发起结算时本字段为空) + */ + @JsonProperty("predict_wecoin_commission") + private Integer predictWecoinCommission; + + /** + * 实际人气卡返佣金额,单位为分(未结算时本字段为空) + */ + @JsonProperty("wecoin_commission") + private Integer wecoinCommission; + + /** + * 商家结算时间 + */ + @JsonProperty("settle_time") + private Long settleTime; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/OrderSharerInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/OrderSharerInfo.java new file mode 100644 index 0000000000..7ed41d2edf --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/OrderSharerInfo.java @@ -0,0 +1,49 @@ +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 OrderSharerInfo implements Serializable { + + private static final long serialVersionUID = 7183259072254660971L; + /** + * 分享员openid + */ + @JsonProperty("sharer_openid") + private String sharerOpenid; + + /** + * 分享员unionid + */ + @JsonProperty("sharer_unionid") + private String sharerUnionid; + + /** + * 分享员类型,0:普通分享员,1:店铺分享员 + */ + @JsonProperty("sharer_type") + private Integer sharerType; + + /** + * 分享场景 {@link me.chanjar.weixin.channel.enums.ShareScene} + */ + @JsonProperty("share_scene") + private Integer shareScene; + + /** + * 分享员数据是否已经解析完成【1:解析完成 0:解析中】 + */ + @JsonProperty("handling_progress") + private Integer handlingProgress; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/OrderSkuDeliverInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/OrderSkuDeliverInfo.java new file mode 100644 index 0000000000..6dd46c9a39 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/OrderSkuDeliverInfo.java @@ -0,0 +1,28 @@ +package me.chanjar.weixin.channel.bean.order; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * 商品发货信息 + * + * @author 北鹤M + */ +@Data +@NoArgsConstructor +public class OrderSkuDeliverInfo implements Serializable { + + private static final long serialVersionUID = 4075897806362929800L; + + /** 商品发货类型:0:现货,1:全款预售 */ + @JsonProperty("stock_type") + private Integer stockType; + + /** 预计发货时间(stock_type=1时返回该字段) */ + @JsonProperty("predict_delivery_time") + private String predictDeliveryTime; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/OrderSkuShareInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/OrderSkuShareInfo.java new file mode 100644 index 0000000000..7912e53348 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/OrderSkuShareInfo.java @@ -0,0 +1,44 @@ +package me.chanjar.weixin.channel.bean.order; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * Sku层分享信息 + * + * @author 北鹤M + */ +@Data +@NoArgsConstructor +public class OrderSkuShareInfo implements Serializable { + + private static final long serialVersionUID = 705312408112124476L; + + /** 分享员openid */ + @JsonProperty("sharer_openid") + private String sharerOpenid; + + /** 分享员unionid */ + @JsonProperty("sharer_unionid") + private String sharerUnionid; + + /** 分享员类型,0:普通分享员,1:店铺分享员 */ + @JsonProperty("sharer_type") + private Integer sharerType; + + /** 分享场景 {@link me.chanjar.weixin.channel.enums.ShareScene} */ + @JsonProperty("share_scene") + private Integer shareScene; + + /** 商品skuid */ + @JsonProperty("sku_id") + private String skuId; + + /** 是否来自企微分享 */ + @JsonProperty("from_wecom") + private Boolean fromWecom; + +} 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 new file mode 100644 index 0000000000..8d5e5aaef0 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/OrderSourceInfo.java @@ -0,0 +1,66 @@ +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 OrderSourceInfo implements Serializable { + + private static final long serialVersionUID = 3131907659419977296L; + + /** + * sku_id + */ + @JsonProperty("sku_id") + private String skuId; + + /** + * 带货账号类型,1:视频号,2:公众号,3:小程序,4:企业微信,5:带货达人,6:服务号,1000:带货机构 + */ + @JsonProperty("account_type") + private Integer accountType; + + /** + * 带货账号id,取决于带货账号类型(分别为视频号id、公众号appid、小程序appid、企业微信id、带货达人appid、服务号appid、带货机构id) + */ + @JsonProperty("account_id") + private String accountId; + + /** + * 账号关联类型,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/order/QualityInsepctInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/QualityInsepctInfo.java new file mode 100644 index 0000000000..64c1102bb2 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/QualityInsepctInfo.java @@ -0,0 +1,22 @@ +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 QualityInsepctInfo implements Serializable { + + private static final long serialVersionUID = 8109819414306253475L; + + /** 质检状态 */ + @JsonProperty("inspect_status") + private Integer inspectStatus; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/RechargeInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/RechargeInfo.java new file mode 100644 index 0000000000..452dd0677c --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/RechargeInfo.java @@ -0,0 +1,28 @@ +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 RechargeInfo implements Serializable { + + /** 虚拟商品充值账号,当account_type=qq或phone_number或mail的时候返回 */ + @JsonProperty("account_no") + private String accountNo; + + /** 账号充值类型,可选项: weixin(微信号),qq(qq),phone_number(电话号码),mail(邮箱) */ + @JsonProperty("account_type") + private String accountType; + + /** 当account_type="weixin"的时候返回 */ + @JsonProperty("wx_openid") + private String wxOpenId; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/TelNumberExtInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/TelNumberExtInfo.java new file mode 100644 index 0000000000..1d9e8b7914 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/TelNumberExtInfo.java @@ -0,0 +1,37 @@ +package me.chanjar.weixin.channel.bean.order; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +/** + * 联系方式信息 + * + * @author imyzt + */ +@Data +public class TelNumberExtInfo { + + /** + * 脱敏手机号 + */ + @JsonProperty("real_tel_number") + private String realTelNumber; + + /** + * 完整的虚拟号码 + */ + @JsonProperty("virtual_tel_number") + private String virtualTelNumber; + + /** + * 主动兑换的虚拟号码过期时间 + */ + @JsonProperty("virtual_tel_expire_time") + private Long virtualTelExpireTime; + + /** + * 主动兑换虚拟号码次数 + */ + @JsonProperty("get_virtual_tel_cnt") + private Long getVirtualTelCnt; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/VirtualNumberInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/VirtualNumberInfo.java new file mode 100644 index 0000000000..217908e27c --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/VirtualNumberInfo.java @@ -0,0 +1,30 @@ +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 VirtualNumberInfo implements Serializable { + + private static final long serialVersionUID = -372834823737476644L; + + /** 虚拟号 */ + @JsonProperty("virtual_number") + private String virtualNumber; + + /** 分机号 */ + @JsonProperty("extension") + private String extension; + + /** 过期时间戳 */ + @JsonProperty("expiration") + private Long expiration; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/VirtualTelNumberResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/VirtualTelNumberResponse.java new file mode 100644 index 0000000000..92f09b59ab --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/VirtualTelNumberResponse.java @@ -0,0 +1,30 @@ +package me.chanjar.weixin.channel.bean.order; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; + +/** + * 兑换虚拟号 返回结果 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class VirtualTelNumberResponse extends WxChannelBaseResponse { + + /** 虚拟号码 */ + @JsonProperty("virtual_tel_number") + private String virtualTelNumber; + + /** 虚拟号码过期时间 */ + @JsonProperty("virtual_tel_expire_time") + private Long virtualTelExpireTime; + + /** 兑换虚拟号码次数 */ + @JsonProperty("get_virtual_tel_cnt") + private Integer getVirtualTelCnt; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/AfterSaleInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/AfterSaleInfo.java new file mode 100644 index 0000000000..693ea68657 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/AfterSaleInfo.java @@ -0,0 +1,22 @@ +package me.chanjar.weixin.channel.bean.product; + + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 商品售后信息 + */ +@Data +@NoArgsConstructor +public class AfterSaleInfo implements Serializable { + + + /** + * 商品的售后地址id,可使用获取地址详情 + */ + @JsonProperty("after_sale_address_id") + private Long afterSaleAddressId; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/DescriptionInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/DescriptionInfo.java new file mode 100644 index 0000000000..b97473e3d3 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/DescriptionInfo.java @@ -0,0 +1,27 @@ +package me.chanjar.weixin.channel.bean.product; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import java.util.List; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 商品详情 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +public class DescriptionInfo implements Serializable { + + private static final long serialVersionUID = 3402153796734747882L; + + /** 商品详情图文,字符类型,最长不超过2000 */ + @JsonProperty("desc") + private String desc; + + /** 商品详情图片,图片类型,最多不超过50张 */ + @JsonProperty("imgs") + private List imgs; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/ExpressInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/ExpressInfo.java new file mode 100644 index 0000000000..0c21d9610e --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/ExpressInfo.java @@ -0,0 +1,28 @@ +package me.chanjar.weixin.channel.bean.product; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 运费信息 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class ExpressInfo implements Serializable { + + private static final long serialVersionUID = 3274035362148612426L; + + /** 运费模板ID(先通过获取运费模板接口merchant/getfreighttemplatelist拿到),若deliver_method=1,则不用填写 */ + @JsonProperty("template_id") + private String templateId; + + /** 商品重量,单位克,若当前运费模版计价方式为[按重量],则必填 */ + @JsonProperty("weight") + private Integer weight; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/ExtraServiceInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/ExtraServiceInfo.java new file mode 100644 index 0000000000..4e9559c565 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/ExtraServiceInfo.java @@ -0,0 +1,39 @@ +package me.chanjar.weixin.channel.bean.product; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * @author Zeyes + */ +@Data +@NoArgsConstructor +public class ExtraServiceInfo implements Serializable { + + private static final long serialVersionUID = -5517806977282063174L; + + /** + * 是否支持七天无理由退货,0-不支持七天无理由, 1-支持七天无理由, 2-支持七天无理由(定制商品除外)。 管理规则请参见七天无理由退货管理规则。类目是否必须支持七天无理由退货, + * 可参考文档获取类目信息中的字段attr.seven_day_return + */ + @JsonProperty("seven_day_return") + private Integer sevenDayReturn; + + /** 先用后付,0-不支持先用后付,1-支持先用后付。若店铺已开通先用后付,支持先用后付的类目商品将在上架后自动打开先用后付。 */ + @JsonProperty("pay_after_use") + private Integer payAfterUse; + + /** 是否支持运费险,0-不支持运费险,1-支持运费险。需要商户开通运费险服务,且当前类目支持运费险才会生效。 */ + @JsonProperty("freight_insurance") + private Integer freightInsurance; + + /** 是否支持假一赔三,0-不支持假一赔三,1-支持假一赔三。 */ + @JsonProperty("fake_one_pay_three") + private Integer fakeOnePayThree; + + /** 是否支持坏损包退,0-不支持坏损包退,1-支持坏损包退。 */ + @JsonProperty("damage_guarantee") + private Integer damageGuarantee; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/LimitInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/LimitInfo.java new file mode 100644 index 0000000000..389773d5e7 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/LimitInfo.java @@ -0,0 +1,28 @@ +package me.chanjar.weixin.channel.bean.product; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 限时购信息 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class LimitInfo implements Serializable { + + private static final long serialVersionUID = -4670198322237114719L; + + /** 限购周期类型,0无限购(默认),1按自然日限购,2按自然周限购,3按自然月限购 */ + @JsonProperty("period_type") + private Integer periodType; + + /** 限购周期类型,0无限购(默认),1按自然日限购,2按自然周限购,3按自然月限购 */ + @JsonProperty("limited_buy_num") + private Integer num; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/ProductQuaInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/ProductQuaInfo.java new file mode 100644 index 0000000000..b411ebe80f --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/ProductQuaInfo.java @@ -0,0 +1,29 @@ +package me.chanjar.weixin.channel.bean.product; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import java.util.List; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 商品资质信息 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class ProductQuaInfo implements Serializable { + + private static final long serialVersionUID = -71766140204505768L; + + /** 商品资质id,对应获取类目信息中的字段product_qua_list[].qua_id */ + @JsonProperty("qua_id") + private String quaId; + + /** 商品资质图片列表 */ + @JsonProperty("qua_url") + private List quaUrl; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/ProductSaleLimitInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/ProductSaleLimitInfo.java new file mode 100644 index 0000000000..9c067cc329 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/ProductSaleLimitInfo.java @@ -0,0 +1,30 @@ +package me.chanjar.weixin.channel.bean.product; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 商品销售库存限制 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class ProductSaleLimitInfo implements Serializable { + + /** 是否受到管控,商品存在售卖限制时,固定返回1 */ + @JsonProperty("is_limited") + private Integer limited; + + /** 售卖限制标题 */ + @JsonProperty("title") + private String title; + + /** 售卖限制描述 */ + @JsonProperty("sub_title") + private String subTitle; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/SkuDeliverInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/SkuDeliverInfo.java new file mode 100644 index 0000000000..d1f10dc5f8 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/SkuDeliverInfo.java @@ -0,0 +1,43 @@ +package me.chanjar.weixin.channel.bean.product; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * sku发货信息 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +public class SkuDeliverInfo implements Serializable { + + private static final long serialVersionUID = 8046963723772755406L; + + /** sku库存情况。0:现货(默认),1:全款预售。部分类目支持全款预售,具体参考文档获取类目信息中的字段attr.pre_sale */ + @JsonProperty("stock_type") + private Integer stockType; + + /** sku发货节点,该字段仅对stock_type=1有效。0:付款后n天发货,1:预售结束后n天发货 */ + @JsonProperty("full_payment_presale_delivery_type") + private Integer fullPaymentPresaleDeliveryType; + + /** sku预售周期开始时间,秒级时间戳,该字段仅对delivery_type=1有效。 */ + @JsonProperty("presale_begin_time") + private Long presaleBeginTime; + + /** + * sku预售周期结束时间,秒级时间戳,该字段仅对delivery_type=1有效。限制:预售结束时间距离现在<=30天, 即presale_end_time - now <= 2592000。预售时间区间<=15天, + * 即presale_end_time - presale_begin_time <= 1296000 + */ + @JsonProperty("presale_end_time") + private Long presaleEndTime; + + /** + * sku发货时效,即付款后/预售结束后{full_payment_presale_delivery_time}天内发货, 该字段仅对stock_type=1时有效。范围是[4, 15]的整数。 + */ + @JsonProperty("full_payment_presale_delivery_time") + private Integer fullPaymentPresaleDeliveryTime; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/SkuFastInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/SkuFastInfo.java new file mode 100644 index 0000000000..b37dfe472c --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/SkuFastInfo.java @@ -0,0 +1,60 @@ +package me.chanjar.weixin.channel.bean.product; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 免审商品更新Sku数据 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@JsonInclude(JsonInclude.Include.NON_NULL) +public class SkuFastInfo implements Serializable { + + /** sku_id */ + @JsonProperty("sku_id") + private String skuId; + + /** 售卖价格,以分为单位,数字类型,最大不超过10000000(1000万元) */ + @JsonProperty("sale_price") + private Integer salePrice; + + @JsonProperty("stock_info") + private StockInfo stockInfo; + + /** sku发货信息 */ + @JsonProperty("sku_deliver_info") + private SkuDeliverInfo skuDeliverInfo; + + /** 是否要删除当前sku */ + @JsonProperty("is_delete") + private Boolean delete; + + /** 商品sku编码 */ + @JsonProperty("sku_code") + private String skuCode; + + /** 更新sku状态 0-默认值;5-上架;11-下架 */ + @JsonProperty("status") + private Integer status; + + + @Data + @NoArgsConstructor + public static class StockInfo implements Serializable { + + /** 修改类型。1: 增加;2:减少;3:设置 */ + @JsonProperty("diff_type") + protected Integer diffType; + + /** 增加、减少或者设置的库存值 */ + @JsonProperty("num") + protected Integer num; + } + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/SkuInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/SkuInfo.java new file mode 100644 index 0000000000..956b188c22 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/SkuInfo.java @@ -0,0 +1,70 @@ +package me.chanjar.weixin.channel.bean.product; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import java.util.List; +import lombok.Data; +import me.chanjar.weixin.channel.bean.base.AttrInfo; + +/** + * SKU信息 + * + * @author Zeyes + */ +@Data +public class SkuInfo implements Serializable { + + private static final long serialVersionUID = -8734396136299597845L; + + /** 商家自定义商品ID */ + @JsonProperty("out_product_id") + private String outProductId; + + /** 商家自定义skuID */ + @JsonProperty("out_sku_id") + private String outSkuId; + + /** sku小图 */ + @JsonProperty("thumb_img") + private String thumbImg; + + /** 售卖价格,以分为单位,数字类型,最大不超过10000000(1000万元) */ + @JsonProperty("sale_price") + private Integer salePrice; + + /** 市场价格,以分为单位,数字类型,最大不超过10000000(1000万元),且必须比sale_price大 */ + @JsonProperty("market_price") + private Integer marketPrice; + + /** 库存,数字类型,最大不超过10000000(1000万) */ + @JsonProperty("stock_num") + private Integer stockNum; + + /** 商品编码,字符类型,最长不超过20 */ + @JsonProperty("sku_code") + private String skuCode; + + /** SKU属性 */ + @JsonProperty("sku_attrs") + private List attrs; + + /** sku发货信息 */ + @JsonProperty("sku_deliver_info") + private SkuDeliverInfo skuDeliverInfo; + + /** skuID */ + @JsonProperty("sku_id") + private String skuId; + + /** sku条形码 */ + @JsonProperty("bar_code") + private String barCode; + + public SkuInfo() { + } + + public SkuInfo(String outProductId, String outSkuId) { + this.outProductId = outProductId; + this.outSkuId = outSkuId; + } +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/SkuStockBatchList.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/SkuStockBatchList.java new file mode 100644 index 0000000000..71f995692f --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/SkuStockBatchList.java @@ -0,0 +1,23 @@ +package me.chanjar.weixin.channel.bean.product; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import java.util.List; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * spu库存列表 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +public class SkuStockBatchList implements Serializable { + private static final long serialVersionUID = -8082428962162052815L; + + /** 库存信息 */ + @JsonProperty("spu_stock_list") + private List spuStockList; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/SkuStockBatchParam.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/SkuStockBatchParam.java new file mode 100644 index 0000000000..93b5cca798 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/SkuStockBatchParam.java @@ -0,0 +1,24 @@ +package me.chanjar.weixin.channel.bean.product; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import java.util.List; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class SkuStockBatchParam implements Serializable { + + private static final long serialVersionUID = 3706326762056220559L; + + /** 商品ID列表 注意这里是 productId ,序列化参数没有写错 */ + @JsonProperty("product_id") + private List productIds; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/SkuStockBatchResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/SkuStockBatchResponse.java new file mode 100644 index 0000000000..eb188bdc79 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/SkuStockBatchResponse.java @@ -0,0 +1,24 @@ +package me.chanjar.weixin.channel.bean.product; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; + +/** + * 批量查询sku库存响应 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class SkuStockBatchResponse extends WxChannelBaseResponse { + + private static final long serialVersionUID = 7745444061881828137L; + + /** 库存信息 */ + @JsonProperty("data") + private SkuStockBatchList data; + } diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/SkuStockInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/SkuStockInfo.java new file mode 100644 index 0000000000..a480d3249b --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/SkuStockInfo.java @@ -0,0 +1,42 @@ +package me.chanjar.weixin.channel.bean.product; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import java.util.List; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 商品库存 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +public class SkuStockInfo implements Serializable { + + private static final long serialVersionUID = 4719729125885685958L; + + /** 通用库存数量 */ + @JsonProperty("normal_stock_num") + private Integer normalStockNum; + + /** 限时抢购库存数量 */ + @JsonProperty("limited_discount_stock_num") + private Integer limitedDiscountStockNum; + + /** 区域库存 */ + @JsonProperty("warehouse_stocks") + private List warehouseStocks; + + /** + * 普通查询:库存总量:通用库存数量 + 限时抢购库存数量 + 区域库存总量 + * 批量查询:库存总量:通用库存数量 + 限时抢购库存数量 + 区域库存数量 + 达人专属计划营销库存数量 + */ + @JsonProperty("total_stock_num") + private Integer totalStockNum; + + /** 达人专属计划营销库存数量 */ + @JsonProperty("finder_stock_num") + private Integer finderTotalNum; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/SkuStockParam.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/SkuStockParam.java new file mode 100644 index 0000000000..cf7374e75e --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/SkuStockParam.java @@ -0,0 +1,34 @@ +package me.chanjar.weixin.channel.bean.product; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * @author Zeyes + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class SkuStockParam implements Serializable { + + private static final long serialVersionUID = -5542939078361208816L; + + /** 内部商品ID */ + @JsonProperty("product_id") + protected String productId; + + /** 内部sku_id */ + @JsonProperty("sku_id") + protected String skuId; + + /** 修改类型。1: 增加;2:减少;3:设置 */ + @JsonProperty("diff_type") + protected Integer diffType; + + /** 增加、减少或者设置的库存值 */ + @JsonProperty("num") + protected Integer num; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/SkuStockResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/SkuStockResponse.java new file mode 100644 index 0000000000..683aece146 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/SkuStockResponse.java @@ -0,0 +1,24 @@ +package me.chanjar.weixin.channel.bean.product; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; + +/** + * 库存信息响应 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class SkuStockResponse extends WxChannelBaseResponse { + + private static final long serialVersionUID = -2156342792354605826L; + + /** 库存信息 */ + @JsonProperty("data") + private SkuStockInfo data; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/SpuCategory.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/SpuCategory.java new file mode 100644 index 0000000000..8adc311f95 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/SpuCategory.java @@ -0,0 +1,22 @@ +package me.chanjar.weixin.channel.bean.product; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 商品类目id + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +public class SpuCategory implements Serializable { + + private static final long serialVersionUID = -8500610555473351789L; + + /** 类目id */ + @JsonProperty("cat_id") + private String id; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/SpuFastInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/SpuFastInfo.java new file mode 100644 index 0000000000..23b1135ba5 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/SpuFastInfo.java @@ -0,0 +1,52 @@ +package me.chanjar.weixin.channel.bean.product; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import java.util.List; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 商品免审更新参数 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@JsonInclude(JsonInclude.Include.NON_NULL) +public class SpuFastInfo implements Serializable { + + /** 商品ID */ + @JsonProperty("product_id") + protected String productId; + + /** SKU列表 */ + @JsonProperty("skus") + protected List skus; + + /** 商品编码 */ + @JsonProperty("spu_code") + protected String spuCode; + + /** 限购信息 */ + @JsonProperty("limit_info") + protected LimitInfo limitInfo; + + /** 运费信息 */ + @JsonProperty("express_info") + protected ExpressInfo expressInfo; + + /** 额外服务 */ + @JsonProperty("extra_service") + protected ExtraServiceInfo extraService; + + /** 发货方式:0-快递发货;1-无需快递,手机号发货;3-无需快递,可选发货账号类型,默认为0,若为无需快递,则无需填写运费模版id */ + @JsonProperty("deliver_method") + private Integer deliverMethod; + + /** 商品待开售信息 */ + @JsonProperty("timing_onsale_info") + private TimingOnSaleInfo timingOnSaleInfo; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/SpuGetResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/SpuGetResponse.java new file mode 100644 index 0000000000..ff15cbf0cb --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/SpuGetResponse.java @@ -0,0 +1,32 @@ +package me.chanjar.weixin.channel.bean.product; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; + +/** + * 商品信息 响应 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class SpuGetResponse extends WxChannelBaseResponse { + + private static final long serialVersionUID = -8955745006296226140L; + + /** 商品线上数据,入参data_type==2时不返回该字段;入参data_type==3且商品未处于上架状态,不返回该字段 */ + @JsonProperty("product") + private SpuInfo product; + + /** 商品草稿数据,入参data_type==1时不返回该字段 */ + @JsonProperty("edit_product") + private SpuInfo editProduct; + + /** 当日售卖上限提醒,当店铺受到售卖管控时返回,没有返回本字段表示没有无额外限制 */ + @JsonProperty("sale_limit_info") + private ProductSaleLimitInfo saleLimitInfo; +} 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 new file mode 100644 index 0000000000..9b2224db94 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/SpuInfo.java @@ -0,0 +1,158 @@ +package me.chanjar.weixin.channel.bean.product; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.List; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.bean.base.AttrInfo; + +/** + * Spu信息 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class SpuInfo extends SpuSimpleInfo { + + private static final long serialVersionUID = -1183209029245287297L; + + /** 标题,字符类型,最少不低于3,最长不超过60。商品标题不得仅为数字、字母、字符或上述三种的组合 */ + @JsonProperty("title") + private String title; + + /** 副标题,最多18字符 */ + @JsonProperty("sub_title") + private String subTitle; + + /** 主图,多张,列表,图片类型,最多不超过9张 */ + @JsonProperty("head_imgs") + private List headImgs; + + /** 发货方式:0-快递发货;1-无需快递,手机号发货;3-无需快递,可选发货账号类型,默认为0,若为无需快递,则无需填写运费模版id */ + @JsonProperty("deliver_method") + private Integer deliverMethod; + + /** + * 发货账号:1-微信openid;2-QQ号;3-手机号;4-邮箱。 + * 可多选,只有deliver_method=3时,本参数有意义。 + * 且当发货账号为微信、QQ和邮箱时,需要更新订单接口读取详情字段,详情参考订单接口的说明 + */ + @JsonProperty("deliver_acct_type") + private List deliverAcctType; + + /** 商品详情 */ + @JsonProperty("desc_info") + private DescriptionInfo descInfo; + + /** 商品类目,大小恒等于3(一二三级类目) */ + @JsonProperty("cats") + private List cats; + + /** 新类目树,商家需要先申请可使用类目 */ + @JsonProperty("cats_v2") + private List catsV2; + + /** 商品参数 */ + @JsonProperty("attrs") + private List attrs; + + /** 商品编码 */ + @JsonProperty("spu_code") + private String spuCode; + + /** 品牌id,无品牌为2100000000 */ + @JsonProperty("brand_id") + private String brandId; + + /** 商品资质图片(最多5张) */ + @JsonProperty("qualifications") + private List qualifications; + + /** 运费信息 */ + @JsonProperty("express_info") + private ExpressInfo expressInfo; + + /** 售后说明 */ + @JsonProperty("aftersale_desc") + private String afterSaleDesc; + + /** 限购信息 */ + @JsonProperty("limited_info") + @JsonInclude(Include.NON_EMPTY) + private LimitInfo limitInfo; + + /** 附加服务 */ + @JsonProperty("extra_service") + private ExtraServiceInfo extraService; + + /** 商品线上状态 {@link me.chanjar.weixin.channel.enums.SpuStatus } */ + @JsonProperty("status") + private Integer status; + + /** 商品草稿状态 */ + @JsonProperty("edit_status") + private Integer editStatus; + + /** 最低价格 */ + @JsonProperty("min_price") + private Integer minPrice; + + /** 创建时间 yyyy-MM-dd HH:mm:ss */ + @JsonProperty("create_time") + private String createTime; + + /** + * 商品草稿最近一次修改时间 + */ + @JsonProperty("edit_time") + private Long editTime; + + /** + * 商品类型。1: 小店普通自营商品;2: 福袋抽奖商品;3: 直播间闪电购商品。 + * 注意: 福袋抽奖、直播间闪电购类型的商品为只读数据,不支持编辑、上架操作,不支持用data_type=2的参数获取。 + */ + @JsonProperty("product_type") + private Integer productType; + + /** + * 商品的售后信息 + */ + @JsonProperty("after_sale_info") + private AfterSaleInfo afterSaleInfo; + + /** + * 当商品类型位福袋抽奖商品(即product_type==2)且该抽奖商品由橱窗的自营商品导入生成时有值, + * 表示导入的来源商品id,其他场景下该字段无值或者值为0 + */ + @JsonProperty("src_product_id") + private String srcProductId; + + /** 商品资质列表 */ + @JsonProperty("product_qua_infos") + private List productQuaInfos; + + /** 尺码表信息 */ + @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; + + /** 商品待开售信息 */ + @JsonProperty("timing_onsale_info") + private TimingOnSaleInfo timingOnSaleInfo; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/SpuListParam.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/SpuListParam.java new file mode 100644 index 0000000000..775bdf990d --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/SpuListParam.java @@ -0,0 +1,32 @@ +package me.chanjar.weixin.channel.bean.product; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import me.chanjar.weixin.channel.bean.base.StreamPageParam; + +/** + * 商品列表查询参数 + * + * @author Zeyes + */ +@Data +@JsonInclude(Include.NON_NULL) +public class SpuListParam extends StreamPageParam { + + private static final long serialVersionUID = -242932365961748404L; + + /** 商品状态 */ + @JsonProperty("status") + private Integer status; + + public SpuListParam() { + } + + public SpuListParam(Integer pageSize, String nextKey, Integer status) { + this.pageSize = pageSize; + this.nextKey = nextKey; + this.status = status; + } +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/SpuListResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/SpuListResponse.java new file mode 100644 index 0000000000..421725c04b --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/SpuListResponse.java @@ -0,0 +1,33 @@ +package me.chanjar.weixin.channel.bean.product; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.List; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; + +/** + * 商品列表信息 响应 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class SpuListResponse extends WxChannelBaseResponse { + + private static final long serialVersionUID = -7448819335418389308L; + + /** 总数 */ + @JsonProperty("total_num") + private Integer totalNum; + + /** 本次翻页的上下文,用于请求下一页 */ + @JsonProperty("next_key") + private String nextKey; + + /** 商品 id 列表 */ + @JsonProperty("product_ids") + private List ids; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/SpuSimpleInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/SpuSimpleInfo.java new file mode 100644 index 0000000000..3e84bb1492 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/SpuSimpleInfo.java @@ -0,0 +1,29 @@ +package me.chanjar.weixin.channel.bean.product; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import java.util.List; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * @author Zeyes + */ +@Data +@NoArgsConstructor +public class SpuSimpleInfo implements Serializable { + + private static final long serialVersionUID = 5583726432139404883L; + + /** 商品ID */ + @JsonProperty("product_id") + protected String productId; + + /** 商家自定义商品ID */ + @JsonProperty("out_product_id") + protected String outProductId; + + /** sku数组 */ + @JsonProperty("skus") + protected List skus; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/SpuSizeChart.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/SpuSizeChart.java new file mode 100644 index 0000000000..4e34ccfac8 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/SpuSizeChart.java @@ -0,0 +1,27 @@ +package me.chanjar.weixin.channel.bean.product; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import java.util.List; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 尺码表信息 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +public class SpuSizeChart implements Serializable { + + private static final long serialVersionUID = -5019617420608575610L; + + /** 是否支持尺码表 */ + @JsonProperty("enable") + private Boolean enable; + + /** 尺码表 */ + @JsonProperty("specification_list") + private List specificationList; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/SpuSizeChartItem.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/SpuSizeChartItem.java new file mode 100644 index 0000000000..7ea4d0a66b --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/SpuSizeChartItem.java @@ -0,0 +1,55 @@ +package me.chanjar.weixin.channel.bean.product; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import java.util.List; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 尺码表 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +public class SpuSizeChartItem implements Serializable { + + private static final long serialVersionUID = -3757716378584654974L; + + /** 尺码属性名称 */ + @JsonProperty("name") + private String name; + + /** 尺码属性值的单位 */ + @JsonProperty("unit") + private String unit; + + /** 尺码属性值是否为区间 */ + @JsonProperty("is_range") + private Boolean range; + + /** 尺码值与尺码属性值的映射列表 */ + @JsonProperty("value_list") + private List valueList; + + @Data + @NoArgsConstructor + public static class ValueRange implements Serializable { + /** 尺码值 */ + @JsonProperty("key") + private String key; + + /** 尺码属性值;尺码属性值为非区间时返回 */ + @JsonProperty("value") + private String value; + + /** 尺码属性值的左边界;尺码属性值为区间时返回 */ + @JsonProperty("left") + private String left; + + /** 尺码属性值的右边界;尺码属性值为区间时返回 */ + @JsonProperty("right") + private String right; + } +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/SpuStockInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/SpuStockInfo.java new file mode 100644 index 0000000000..4564f069b8 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/SpuStockInfo.java @@ -0,0 +1,25 @@ +package me.chanjar.weixin.channel.bean.product; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import java.util.List; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * SPU库存信息 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +public class SpuStockInfo implements Serializable { + + /** 商品ID */ + @JsonProperty("product_id") + protected String productId; + + /** sku库存 */ + @JsonProperty("sku_stock") + private List skuStock; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/SpuUpdateInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/SpuUpdateInfo.java new file mode 100644 index 0000000000..f6214c5d78 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/SpuUpdateInfo.java @@ -0,0 +1,24 @@ +package me.chanjar.weixin.channel.bean.product; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +/** + * 商品更新数据 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +public class SpuUpdateInfo extends SpuInfo { + + /** 添加完成后是否立即上架。1:是;0:否;默认0 */ + @JsonProperty("listing") + private Integer listing; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/SpuUpdateResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/SpuUpdateResponse.java new file mode 100644 index 0000000000..815ee4412c --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/SpuUpdateResponse.java @@ -0,0 +1,25 @@ +package me.chanjar.weixin.channel.bean.product; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; + +/** + * 商品信息 响应 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class SpuUpdateResponse extends WxChannelBaseResponse { + + private static final long serialVersionUID = -7072796795527767292L; + + /** 商品信息 */ + @JsonProperty("data") + private SpuInfo data; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/TimingOnSaleInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/TimingOnSaleInfo.java new file mode 100644 index 0000000000..29270d426c --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/TimingOnSaleInfo.java @@ -0,0 +1,36 @@ +package me.chanjar.weixin.channel.bean.product; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * 商品待开售信息 + * + * @author chu + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class TimingOnSaleInfo implements Serializable { + + /** 状态枚举 0-没有待开售;1-待开售 */ + @JsonProperty("status") + private Integer status; + + /** 开售时间,秒级时间戳,0为未配置时间 */ + @JsonProperty("onsale_time") + private Long onSaleTime; + + /** 是否隐藏价格 0-不隐藏;1-隐藏 */ + @JsonProperty("is_hide_price") + private Integer isHidePrice; + + /** 待开售任务ID,可用于请求立即开售 */ + @JsonProperty("task_id") + private Integer taskId; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/WarehouseStockInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/WarehouseStockInfo.java new file mode 100644 index 0000000000..b0235534bb --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/WarehouseStockInfo.java @@ -0,0 +1,30 @@ +package me.chanjar.weixin.channel.bean.product; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 区域库存 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +public class WarehouseStockInfo implements Serializable { + + private static final long serialVersionUID = 3184902895765107425L; + + /** 区域库存外部id */ + @JsonProperty("out_warehouse_id") + private String outWarehouseId; + + /** 区域库存数量 */ + @JsonProperty("num") + private Integer num; + + /** 区域库存的锁定库存(已下单未支付的库存)数量 */ + @JsonProperty("lock_stock") + private Integer lockStock; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/link/ProductH5UrlResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/link/ProductH5UrlResponse.java new file mode 100644 index 0000000000..0dee49f165 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/link/ProductH5UrlResponse.java @@ -0,0 +1,22 @@ +package me.chanjar.weixin.channel.bean.product.link; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; + +/** + * 商品H5短链 结果 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class ProductH5UrlResponse extends WxChannelBaseResponse { + + /** 商品H5短链 */ + @JsonProperty("product_h5url") + private String productH5url; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/link/ProductQrCodeResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/link/ProductQrCodeResponse.java new file mode 100644 index 0000000000..a6876b78f1 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/link/ProductQrCodeResponse.java @@ -0,0 +1,22 @@ +package me.chanjar.weixin.channel.bean.product.link; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; + +/** + * 商品二维码 结果 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class ProductQrCodeResponse extends WxChannelBaseResponse { + + /** 商品二维码 */ + @JsonProperty("product_qrcode") + private String productQrcode; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/link/ProductTagLinkResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/link/ProductTagLinkResponse.java new file mode 100644 index 0000000000..59712130d8 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/link/ProductTagLinkResponse.java @@ -0,0 +1,22 @@ +package me.chanjar.weixin.channel.bean.product.link; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; + +/** + * 商品口令 结果 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class ProductTagLinkResponse extends WxChannelBaseResponse { + + /** 商品口令 */ + @JsonProperty("product_taglink") + private String productTaglink; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/sharer/FinderSceneInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/sharer/FinderSceneInfo.java new file mode 100644 index 0000000000..76d54d90c9 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/sharer/FinderSceneInfo.java @@ -0,0 +1,38 @@ +package me.chanjar.weixin.channel.bean.sharer; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 视频号场景信息 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +public class FinderSceneInfo implements Serializable { + + private static final long serialVersionUID = 5298261857489231549L; + /** 视频号唯一标识 */ + @JsonProperty("promoter_id") + private String promoterId; + + /** 视频号昵称 */ + @JsonProperty("finder_nickname") + private String finderNickname; + + /** 直播间唯一标识 */ + @JsonProperty("live_export_id") + private String liveExportId; + + /** 短视频唯一标识 */ + @JsonProperty("video_export_id") + private String videoExportId; + + /** 短视频标题 */ + @JsonProperty("video_title") + private String videoTitle; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/sharer/SharerBindResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/sharer/SharerBindResponse.java new file mode 100644 index 0000000000..4a0f8f2bb4 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/sharer/SharerBindResponse.java @@ -0,0 +1,31 @@ +package me.chanjar.weixin.channel.bean.sharer; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; + +/** + * 分享员绑定响应 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class SharerBindResponse extends WxChannelBaseResponse { + + private static final long serialVersionUID = 7078787380791500161L; + /** 邀请二维码的图片二进制base64编码,3天有效 */ + @JsonProperty("qrcode_img_base64") + private String qrcodeImgBase64; + + public String getQrcodeImgBase64() { + return qrcodeImgBase64; + } + + public void setQrcodeImgBase64(String qrcodeImgBase64) { + this.qrcodeImgBase64 = qrcodeImgBase64; + } +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/sharer/SharerInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/sharer/SharerInfo.java new file mode 100644 index 0000000000..73aaeddbd4 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/sharer/SharerInfo.java @@ -0,0 +1,39 @@ +package me.chanjar.weixin.channel.bean.sharer; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.Data; +import lombok.NoArgsConstructor; + + +/** + * 分享员信息 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +public class SharerInfo implements Serializable { + + private static final long serialVersionUID = -4373597470611742887L; + /** 分享员openid */ + @JsonProperty("openid") + private String openid; + + /** 分享员unionid */ + @JsonProperty("unionid") + private String unionid; + + /** 分享员openid */ + @JsonProperty("nickname") + private String nickname; + + /** 绑定时间 */ + @JsonProperty("bind_time") + private Long bindTime; + + /** 分享员类型 {@link me.chanjar.weixin.channel.enums.SharerType} */ + @JsonProperty("sharer_type") + private Integer sharerType; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/sharer/SharerInfoResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/sharer/SharerInfoResponse.java new file mode 100644 index 0000000000..554109c1a9 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/sharer/SharerInfoResponse.java @@ -0,0 +1,25 @@ +package me.chanjar.weixin.channel.bean.sharer; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.List; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; + +/** + * 分享员信息响应 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class SharerInfoResponse extends WxChannelBaseResponse { + + private static final long serialVersionUID = 1090517907546557929L; + /** 分享员信息 */ + @JsonProperty("sharer_info_list") + private List list; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/sharer/SharerListParam.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/sharer/SharerListParam.java new file mode 100644 index 0000000000..97ab2797b8 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/sharer/SharerListParam.java @@ -0,0 +1,30 @@ +package me.chanjar.weixin.channel.bean.sharer; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import me.chanjar.weixin.channel.bean.base.PageParam; + +/** + * @author Zeyes + */ +@Data +@EqualsAndHashCode(callSuper = true) +@JsonInclude(Include.NON_NULL) +public class SharerListParam extends PageParam { + + private static final long serialVersionUID = -2454284952706596246L; + /** 分享员类型 {@link me.chanjar.weixin.channel.enums.SharerType} */ + @JsonProperty("sharer_type") + private Integer sharerType; + + public SharerListParam() { + } + + public SharerListParam(Integer page, Integer pageSize, Integer sharerType) { + super(page, pageSize); + this.sharerType = sharerType; + } +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/sharer/SharerOrder.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/sharer/SharerOrder.java new file mode 100644 index 0000000000..682753e64f --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/sharer/SharerOrder.java @@ -0,0 +1,70 @@ +package me.chanjar.weixin.channel.bean.sharer; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 分享员订单 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +public class SharerOrder implements Serializable { + + private static final long serialVersionUID = 1528673402572025670L; + /** + * 订单号 + */ + @JsonProperty("order_id") + private String orderId; + + /** + * 分享场景 {@link me.chanjar.weixin.channel.enums.ShareScene} + */ + @JsonProperty("share_scene") + private Integer sharerScene; + + /** + * 分享员openid + */ + @JsonProperty("sharer_openid") + private String sharerOpenid; + + /** + * 分享员类型 {@link me.chanjar.weixin.channel.enums.SharerType} + */ + @JsonProperty("sharer_type") + private Integer sharerType; + + /** + * 商品sku_id + */ + @JsonProperty("sku_id") + private String skuId; + + + /** + * 商品唯一id + */ + @JsonProperty("product_id") + private String productId; + + + /** + * 是否从企微分享 + */ + @JsonProperty("from_wecom") + private Boolean fromWxWork; + + + /** + * 视频号场景信息 + */ + @JsonProperty("finder_scene_info") + private FinderSceneInfo sceneInfo; + + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/sharer/SharerOrderParam.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/sharer/SharerOrderParam.java new file mode 100644 index 0000000000..5ada6e3bcf --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/sharer/SharerOrderParam.java @@ -0,0 +1,36 @@ +package me.chanjar.weixin.channel.bean.sharer; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.bean.base.PageParam; + +/** + * @author Zeyes + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +@JsonInclude(Include.NON_NULL) +public class SharerOrderParam extends PageParam { + + private static final long serialVersionUID = 5240085870008898601L; + /** 分享员openid */ + @JsonProperty("openid") + private String openid; + + /** 分享场景 */ + @JsonProperty("share_scene") + private Integer shareScene; + + /** 订单创建开始时间 */ + @JsonProperty("start_time") + private Long startTime; + + /** 订单创建结束时间 */ + @JsonProperty("end_time") + private Long endTime; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/sharer/SharerOrderResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/sharer/SharerOrderResponse.java new file mode 100644 index 0000000000..c84da4114b --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/sharer/SharerOrderResponse.java @@ -0,0 +1,24 @@ +package me.chanjar.weixin.channel.bean.sharer; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.List; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; + +/** + * 分享员订单响应 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class SharerOrderResponse extends WxChannelBaseResponse { + + private static final long serialVersionUID = 2807417719466178508L; + /** 分享员订单 */ + @JsonProperty("order_list") + private List list; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/sharer/SharerSearchParam.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/sharer/SharerSearchParam.java new file mode 100644 index 0000000000..a2669775cb --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/sharer/SharerSearchParam.java @@ -0,0 +1,32 @@ +package me.chanjar.weixin.channel.bean.sharer; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.Data; + +/** + * @author Zeyes + */ +@Data +@JsonInclude(Include.NON_NULL) +public class SharerSearchParam implements Serializable { + + private static final long serialVersionUID = -6763899740755735718L; + /** 分享员openid */ + @JsonProperty("openid") + private String openid; + + /** 微信号 */ + @JsonProperty("username") + private String username; + + public SharerSearchParam() { + } + + public SharerSearchParam(String openid, String username) { + this.openid = openid; + this.username = username; + } +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/sharer/SharerSearchResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/sharer/SharerSearchResponse.java new file mode 100644 index 0000000000..52631521df --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/sharer/SharerSearchResponse.java @@ -0,0 +1,40 @@ +package me.chanjar.weixin.channel.bean.sharer; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; + +/** + * 分享员绑定响应 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class SharerSearchResponse extends WxChannelBaseResponse { + + private static final long serialVersionUID = -5346019069466917659L; + /** 分享员openid */ + @JsonProperty("openid") + private String openid; + + /** 分享员unionid */ + @JsonProperty("unionid") + private String unionid; + + /** 分享员openid */ + @JsonProperty("nickname") + private String nickname; + + /** 绑定时间 */ + @JsonProperty("bind_time") + private Long bindTime; + + /** 分享员类型 {@link me.chanjar.weixin.channel.enums.SharerType} */ + @JsonProperty("sharer_type") + private Integer sharerType; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/sharer/SharerUnbindParam.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/sharer/SharerUnbindParam.java new file mode 100644 index 0000000000..cd8f21d409 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/sharer/SharerUnbindParam.java @@ -0,0 +1,25 @@ +package me.chanjar.weixin.channel.bean.sharer; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import java.util.List; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * @author Zeyes + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@JsonInclude(Include.NON_NULL) +public class SharerUnbindParam implements Serializable { + + private static final long serialVersionUID = -4515654492511136037L; + /** openid列表 */ + @JsonProperty("openid_list") + private List openIds; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/sharer/SharerUnbindResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/sharer/SharerUnbindResponse.java new file mode 100644 index 0000000000..9166bc0b58 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/sharer/SharerUnbindResponse.java @@ -0,0 +1,32 @@ +package me.chanjar.weixin.channel.bean.sharer; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.List; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; + +/** + * 分享员解绑响应 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class SharerUnbindResponse extends WxChannelBaseResponse { + + private static final long serialVersionUID = -2395560383862569445L; + /** 成功列表 */ + @JsonProperty("success_openid") + private List successList; + + /** 失败列表,可重试 */ + @JsonProperty("fail_openid") + private List failList; + + /** 拒绝列表,不可重试(openid错误,未到解绑时间等) */ + @JsonProperty("refuse_openid") + private List refuseList; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/shop/ShopInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/shop/ShopInfo.java new file mode 100644 index 0000000000..12b4c684c6 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/shop/ShopInfo.java @@ -0,0 +1,36 @@ +package me.chanjar.weixin.channel.bean.shop; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 店铺信息 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +public class ShopInfo implements Serializable { + + /** 店铺名称 */ + @JsonProperty("nickname") + private String nickname; + + /** 店铺头像URL */ + @JsonProperty("headimg_url") + private String headImgUrl; + + /** 店铺类型,目前为"企业"或"个体工商户" */ + @JsonProperty("subject_type") + private String subjectType; + + /** 店铺状态,目前为 opening 或 open_finished 或 closing 或 close_finished */ + @JsonProperty("status") + private String status; + + /** 店铺原始ID */ + @JsonProperty("username") + private String username; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/shop/ShopInfoResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/shop/ShopInfoResponse.java new file mode 100644 index 0000000000..b4317ad3c0 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/shop/ShopInfoResponse.java @@ -0,0 +1,19 @@ +package me.chanjar.weixin.channel.bean.shop; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; + +/** + * 店铺基本信息响应 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +public class ShopInfoResponse extends WxChannelBaseResponse { + + @JsonProperty("info") + private ShopInfo info; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/token/StableTokenParam.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/token/StableTokenParam.java new file mode 100644 index 0000000000..8bcacb649b --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/token/StableTokenParam.java @@ -0,0 +1,34 @@ +package me.chanjar.weixin.channel.bean.token; + +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; + +/** + * 稳定版access_token,请求参数 + * + * @author asushiye + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@JsonInclude(JsonInclude.Include.NON_NULL) +public class StableTokenParam implements Serializable { + private static final long serialVersionUID = 6849364823232834171L; + + @JsonProperty("grant_type") + private String grantType; + + @JsonProperty("appid") + private String appId; + + @JsonProperty("secret") + private String secret; + + @JsonProperty("force_refresh") + private Boolean forceRefresh; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/vip/ScoreInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/vip/ScoreInfo.java new file mode 100644 index 0000000000..ac2d2f9763 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/vip/ScoreInfo.java @@ -0,0 +1,23 @@ +package me.chanjar.weixin.channel.bean.vip; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * 视频号小店-会员功能 - 订单详情 + * + * @author asushiye + * + */ +@Data +@NoArgsConstructor +public class ScoreInfo implements Serializable { + + private static final long serialVersionUID = -3290653233070826576L; + /** 积分 */ + @JsonProperty("score") + protected String score; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/vip/UserGradeInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/vip/UserGradeInfo.java new file mode 100644 index 0000000000..b015773480 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/vip/UserGradeInfo.java @@ -0,0 +1,27 @@ +package me.chanjar.weixin.channel.bean.vip; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * 视频号小店-会员功能 - 用户等级信息 + * + * @author asushiye + * + */ +@Data +@NoArgsConstructor +public class UserGradeInfo implements Serializable { + + private static final long serialVersionUID = -8040963202754069865L; + /** 等级编号 */ + @JsonProperty("grade") + protected Integer grade; + + /** 用户经验值 */ + @JsonProperty("experience_value") + protected String experienceValue; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/vip/UserInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/vip/UserInfo.java new file mode 100644 index 0000000000..1104d532f1 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/vip/UserInfo.java @@ -0,0 +1,23 @@ +package me.chanjar.weixin.channel.bean.vip; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * 视频号小店-会员功能 - 订单详情 + * + * @author asushiye + * + */ +@Data +@NoArgsConstructor +public class UserInfo implements Serializable { + + private static final long serialVersionUID = 8523354700203385190L; + /** 手机号 */ + @JsonProperty("phone_number") + protected String phoneNumber; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/vip/VipGradeParam.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/vip/VipGradeParam.java new file mode 100644 index 0000000000..5f5004f35c --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/vip/VipGradeParam.java @@ -0,0 +1,31 @@ +package me.chanjar.weixin.channel.bean.vip; + +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; + +/** + * @author : zhenyun.su + * @since : 2023/10/8 + + */ + +@Data +@NoArgsConstructor +@JsonInclude(JsonInclude.Include.NON_NULL) +@AllArgsConstructor +public class VipGradeParam implements Serializable { + + + private static final long serialVersionUID = 8672089025435220864L; + @JsonProperty("openid") + private String openId; + + @JsonProperty("grade") + private Integer grade; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/vip/VipInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/vip/VipInfo.java new file mode 100644 index 0000000000..64eafbc3a4 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/vip/VipInfo.java @@ -0,0 +1,47 @@ +package me.chanjar.weixin.channel.bean.vip; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * 视频号小店-会员功能 - 订单详情 + * + * @author asushiye + * + * "info": { + * "openid": "OPENID", + * "unionid": "UNIONID", + * "user_info": { + * "phone_number": "123456789" + * }, + * "user_grade_info": { + * "grade": 1, + * "experience_value": "100" + * } + * } + */ +@Data +@NoArgsConstructor +public class VipInfo implements Serializable { + private static final long serialVersionUID = -215590991862774701L; + + /** 视频号openid */ + @JsonProperty("openid") + protected String openId; + + /** 视频号union_id */ + @JsonProperty("union_id") + protected String unionId; + + /** 用户信息 */ + @JsonProperty("user_info") + protected UserInfo userInfo; + + /** 用户等级信息 */ + @JsonProperty("user_grade_info") + protected UserGradeInfo userGradeInfo; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/vip/VipInfoParam.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/vip/VipInfoParam.java new file mode 100644 index 0000000000..09c28f5510 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/vip/VipInfoParam.java @@ -0,0 +1,27 @@ +package me.chanjar.weixin.channel.bean.vip; + +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; + +/** + * @author : zhenyun.su + * @since : 2023/10/8 + */ + +@Data +@NoArgsConstructor +@JsonInclude(JsonInclude.Include.NON_NULL) +@AllArgsConstructor +public class VipInfoParam implements Serializable { + private static final long serialVersionUID = -4196252299609288196L; + @JsonProperty("openid") + private String openId; + + @JsonProperty("need_phone_number") + private Boolean needPhoneNumber; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/vip/VipInfoResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/vip/VipInfoResponse.java new file mode 100644 index 0000000000..3411eef038 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/vip/VipInfoResponse.java @@ -0,0 +1,20 @@ +package me.chanjar.weixin.channel.bean.vip; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; + +/** + * @author : zhenyun.su + * @since : 2023/10/8 + */ + +@Data +@NoArgsConstructor +public class VipInfoResponse extends WxChannelBaseResponse { + + private static final long serialVersionUID = -2439510304690862381L; + @JsonProperty("info") + private VipInfo vipInfo; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/vip/VipListParam.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/vip/VipListParam.java new file mode 100644 index 0000000000..d23c41fcd5 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/vip/VipListParam.java @@ -0,0 +1,32 @@ +package me.chanjar.weixin.channel.bean.vip; + +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; + +/** + * @author : zhenyun.su + * @since : 2023/10/8 + */ + +@Data +@NoArgsConstructor +@JsonInclude(JsonInclude.Include.NON_NULL) +@AllArgsConstructor +public class VipListParam implements Serializable { + + private static final long serialVersionUID = 7503422865410116202L; + @JsonProperty("need_phone_number") + private Boolean needPhoneNumber; + + @JsonProperty("page_num") + private Integer pageNum; + + @JsonProperty("page_size") + private Integer pageSize; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/vip/VipListResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/vip/VipListResponse.java new file mode 100644 index 0000000000..0e213f8d19 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/vip/VipListResponse.java @@ -0,0 +1,25 @@ +package me.chanjar.weixin.channel.bean.vip; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; + +import java.util.List; + +/** + * @author : zhenyun.su + * @since : 2023/10/8 + */ + +@Data +@NoArgsConstructor +public class VipListResponse extends WxChannelBaseResponse { + + private static final long serialVersionUID = -8127372979925053579L; + @JsonProperty("list") + private List vipInfos; + + @JsonProperty("total_num") + private Long totalNum; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/vip/VipOpenIdParam.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/vip/VipOpenIdParam.java new file mode 100644 index 0000000000..8f52ded878 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/vip/VipOpenIdParam.java @@ -0,0 +1,24 @@ +package me.chanjar.weixin.channel.bean.vip; + +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; + +/** + * @author : zhenyun.su + * @since : 2023/10/8 + */ + +@Data +@NoArgsConstructor +@JsonInclude(JsonInclude.Include.NON_NULL) +@AllArgsConstructor +public class VipOpenIdParam implements Serializable { + private static final long serialVersionUID = -7924178026258012317L; + @JsonProperty("openid") + private String openId; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/vip/VipScoreParam.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/vip/VipScoreParam.java new file mode 100644 index 0000000000..51b679f393 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/vip/VipScoreParam.java @@ -0,0 +1,39 @@ +package me.chanjar.weixin.channel.bean.vip; + +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; + +/** + * @author : zhenyun.su + * @since : 2023/10/8 + * { + * "openid": "OPENID", + * "score": "100", + * "remark": "备注", + * "request_id": "REQUEST_ID" + * } + */ + +@Data +@NoArgsConstructor +@JsonInclude(JsonInclude.Include.NON_NULL) +@AllArgsConstructor +public class VipScoreParam implements Serializable { + private static final long serialVersionUID = -4122983978977407168L; + @JsonProperty("openid") + private String openId; + + @JsonProperty("score") + private String score; + + @JsonProperty("remark") + private String remark; + + @JsonProperty("request_id") + private String requestId; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/vip/VipScoreResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/vip/VipScoreResponse.java new file mode 100644 index 0000000000..da356db74f --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/vip/VipScoreResponse.java @@ -0,0 +1,20 @@ +package me.chanjar.weixin.channel.bean.vip; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; + +/** + * @author : zhenyun.su + * @since : 2023/10/8 + */ + +@Data +@NoArgsConstructor +public class VipScoreResponse extends WxChannelBaseResponse { + + private static final long serialVersionUID = -7252972818862693546L; + @JsonProperty("info") + private ScoreInfo scoreInfo; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/warehouse/LocationPriorityResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/warehouse/LocationPriorityResponse.java new file mode 100644 index 0000000000..5959cb746d --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/warehouse/LocationPriorityResponse.java @@ -0,0 +1,25 @@ +package me.chanjar.weixin.channel.bean.warehouse; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.List; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; + +/** + * 仓库优先级响应 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class LocationPriorityResponse extends WxChannelBaseResponse { + + private static final long serialVersionUID = -4037484169497319150L; + + /** 按照out_warehouse_id排序优先级从高到低 */ + @JsonProperty("priority_sort") + private List prioritySort; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/warehouse/PriorityLocationParam.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/warehouse/PriorityLocationParam.java new file mode 100644 index 0000000000..0b304487a7 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/warehouse/PriorityLocationParam.java @@ -0,0 +1,24 @@ +package me.chanjar.weixin.channel.bean.warehouse; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.List; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +/** + * 带优先级的仓库区域 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class PriorityLocationParam extends WarehouseLocation { + + private static final long serialVersionUID = -3087702364669180903L; + + /** 按照out_warehouse_id排序优先级从高到低 */ + @JsonProperty("priority_sort") + private List prioritySort; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/warehouse/StockGetParam.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/warehouse/StockGetParam.java new file mode 100644 index 0000000000..99e00a4801 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/warehouse/StockGetParam.java @@ -0,0 +1,29 @@ +package me.chanjar.weixin.channel.bean.warehouse; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * @author Zeyes + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class StockGetParam implements Serializable { + + private static final long serialVersionUID = -4144913434092446664L; + /** 商品ID */ + @JsonProperty("product_id") + private String productId; + + /** skuID */ + @JsonProperty("sku_id") + private String skuId; + + /** 外部仓库ID */ + @JsonProperty("out_warehouse_id") + private String outWarehouseId; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/warehouse/UpdateLocationParam.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/warehouse/UpdateLocationParam.java new file mode 100644 index 0000000000..5b71c0a4b4 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/warehouse/UpdateLocationParam.java @@ -0,0 +1,29 @@ +package me.chanjar.weixin.channel.bean.warehouse; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import java.util.List; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 仓库区域 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class UpdateLocationParam implements Serializable { + + private static final long serialVersionUID = 6102771485047925091L; + + /** 外部仓库ID */ + @JsonProperty("out_warehouse_id") + private String outWarehouseId; + + /** 覆盖区域 */ + @JsonProperty("cover_locations") + private List coverLocations; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/warehouse/Warehouse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/warehouse/Warehouse.java new file mode 100644 index 0000000000..7ca07e637f --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/warehouse/Warehouse.java @@ -0,0 +1,36 @@ +package me.chanjar.weixin.channel.bean.warehouse; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import java.util.List; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 仓库 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +public class Warehouse implements Serializable { + + private static final long serialVersionUID = -2322154583471063637L; + + /** 外部仓库ID,一个店铺下,同一个外部ID只能创建一个仓库,最大32字符 */ + @JsonProperty("out_warehouse_id") + private String outWarehouseId; + + /** 仓库名称 */ + @JsonProperty("name") + private String name; + + /** 仓库介绍 */ + @JsonProperty("intro") + private String intro; + + /** 覆盖区域,可以在创建后添加 */ + @JsonProperty("cover_locations") + private List coverLocations; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/warehouse/WarehouseIdsResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/warehouse/WarehouseIdsResponse.java new file mode 100644 index 0000000000..57c989a56b --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/warehouse/WarehouseIdsResponse.java @@ -0,0 +1,48 @@ +package me.chanjar.weixin.channel.bean.warehouse; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.List; +import java.util.Map; +import lombok.Data; +import lombok.EqualsAndHashCode; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; + +/** + * 仓库id列表响应 + * + * @author Zeyes + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class WarehouseIdsResponse extends WxChannelBaseResponse { + + private static final long serialVersionUID = 3974529583232187473L; + + /** 外部仓库ID列表 */ + @JsonProperty("out_warehouse_ids") + private List ids; + + /** 本次翻页的上下文,用于请求下一页,如果是空,则当前是最后一页 */ + @JsonProperty("next_key") + private String nextKey; + + public WarehouseIdsResponse() { + } + + @JsonProperty("data") + private void unpackNameFromNestedObject(Map map) { + if (map == null) { + return; + } + Object obj = map.get("out_warehouse_ids"); + if (obj != null) { + if (obj instanceof List) { + this.ids = (List) obj; + } + } + obj = map.get("next_key"); + if (obj != null) { + this.nextKey = (String) obj; + } + } +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/warehouse/WarehouseLocation.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/warehouse/WarehouseLocation.java new file mode 100644 index 0000000000..33309522bb --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/warehouse/WarehouseLocation.java @@ -0,0 +1,36 @@ +package me.chanjar.weixin.channel.bean.warehouse; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 仓库区域 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class WarehouseLocation implements Serializable { + + private static final long serialVersionUID = 1626579682640060352L; + + /** 省份地址编码 */ + @JsonProperty("address_id1") + private Integer addressId1; + + /** 市地址编码 */ + @JsonProperty("address_id2") + private Integer addressId2; + + /** 区地址编码 */ + @JsonProperty("address_id3") + private Integer addressId3; + + /** 街道地址编码 */ + @JsonProperty("address_id4") + private Integer addressId4; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/warehouse/WarehouseLocationParam.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/warehouse/WarehouseLocationParam.java new file mode 100644 index 0000000000..2b64e55dea --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/warehouse/WarehouseLocationParam.java @@ -0,0 +1,22 @@ +package me.chanjar.weixin.channel.bean.warehouse; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; +import lombok.Data; + +/** + * @author Zeyes + */ +@Data +@JsonInclude(Include.NON_NULL) +public class WarehouseLocationParam extends WarehouseLocation { + + private static final long serialVersionUID = 3347484433136057123L; + + public WarehouseLocationParam() { + } + + public WarehouseLocationParam(Integer addressId1, Integer addressId2, Integer addressId3, Integer addressId4) { + super(addressId1, addressId2, addressId3, addressId4); + } +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/warehouse/WarehouseParam.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/warehouse/WarehouseParam.java new file mode 100644 index 0000000000..77ac1a8134 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/warehouse/WarehouseParam.java @@ -0,0 +1,20 @@ +package me.chanjar.weixin.channel.bean.warehouse; + +import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +/** + * 仓库 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +public class WarehouseParam extends Warehouse { + + private static final long serialVersionUID = -3412047348380785225L; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/warehouse/WarehouseResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/warehouse/WarehouseResponse.java new file mode 100644 index 0000000000..fa96771d67 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/warehouse/WarehouseResponse.java @@ -0,0 +1,21 @@ +package me.chanjar.weixin.channel.bean.warehouse; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; + +/** + * 仓库响应 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +public class WarehouseResponse extends WxChannelBaseResponse { + + private static final long serialVersionUID = 3206095869486573824L; + /** 仓库库存 */ + @JsonProperty("data") + private Warehouse data; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/warehouse/WarehouseStockParam.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/warehouse/WarehouseStockParam.java new file mode 100644 index 0000000000..5a4354504d --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/warehouse/WarehouseStockParam.java @@ -0,0 +1,22 @@ +package me.chanjar.weixin.channel.bean.warehouse; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.bean.product.SkuStockParam; + +/** + * 库存参数 + * + * @author Zeyes + */ +@Data +@NoArgsConstructor +public class WarehouseStockParam extends SkuStockParam { + + private static final long serialVersionUID = -5121207621628542490L; + + /** 外部仓库ID */ + @JsonProperty("out_warehouse_id") + private String outWarehouseId; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/warehouse/WarehouseStockResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/warehouse/WarehouseStockResponse.java new file mode 100644 index 0000000000..64d0d2b5b0 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/warehouse/WarehouseStockResponse.java @@ -0,0 +1,34 @@ +package me.chanjar.weixin.channel.bean.warehouse; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.Map; +import lombok.Data; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; + +/** + * 仓库库存响应 + * + * @author Zeyes + */ +@Data +public class WarehouseStockResponse extends WxChannelBaseResponse { + + private static final long serialVersionUID = 1810645965041317763L; + /** 仓库库存 */ + @JsonProperty("num") + private Integer num; + + public WarehouseStockResponse() { + } + + @JsonProperty("data") + private void unpackNameFromNestedObject(Map map) { + if (map == null) { + return; + } + Object obj = map.get("num"); + if (obj != null) { + this.num = (Integer) obj; + } + } +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/window/request/AddWindowProductRequest.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/window/request/AddWindowProductRequest.java new file mode 100644 index 0000000000..b069826d17 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/window/request/AddWindowProductRequest.java @@ -0,0 +1,39 @@ +package me.chanjar.weixin.channel.bean.window.request; + + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 上架商品到橱窗 + * @author imyzt + * @date 2024/01/27 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@JsonInclude(JsonInclude.Include.NON_NULL) +public class AddWindowProductRequest { + + /** + * 橱窗商品ID + */ + @JsonProperty("product_id") + private String productId; + + /** + * 商品来源店铺的appid + */ + @JsonProperty("appid") + private String appid; + + /** + * 是否需要在个人橱窗页隐藏 (默认为false) + */ + @JsonProperty("is_hide_for_window") + private Boolean isHideForWindow; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/window/request/GetWindowProductListRequest.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/window/request/GetWindowProductListRequest.java new file mode 100644 index 0000000000..9558f784f8 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/window/request/GetWindowProductListRequest.java @@ -0,0 +1,57 @@ +package me.chanjar.weixin.channel.bean.window.request; + + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 获取账号收集的留资数据详情 + * @author imyzt + * @date 2024/01/27 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@JsonInclude(JsonInclude.Include.NON_NULL) +public class GetWindowProductListRequest { + + /** + * 用于指定查询某个店铺来源的商品 + */ + @JsonProperty("appid") + private String appid; + + /** + * 用于指定查询属于某个分店ID下的商品 + */ + @JsonProperty("branch_id") + private int branchId; + + /** + * 单页商品数(不超过200) + */ + @JsonProperty("page_size") + private int pageSize; + + /** + * 页面下标,下标从1开始,默认为1 + */ + @JsonProperty("page_index") + private int pageIndex; + + /** + * 由上次请求返回,顺序翻页时需要传入,会从上次返回的结果往后翻一页(填了该值后page_index不生效) + */ + @JsonProperty("last_buffer") + private String lastBuffer; + + /** + * 是否需要返回满足筛选条件的商品总数 + */ + @JsonProperty("need_total_num") + private int needTotalNum; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/window/request/WindowProductRequest.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/window/request/WindowProductRequest.java new file mode 100644 index 0000000000..dc68c12df3 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/window/request/WindowProductRequest.java @@ -0,0 +1,32 @@ +package me.chanjar.weixin.channel.bean.window.request; + + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 橱窗商品 + * @author imyzt + * @date 2024/01/27 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@JsonInclude(JsonInclude.Include.NON_NULL) +public class WindowProductRequest { + + /** + * 橱窗商品ID + */ + @JsonProperty("product_id") + private String productId; + + /** + * 商品来源店铺的appid + */ + @JsonProperty("appid") + private String appid; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/window/response/GetWindowProductListResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/window/response/GetWindowProductListResponse.java new file mode 100644 index 0000000000..de81e0f3a8 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/window/response/GetWindowProductListResponse.java @@ -0,0 +1,55 @@ +package me.chanjar.weixin.channel.bean.window.response; + + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; + +import java.util.List; + +/** + * 获取账号收集的留资数据详情 + * @author imyzt + * @date 2024/01/27 + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class GetWindowProductListResponse extends WxChannelBaseResponse { + + /** + * 商品列表 + */ + private List products; + + /** + * 本次翻页的上下文,用于顺序翻页请求 + */ + @JsonProperty("last_buffer") + private String lastBuffer; + + /** + * 商品总数 + */ + @JsonProperty("total_num") + private int totalNum; + + /** + * 商品信息类 + */ + @Data + public static class ProductInfo { + /** + * 橱窗商品id + */ + @JsonProperty("product_id") + private String productId; + + /** + * 商品来源店铺的appid + */ + private String appid; + } +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/window/response/GetWindowProductResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/window/response/GetWindowProductResponse.java new file mode 100644 index 0000000000..9127ee9856 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/window/response/GetWindowProductResponse.java @@ -0,0 +1,238 @@ +package me.chanjar.weixin.channel.bean.window.response; + + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; + +/** + * 获取橱窗商品详情 + * @author imyzt + * @date 2024/01/27 + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class GetWindowProductResponse extends WxChannelBaseResponse { + + /** + * 橱窗商品详情 + */ + @JsonProperty("product") + private String product; + + @Data + public static class Product { + /** + * 橱窗商品ID + */ + @JsonProperty("product_id") + private String productId; + + /** + * 商家侧外部商品ID + */ + @JsonProperty("out_product_id") + private String outProductId; + + /** + * 商品标题 + */ + private String title; + + /** + * 商品头图url + */ + @JsonProperty("img_url") + private String imgUrl; + + /** + * 商品所属三级类目ID + */ + @JsonProperty("third_category_id") + private String thirdCategoryId; + + /** + * 商品状态 + * 1 已上架到橱窗 + * 2 未上架到橱窗 + * 3 已在商品来源处删除 + */ + private Integer status; + + /** + * 价格区间最大值(单位分) (市场价,原价) + */ + @JsonProperty("market_price") + private Long marketPrice; + + /** + * 价格区间最小值(单位分) (销售价) + */ + @JsonProperty("selling_price") + private Long sellingPrice; + + /** + * 剩余库存 + */ + private Long stock; + + /** + * 商品来源店铺的appid(非带货商品才拥有) + */ + private String appid; + + /** + * 商品详情页路径信息 + */ + @JsonProperty("page_path") + private PagePath pagePath; + + /** + * 商品所属电商平台ID + */ + @JsonProperty("platform_id") + private Long platformId; + + /** + * 商品所属电商平台名 + */ + @JsonProperty("platform_name") + private String platformName; + + /** + * 是否在个人橱窗页隐藏 + */ + @JsonProperty("is_hide_for_window") + private Boolean isHideForWindow; + + /** + * 商品是否处于禁止售卖的状态 + */ + private Boolean banned; + + /** + * 禁售原因及申请相关信息 + */ + @JsonProperty("banned_details") + private BannedDetails bannedDetails; + + /** + * 分店信息 + */ + @JsonProperty("branch_info") + private BranchInfo branchInfo; + + /** + * 抢购活动信息 + */ + @JsonProperty("limit_discount_info") + private LimitDiscountInfo limitDiscountInfo; + } + + /** + * 商品详情页路径信息 + */ + @Data + public static class PagePath { + /** + * 商品详情半屏页、全屏页所属appid + */ + private String appid; + + /** + * 商品详情半屏页path + */ + @JsonProperty("half_page_path") + private String halfPagePath; + + /** + * 商品详情全屏页path + */ + @JsonProperty("full_page_path") + private String fullPagePath; + } + + /** + * 商品禁售原因及申请相关信息 + */ + @Data + public static class BannedDetails { + /** + * 禁售原因 + * 0 三级类目在橱窗禁售 或 商品在来源处被禁售 + * 1 商品属于可申请售卖的类目,但商家未完成申请 + * 2 商品所属分店未处于营业状态 + */ + private Integer reason; + + /** + * 需要申请的类目ID + */ + @JsonProperty("need_apply_category_id") + private String needApplyCategoryId; + + /** + * 需要申请的类目名 + */ + @JsonProperty("need_apply_category_name") + private String needApplyCategoryName; + } + + /** + * 分店信息 + */ + @Data + public static class BranchInfo { + /** + * 分店ID + */ + @JsonProperty("branch_id") + private Long branchId; + + /** + * 分店名 + */ + @JsonProperty("branch_name") + private String branchName; + + /** + * 分店状态 + * 0 营业中 + * 1 停业 + */ + @JsonProperty("branch_status") + private Integer branchStatus; + } + + /** + * 抢购活动信息 + */ + @Data + public static class LimitDiscountInfo { + /** + * 是否有生效中的抢购活动 + */ + @JsonProperty("is_effect") + private Boolean isEffect; + + /** + * 抢购价 + */ + @JsonProperty("discount_price") + private Long discountPrice; + + /** + * 抢购活动结束时间(毫秒时间戳) + */ + @JsonProperty("end_time_ms") + private String endTimeMs; + + /** + * 抢购剩余库存 + */ + private Long stock; + } +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/common/ChannelWxError.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/common/ChannelWxError.java new file mode 100644 index 0000000000..705eacc898 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/common/ChannelWxError.java @@ -0,0 +1,27 @@ +package me.chanjar.weixin.channel.common; + +import me.chanjar.weixin.channel.enums.WxChannelErrorMsgEnum; +import me.chanjar.weixin.common.error.WxError; + +/** + * 微信视频号错误码 + * + * @author Zeyes + * @deprecated 请使用 {@link me.chanjar.weixin.common.error.WxError} 替代 + */ +@Deprecated +public class ChannelWxError extends WxError { + + private static final long serialVersionUID = -2638512715814977441L; + + public ChannelWxError() { + } + + public ChannelWxError(int errorCode, String errorMsgEn) { + super(errorCode, errorMsgEn); + if (WxChannelErrorMsgEnum.findMsgByCode(errorCode) != null) { + this.setErrorMsg(WxChannelErrorMsgEnum.findMsgByCode(errorCode)); + } + this.setErrorMsgEn(errorMsgEn); + } +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/config/WxChannelConfig.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/config/WxChannelConfig.java new file mode 100644 index 0000000000..64344dae58 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/config/WxChannelConfig.java @@ -0,0 +1,193 @@ +package me.chanjar.weixin.channel.config; + +import java.util.concurrent.locks.Lock; +import me.chanjar.weixin.channel.api.BaseWxChannelService; +import me.chanjar.weixin.common.bean.WxAccessToken; +import me.chanjar.weixin.common.util.http.apache.ApacheHttpClientBuilder; + +/** + * 视频号小店配置 + * + * @author Zeyes + */ +public interface WxChannelConfig { + + /** + * Gets access token. + * + * @return the access token + */ + String getAccessToken(); + + /** + * Is use stable access token api + * + * @link 获取稳定版AccessToken + * @return the boolean + */ + boolean isStableAccessToken(); + + /** + * Gets access token lock. + * + * @return the access token lock + */ + Lock getAccessTokenLock(); + + /** + * Is access token expired boolean. + * + * @return the boolean + */ + boolean isAccessTokenExpired(); + + /** + * 强制将access token过期掉 + */ + void expireAccessToken(); + + /** + * 应该是线程安全的 + * + * @param accessToken 要更新的WxAccessToken对象 + */ + void updateAccessToken(WxAccessToken accessToken); + + /** + * 应该是线程安全的 + * + * @param accessToken 新的accessToken值 + * @param expiresInSeconds 过期时间,以秒为单位 + */ + void updateAccessToken(String accessToken, int expiresInSeconds); + + /** + * Gets appid. + * + * @return the appid + */ + String getAppid(); + + /** + * Gets secret. + * + * @return the secret + */ + String getSecret(); + + /** + * Gets token. + * + * @return the token + */ + String getToken(); + + /** + * Gets aes key. + * + * @return the aes key + */ + String getAesKey(); + + /** + * Gets msg data format. + * + * @return the msg data format + */ + String getMsgDataFormat(); + + /** + * Gets expires time. + * + * @return the expires time + */ + long getExpiresTime(); + + /** + * Gets http proxy host. + * + * @return the http proxy host + */ + String getHttpProxyHost(); + + /** + * Gets http proxy port. + * + * @return the http proxy port + */ + int getHttpProxyPort(); + + /** + * Gets http proxy username. + * + * @return the http proxy username + */ + String getHttpProxyUsername(); + + /** + * Gets http proxy password. + * + * @return the http proxy password + */ + String getHttpProxyPassword(); + + /** + * http 请求重试间隔 + *
+   *  {@link BaseWxChannelService#setRetrySleepMillis(int)(int)}
+   * 
+ */ + int getRetrySleepMillis(); + + /** + * http 请求最大重试次数 + *
+   *   {@link BaseWxChannelService#setMaxRetryTimes(int)}
+   * 
+ */ + int getMaxRetryTimes(); + + /** + * http client builder + * + * @return ApacheHttpClientBuilder apache http client builder + */ + ApacheHttpClientBuilder getApacheHttpClientBuilder(); + + /** + * 是否自动刷新token + * + * @return the boolean + */ + boolean autoRefreshToken(); + + /** + * 设置自定义的apiHost地址 + * 具体取值,可以参考https://developers.weixin.qq.com/doc/offiaccount/Basic_Information/Interface_field_description.html + * + * @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 + * + * @return 自定义的api域名地址 + */ + String getApiHostUrl(); + + /** + * 获取自定义的获取accessToken地址,用于向自定义统一服务获取accessToken + * + * @return 自定义的获取accessToken地址 + */ + String getAccessTokenUrl(); + + /** + * 设置自定义的获取accessToken地址 可用于设置获取accessToken的自定义服务 + * + * @param accessTokenUrl 自定义的获取accessToken地址 + */ + void setAccessTokenUrl(String accessTokenUrl); +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/config/impl/WxChannelDefaultConfigImpl.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/config/impl/WxChannelDefaultConfigImpl.java new file mode 100644 index 0000000000..1c3930caf5 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/config/impl/WxChannelDefaultConfigImpl.java @@ -0,0 +1,244 @@ +package me.chanjar.weixin.channel.config.impl; + +import java.io.File; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; +import lombok.Getter; +import me.chanjar.weixin.channel.config.WxChannelConfig; +import me.chanjar.weixin.channel.util.JsonUtils; +import me.chanjar.weixin.common.bean.WxAccessToken; +import me.chanjar.weixin.common.util.http.apache.ApacheHttpClientBuilder; + +/** + * 基于内存的微信配置provider,在实际生产环境中应该将这些配置持久化 + * + * @author Zeyes + */ +@Getter +public class WxChannelDefaultConfigImpl implements WxChannelConfig { + + protected volatile String appid; + protected volatile String token; + protected Lock accessTokenLock = new ReentrantLock(); + /** + * 临时文件目录. + */ + protected volatile File tmpDirFile; + private volatile String msgDataFormat; + private volatile String secret; + private volatile String accessToken; + private volatile String aesKey; + private volatile long expiresTime; + private volatile String httpProxyHost; + private volatile int httpProxyPort; + private volatile String httpProxyUsername; + private volatile String httpProxyPassword; + /** 是否使用稳定版获取accessToken接口 */ + private volatile boolean stableAccessToken; + + private volatile int retrySleepMillis = 1000; + private volatile int maxRetryTimes = 5; + private volatile ApacheHttpClientBuilder apacheHttpClientBuilder; + private String apiHostUrl; + private String accessTokenUrl; + + /** + * 会过期的数据提前过期时间,默认预留200秒的时间 + */ + protected long expiresAheadInMillis(int expiresInSeconds) { + return System.currentTimeMillis() + (expiresInSeconds - 200) * 1000L; + } + + /** + * 判断 expiresTime 是否已经过期 + */ + protected boolean isExpired(long expiresTime) { + return System.currentTimeMillis() > expiresTime; + } + + @Override + public String getAccessToken() { + return this.accessToken; + } + + public void setAccessToken(String accessToken) { + this.accessToken = accessToken; + } + + @Override + public boolean isStableAccessToken() { + return stableAccessToken; + } + + public void setStableAccessToken(boolean stableAccessToken) { + this.stableAccessToken = stableAccessToken; + } + + @Override + public Lock getAccessTokenLock() { + return this.accessTokenLock; + } + + public void setAccessTokenLock(Lock accessTokenLock) { + this.accessTokenLock = accessTokenLock; + } + + @Override + public boolean isAccessTokenExpired() { + return isExpired(this.expiresTime); + } + + @Override + public synchronized void updateAccessToken(WxAccessToken accessToken) { + updateAccessToken(accessToken.getAccessToken(), accessToken.getExpiresIn()); + } + + @Override + public synchronized void updateAccessToken(String accessToken, int expiresInSeconds) { + setAccessToken(accessToken); + setExpiresTime(expiresAheadInMillis(expiresInSeconds)); + } + + + @Override + public void expireAccessToken() { + this.expiresTime = 0; + } + + @Override + public String getSecret() { + return this.secret; + } + + public void setSecret(String secret) { + this.secret = secret; + } + + @Override + public String getToken() { + return this.token; + } + + public void setToken(String token) { + this.token = token; + } + + @Override + public long getExpiresTime() { + return this.expiresTime; + } + + public void setExpiresTime(long expiresTime) { + this.expiresTime = expiresTime; + } + + @Override + public String getAesKey() { + return this.aesKey; + } + + public void setAesKey(String aesKey) { + this.aesKey = aesKey; + } + + @Override + public String getMsgDataFormat() { + return this.msgDataFormat; + } + + public void setMsgDataFormat(String msgDataFormat) { + this.msgDataFormat = msgDataFormat; + } + + @Override + public String getHttpProxyHost() { + return this.httpProxyHost; + } + + public void setHttpProxyHost(String httpProxyHost) { + this.httpProxyHost = httpProxyHost; + } + + @Override + public int getHttpProxyPort() { + return this.httpProxyPort; + } + + public void setHttpProxyPort(int httpProxyPort) { + this.httpProxyPort = httpProxyPort; + } + + @Override + public String getHttpProxyUsername() { + return this.httpProxyUsername; + } + + public void setHttpProxyUsername(String httpProxyUsername) { + this.httpProxyUsername = httpProxyUsername; + } + + @Override + public String getHttpProxyPassword() { + return this.httpProxyPassword; + } + + public void setHttpProxyPassword(String httpProxyPassword) { + this.httpProxyPassword = httpProxyPassword; + } + + @Override + public int getRetrySleepMillis() { + return this.retrySleepMillis; + } + + public void setRetrySleepMillis(int retrySleepMillis) { + this.retrySleepMillis = retrySleepMillis; + } + + @Override + public int getMaxRetryTimes() { + return this.maxRetryTimes; + } + + public void setMaxRetryTimes(int maxRetryTimes) { + this.maxRetryTimes = maxRetryTimes; + } + + @Override + public String toString() { + return JsonUtils.encode(this); + } + + @Override + public ApacheHttpClientBuilder getApacheHttpClientBuilder() { + return this.apacheHttpClientBuilder; + } + + public void setApacheHttpClientBuilder(ApacheHttpClientBuilder apacheHttpClientBuilder) { + this.apacheHttpClientBuilder = apacheHttpClientBuilder; + } + + @Override + public boolean autoRefreshToken() { + return true; + } + + @Override + public void setApiHostUrl(String apiHostUrl) { + this.apiHostUrl = apiHostUrl; + } + + @Override + public void setAccessTokenUrl(String accessTokenUrl) { + this.accessTokenUrl = accessTokenUrl; + } + + @Override + public String getAppid() { + return appid; + } + + public void setAppid(String appid) { + this.appid = appid; + } +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/config/impl/WxChannelRedisConfigImpl.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/config/impl/WxChannelRedisConfigImpl.java new file mode 100644 index 0000000000..cbb289c899 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/config/impl/WxChannelRedisConfigImpl.java @@ -0,0 +1,73 @@ +package me.chanjar.weixin.channel.config.impl; + +import java.util.concurrent.TimeUnit; +import me.chanjar.weixin.common.redis.WxRedisOps; + +/** + * 基于redis存储的微信视频号小店配置类 + * + * @author Zeyes + */ +public class WxChannelRedisConfigImpl extends WxChannelDefaultConfigImpl { + + private static final String ACCESS_TOKEN_KEY_TPL = "%s:access_token:%s"; + private static final String LOCK_KEY_TPL = "%s:lock:%s:"; + + private final WxRedisOps redisOps; + private final String keyPrefix; + + private volatile String accessTokenKey; + private volatile String lockKey; + + public WxChannelRedisConfigImpl(WxRedisOps redisOps, String keyPrefix) { + this.redisOps = redisOps; + this.keyPrefix = keyPrefix; + } + + @Override + public void setAppid(String appId) { + super.setAppid(appId); + this.accessTokenKey = String.format(ACCESS_TOKEN_KEY_TPL, this.keyPrefix, appId); + this.lockKey = String.format(LOCK_KEY_TPL, this.keyPrefix, appId); + super.accessTokenLock = this.redisOps.getLock(lockKey.concat("accessTokenLock")); + } + + //------------------------------------------------------------------------ + // token相关 + //------------------------------------------------------------------------ + @Override + public String getAccessToken() { + return redisOps.getValue(this.accessTokenKey); + } + + @Override + public boolean isAccessTokenExpired() { + Long expire = redisOps.getExpire(this.accessTokenKey); + return expire == null || expire < 2; + } + + @Override + public synchronized void updateAccessToken(String accessToken, int expiresInSeconds) { + redisOps.setValue(this.accessTokenKey, accessToken, expiresInSeconds - 200, TimeUnit.SECONDS); + } + + @Override + public void expireAccessToken() { + redisOps.expire(this.accessTokenKey, 0, TimeUnit.SECONDS); + } + + + @Override + public String toString() { + return "WxChannelRedisConfigImpl{" + + "appid='" + appid + '\'' + + ", token='" + token + '\'' + + ", accessTokenLock=" + accessTokenLock + + ", tmpDirFile=" + tmpDirFile + + ", redisOps=" + redisOps + + ", keyPrefix='" + keyPrefix + '\'' + + ", accessTokenKey='" + accessTokenKey + '\'' + + ", lockKey='" + lockKey + '\'' + + '}'; + } +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/config/impl/WxChannelRedissonConfigImpl.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/config/impl/WxChannelRedissonConfigImpl.java new file mode 100644 index 0000000000..d5de517163 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/config/impl/WxChannelRedissonConfigImpl.java @@ -0,0 +1,89 @@ +package me.chanjar.weixin.channel.config.impl; + +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.Lock; +import lombok.NonNull; +import me.chanjar.weixin.common.bean.WxAccessToken; +import me.chanjar.weixin.common.redis.RedissonWxRedisOps; +import me.chanjar.weixin.common.redis.WxRedisOps; +import org.apache.commons.lang3.StringUtils; +import org.redisson.api.RedissonClient; + +/** + * 基于Redisson的实现 + * + * @author yuanqixun + * created on 2020/5/3 + */ +public class WxChannelRedissonConfigImpl extends WxChannelDefaultConfigImpl { + + protected static final String LOCK_KEY = "wx_channel_lock:"; + protected static final String MA_ACCESS_TOKEN_KEY = "wx_channel_access_token_key:"; + + /** + * redis 存储的 key 的前缀,可为空 + */ + protected String keyPrefix; + protected String accessTokenKey; + protected String lockKey; + + private final WxRedisOps redisOps; + + public WxChannelRedissonConfigImpl(@NonNull RedissonClient redissonClient, String keyPrefix) { + this(new RedissonWxRedisOps(redissonClient), keyPrefix); + } + + public WxChannelRedissonConfigImpl(@NonNull RedissonClient redissonClient) { + this(redissonClient, null); + } + + private WxChannelRedissonConfigImpl(@NonNull WxRedisOps redisOps, String keyPrefix) { + this.redisOps = redisOps; + this.keyPrefix = keyPrefix; + } + + @Override + public void setAppid(String appid) { + super.setAppid(appid); + String prefix = StringUtils.isBlank(keyPrefix) ? "" : + (StringUtils.endsWith(keyPrefix, ":") ? keyPrefix : (keyPrefix + ":")); + lockKey = prefix + LOCK_KEY.concat(appid); + accessTokenKey = prefix + MA_ACCESS_TOKEN_KEY.concat(appid); + } + + protected Lock getLockByKey(String key) { + return redisOps.getLock(key); + } + + @Override + public Lock getAccessTokenLock() { + return getLockByKey(this.lockKey.concat(":").concat("accessToken")); + } + + @Override + public String getAccessToken() { + return redisOps.getValue(this.accessTokenKey); + } + + @Override + public boolean isAccessTokenExpired() { + Long expire = redisOps.getExpire(this.accessTokenKey); + return expire == null || expire < 2; + } + + @Override + public void updateAccessToken(WxAccessToken accessToken) { + redisOps.setValue(this.accessTokenKey, accessToken.getAccessToken(), accessToken.getExpiresIn(), TimeUnit.SECONDS); + } + + @Override + public void updateAccessToken(String accessToken, int expiresInSeconds) { + redisOps.setValue(this.accessTokenKey, accessToken, expiresInSeconds, TimeUnit.SECONDS); + } + + @Override + public void expireAccessToken() { + redisOps.expire(this.accessTokenKey, 0, TimeUnit.SECONDS); + } + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/constant/MessageEventConstants.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/constant/MessageEventConstants.java new file mode 100644 index 0000000000..675b5d8a57 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/constant/MessageEventConstants.java @@ -0,0 +1,98 @@ +package me.chanjar.weixin.channel.constant; + +/** + * 消息回调 + * + * @author Zeyes + */ +public interface MessageEventConstants { + /** 品牌资质事件回调 */ + String BRAND = "channels_ec_brand"; + /** 商品审核结果 */ + String PRODUCT_SPU_AUDIT = "product_spu_audit"; + /** 商品上下架 */ + String PRODUCT_SPU_STATUS_UPDATE = "product_spu_listing"; + /** 商品更新 */ + String PRODUCT_SPU_UPDATE = "product_spu_update"; + /** 类目审核结果 */ + String PRODUCT_CATEGORY_AUDIT = "product_category_audit"; + /** 库存不足 */ + String PRODUCT_STOCK_NO_ENOUGH = "channels_ec_stock_no_enough"; + /** 订单下单 */ + String ORDER_NEW = "channels_ec_order_new"; + /** 订单取消 */ + String ORDER_CANCEL = "channels_ec_order_cancel"; + /** 订单支付成功 */ + String ORDER_PAY = "channels_ec_order_pay"; + /** 订单待发货 */ + String ORDER_WAIT_SHIPPING = "channels_ec_order_wait_shipping"; + /** 订单发货 */ + String ORDER_DELIVER = "channels_ec_order_deliver"; + /** 订单确认收货 */ + String ORDER_CONFIRM = "channels_ec_order_confirm"; + /** 订单结算成功 */ + String ORDER_SETTLE = "channels_ec_order_settle"; + /** 订单其他信息更新 */ + String ORDER_EXT_INFO_UPDATE = "channels_ec_order_ext_info_update"; + /** 订单状态更新 */ + String ORDER_STATUS_UPDATE = "product_order_status_update"; + /** 售后单更新通知 */ + String AFTER_SALE_UPDATE = "channels_ec_aftersale_update"; + /** 纠纷更新通知 */ + String COMPLAINT_NOTIFY = "channels_ec_complaint_update"; + // 优惠券相关 + /** 优惠券领取通知 */ + String RECEIVE_COUPON = "channels_ec_coupon_receive"; + /** 创建优惠券通知 */ + String CREATE_COUPON = "channels_ec_coupon_create"; + /** 优惠券删除通知 */ + String DELETE_COUPON = "channels_ec_coupon_delete"; + /** 优惠券过期通知 */ + String EXPIRE_COUPON = "channels_ec_coupon_expire"; + /** 更新优惠券信息通知 */ + String UPDATE_COUPON_INFO = "channels_ec_coupon_info_change"; + /** 优惠券作废通知 */ + String INVALID_COUPON = "channels_ec_coupon_invalid"; + /** 用户优惠券过期通知 */ + String USER_COUPON_EXPIRE = "channels_ec_user_coupon_expire"; + /** 优惠券返还通知 */ + String USER_COUPON_UNUSE = "channels_ec_user_coupon_unuse"; + /** 优惠券核销通知 */ + String USER_COUPON_USE = "channels_ec_user_coupon_use"; + /** 发放团购优惠成功回调 */ + String VOUCHER_SEND_SUCC = "channels_ec_voucher_send_succ"; + // 资金相关 + /** 结算账户变更回调 */ + String ACCOUNT_NOTIFY = "channels_ec_acct_notify"; + /** 提现回调 */ + String WITHDRAW_NOTIFY = "channels_ec_withdraw_notify"; + /** 提现二维码回调 */ + String QRCODE_STATUS = "qrcode_status"; + // 团长 + String SUPPLIER_ITEM_UPDATE = "head_supplier_item_update"; + // 其他 + /** 进入会话事件 */ + String USER_ENTER_TEMP_SESSION = "user_enter_tempsession"; + + // 会员相关 + /** 用户加入会员 */ + String USER_VIP_JOIN = "channels_ec_vip_join"; + /** 用户注销会员 */ + String USER_VIP_CLOSE = "channels_ec_vip_close"; + /** 用户等级更新 */ + String USER_VIP_GRADE_INFO_UPDATE = "channels_ec_vip_grade_info_update"; + /** 用户积分更新 */ + String USER_VIP_SCORE_UPDATE = "channels_ec_vip_score_update"; + /** 用户积分兑换 */ + String USER_VIP_SCORE_EXCHANGE = "channels_ec_vip_score_exchange"; + + // 分享员相关 + /** 分享员变更 **/ + String SHARER_CHANGE = "channels_ec_sharer_change"; + + // 店铺相关 + /** 小店注销 */ + String CLOSE_STORE = "channels_ec_close_store"; + /** 小店修改 */ + String SET_SHOP_NICKNAME = "set_shop_nickname"; +} 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 new file mode 100644 index 0000000000..4859b723fb --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/constant/WxChannelApiUrlConstants.java @@ -0,0 +1,579 @@ +package me.chanjar.weixin.channel.constant; + +import lombok.experimental.UtilityClass; + +/** + * 视频号小店接口地址常量 + * + * @author Zeyes + */ +@UtilityClass +public class WxChannelApiUrlConstants { + + /** + * 获取access_token. + */ + public static final String GET_ACCESS_TOKEN_URL = + "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s"; + + /** + * 获取Stable access_token. + */ + public static final String GET_STABLE_ACCESS_TOKEN_URL = "https://api.weixin.qq.com/cgi-bin/stable_token"; + + /** 基础接口 */ + public interface Basics { + + /** 获取店铺基本信息 */ + String GET_SHOP_INFO = "https://api.weixin.qq.com/channels/ec/basics/info/get"; + /** 上传图片 */ + String IMG_UPLOAD_URL = "https://api.weixin.qq.com/channels/ec/basics/img/upload"; + /** 上传资质图片 */ + String UPLOAD_QUALIFICATION_FILE = "https://api.weixin.qq.com/channels/ec/basics/qualification/upload"; + /** 下载图片 */ + String GET_IMG_URL = "https://api.weixin.qq.com/channels/ec/basics/media/get"; + /** 获取地址编码 */ + String GET_ADDRESS_CODE = "https://api.weixin.qq.com/channels/ec/basics/addresscode/get"; + } + + /** 商品类目相关接口 */ + public interface Category { + + /** 获取所有的类目 */ + String LIST_ALL_CATEGORY_URL = "https://api.weixin.qq.com/channels/ec/category/all"; + /** 获取类目详情 */ + String GET_CATEGORY_DETAIL_URL = "https://api.weixin.qq.com/channels/ec/category/detail"; + /** 获取可用的子类目详情 */ + String AVAILABLE_CATEGORY_URL = "https://api.weixin.qq.com/channels/ec/category/availablesoncategories/get"; + /** 上传类目资质 */ + String ADD_CATEGORY_URL = "https://api.weixin.qq.com/channels/ec/category/add"; + /** 获取类目审核结果 */ + String GET_CATEGORY_AUDIT_URL = "https://api.weixin.qq.com/channels/ec/category/audit/get"; + /** 取消类目提审 */ + String CANCEL_CATEGORY_AUDIT_URL = "https://api.weixin.qq.com/channels/ec/category/audit/cancel"; + /** 获取账号申请通过的类目和资质信息 */ + String LIST_PASS_CATEGORY_URL = "https://api.weixin.qq.com/channels/ec/category/list/get"; + /** 获取店铺的类目权限列表 */ + String LIST_RELATION_CATEGORY_URL = "https://api.weixin.qq.com/shop/ec/category/get_category_relation_list"; + } + + /** 主页管理相关接口 */ + public interface HomePage { + + /** 添加分类关联的商品 */ + String ADD_TREE_PRODUCT_URL = "https://api.weixin.qq.com/channels/ec/store/classification/tree/product/add"; + /** 删除分类关联的商品 */ + String DEL_TREE_PRODUCT_URL = "https://api.weixin.qq.com/channels/ec/store/classification/tree/product/del"; + /** 获取分类关联的商品ID列表 */ + String LIST_TREE_PRODUCT_URL = "https://api.weixin.qq.com/channels/ec/store/classification/tree/product/get"; + /** 设置展示在店铺主页的商品分类 */ + String SET_SHOW_TREE_URL = "https://api.weixin.qq.com/channels/ec/store/classification/tree/set"; + /** 获取在店铺主页展示的商品分类 */ + String GET_SHOW_TREE_URL = "https://api.weixin.qq.com/channels/ec/store/classification/tree/get"; + + /** 获取主页展示商品列表 */ + String LIST_WINDOW_PRODUCT_URL = "https://api.weixin.qq.com/channels/ec/store/window/product/list/get"; + /** 重新排序主页展示商品 */ + String REORDER_WINDOW_PRODUCT_URL = "https://api.weixin.qq.com/channels/ec/store/window/product/reorder"; + /** 隐藏小店主页商品 */ + String HIDE_WINDOW_PRODUCT_URL = "https://api.weixin.qq.com/channels/ec/store/window/product/hide"; + /** 置顶小店主页商品 */ + String TOP_WINDOW_PRODUCT_URL = "https://api.weixin.qq.com/channels/ec/store/window/product/settop"; + + /** 提交主页背景图申请 */ + String APPLY_BACKGROUND_URL = "https://api.weixin.qq.com/channels/ec/basics/homepage/background/apply/submit"; + /** 查询主页背景图 */ + String GET_BACKGROUND_URL = "https://api.weixin.qq.com/channels/ec/basics/homepage/background/get"; + /** 撤销主页背景图申请 */ + String CANCEL_BACKGROUND_URL = "https://api.weixin.qq.com/channels/ec/basics/homepage/background/apply/cancel"; + /** 清空主页背景图并撤销流程中的申请 */ + String REMOVE_BACKGROUND_URL = "https://api.weixin.qq.com/channels/ec/basics/homepage/background/remove"; + + /** 提交精选展示位申请 */ + String APPLY_BANNER_URL = "https://api.weixin.qq.com/channels/ec/basics/homepage/banner/apply/submit"; + /** 查询精选展示位 */ + String GET_BANNER_URL = "https://api.weixin.qq.com/channels/ec/basics/homepage/banner/get"; + /** 撤销精选展示位申请 */ + String CANCEL_BANNER_URL = "https://api.weixin.qq.com/channels/ec/basics/homepage/banner/apply/cancel"; + /** 清空精选展示位并撤销流程中的申请 */ + String REMOVE_BANNER_URL = "https://api.weixin.qq.com/channels/ec/basics/homepage/banner/remove"; + } + + /** 品牌资质相关接口 */ + public interface Brand { + + /** 获取品牌库列表 */ + String ALL_BRAND_URL = "https://api.weixin.qq.com/channels/ec/brand/all"; + /** 新增品牌资质 */ + String ADD_BRAND_URL = "https://api.weixin.qq.com/channels/ec/brand/add"; + /** 更新品牌资质 */ + String UPDATE_BRAND_URL = "https://api.weixin.qq.com/channels/ec/brand/update"; + /** 撤回品牌资质审核 */ + String CANCEL_BRAND_AUDIT_URL = "https://api.weixin.qq.com/channels/ec/brand/audit/cancel"; + /** 删除品牌资质 */ + String DELETE_BRAND_URL = "https://api.weixin.qq.com/channels/ec/brand/delete"; + /** 获取品牌资质申请详情 */ + String GET_BRAND_URL = "https://api.weixin.qq.com/channels/ec/brand/get"; + /** 获取品牌资质申请列表 */ + String LIST_BRAND_URL = "https://api.weixin.qq.com/channels/ec/brand/list/get"; + /** 获取生效中的品牌资质列表 */ + String LIST_BRAND_VALID_URL = "https://api.weixin.qq.com/channels/ec/brand/valid/list/get"; + } + + /** 商品操作相关接口 */ + public interface Spu { + + /** 添加商品 */ + String SPU_ADD_URL = "https://api.weixin.qq.com/channels/ec/product/add"; + /** 删除商品 */ + String SPU_DEL_URL = "https://api.weixin.qq.com/channels/ec/product/delete"; + /** 获取商品详情 */ + String SPU_GET_URL = "https://api.weixin.qq.com/channels/ec/product/get"; + /** 获取商品列表 */ + String SPU_LIST_URL = "https://api.weixin.qq.com/channels/ec/product/list/get"; + /** 更新商品 */ + String SPU_UPDATE_URL = "https://api.weixin.qq.com/channels/ec/product/update"; + /** 更新商品 */ + String SPU_AUDIT_FREE_UPDATE_URL = "https://api.weixin.qq.com/channels/ec/product/auditfree"; + /** 上架商品 */ + String SPU_LISTING_URL = "https://api.weixin.qq.com/channels/ec/product/listing"; + /** 下架商品 */ + String SPU_DELISTING_URL = "https://api.weixin.qq.com/channels/ec/product/delisting"; + /** 撤回商品审核 */ + String CANCEL_AUDIT_URL = "https://api.weixin.qq.com/channels/ec/product/audit/cancel"; + /** 获取商品H5短链 */ + String SPU_H5URL_URL = "https://api.weixin.qq.com/channels/ec/product/h5url/get"; + /** 获取商品二维码 */ + String SPU_QRCODE_URL = "https://api.weixin.qq.com/channels/ec/product/qrcode/get"; + /** 获取商品口令 */ + String SPU_TAGLINK_URL = "https://api.weixin.qq.com/channels/ec/product/taglink/get"; + /** 获取实时库存 */ + String SPU_GET_STOCK_URL = "https://api.weixin.qq.com/channels/ec/product/stock/get"; + /** 获取实时库存 */ + String SPU_GET_STOCK_BATCH_URL = "https://api.weixin.qq.com/channels/ec/product/stock/batchget"; + /** 更新商品库存 */ + String SPU_UPDATE_STOCK_URL = "https://api.weixin.qq.com/channels/ec/product/stock/update"; + /** 添加限时抢购任务 */ + String ADD_LIMIT_TASK_URL = "https://api.weixin.qq.com/channels/ec/product/limiteddiscounttask/add"; + /** 拉取限时抢购任务列表 */ + String LIST_LIMIT_TASK_URL = "https://api.weixin.qq.com/channels/ec/product/limiteddiscounttask/list/get"; + /** 停止限时抢购任务 */ + String STOP_LIMIT_TASK_URL = "https://api.weixin.qq.com/channels/ec/product/limiteddiscounttask/stop"; + /** 删除限时抢购任务 */ + String DELETE_LIMIT_TASK_URL = "https://api.weixin.qq.com/channels/ec/product/limiteddiscounttask/delete"; + } + + /** 区域仓库 */ + public interface Warehouse { + + /** 添加区域仓库 */ + String ADD_WAREHOUSE_URL = "https://api.weixin.qq.com/channels/ec/warehouse/create"; + /** 获取区域仓库列表 */ + String LIST_WAREHOUSE_URL = "https://api.weixin.qq.com/channels/ec/warehouse/list/get"; + /** 获取区域仓库详情 */ + String GET_WAREHOUSE_URL = "https://api.weixin.qq.com/channels/ec/warehouse/get"; + /** 更新区域仓库详情 */ + String UPDATE_WAREHOUSE_URL = "https://api.weixin.qq.com/channels/ec/warehouse/detail/update"; + /** 批量增加覆盖区域 */ + String ADD_COVER_AREA_URL = "https://api.weixin.qq.com/channels/ec/warehouse/coverlocations/add"; + /** 批量删除覆盖区域 */ + String DELETE_COVER_AREA_URL = "https://api.weixin.qq.com/channels/ec/warehouse/coverlocations/del"; + /** 设置指定地址下的仓的优先级 */ + String SET_WAREHOUSE_PRIORITY_URL = "https://api.weixin.qq.com/channels/ec/warehouse/address/prioritysort/set"; + /** 获取指定地址下的仓的优先级 */ + String GET_WAREHOUSE_PRIORITY_URL = "https://api.weixin.qq.com/channels/ec/warehouse/address/prioritysort/get"; + /** 更新区域仓库存 */ + String UPDATE_WAREHOUSE_STOCK_URL = "https://api.weixin.qq.com/channels/ec/warehouse/stock/update"; + /** 获取区域仓库存 */ + String GET_WAREHOUSE_STOCK_URL = "https://api.weixin.qq.com/channels/ec/warehouse/stock/get"; + } + + /** 订单相关接口 */ + public interface Order { + + /** 获取订单列表 */ + String ORDER_LIST_URL = "https://api.weixin.qq.com/channels/ec/order/list/get"; + /** 获取订单详情 */ + String ORDER_GET_URL = "https://api.weixin.qq.com/channels/ec/order/get"; + /** 更改订单价格 */ + String UPDATE_PRICE_URL = "https://api.weixin.qq.com/channels/ec/order/price/update"; + /** 修改订单备注 */ + String UPDATE_REMARK_URL = "https://api.weixin.qq.com/channels/ec/order/merchantnotes/update"; + /** 更修改订单地址 */ + String UPDATE_ADDRESS_URL = "https://api.weixin.qq.com/channels/ec/order/address/update"; + /** 修改物流信息 */ + String UPDATE_EXPRESS_URL = "https://api.weixin.qq.com/channels/ec/order/deliveryinfo/update"; + /** 同意用户修改收货地址申请 */ + String ACCEPT_ADDRESS_MODIFY_URL = "https://api.weixin.qq.com/channels/ec/order/addressmodify/accept"; + /** 拒绝用户修改收货地址申请 */ + String REJECT_ADDRESS_MODIFY_URL = "https://api.weixin.qq.com/channels/ec/order/addressmodify/reject"; + /** 订单搜索 */ + String ORDER_SEARCH_URL = "https://api.weixin.qq.com/channels/ec/order/search"; + /** 上传生鲜质检信息 */ + String UPLOAD_FRESH_INSPECT_URL = "https://api.weixin.qq.com/channels/ec/order/freshinspect/submit"; + /** 兑换虚拟号 */ + String VIRTUAL_TEL_NUMBER_URL = "https://api.weixin.qq.com/channels/ec/order/virtualtelnumber/get"; + /** 解码订单包含的敏感数据 */ + String DECODE_SENSITIVE_INFO_URL = "https://api.weixin.qq.com/channels/ec/order/sensitiveinfo/decode"; + } + + /** 售后相关接口 */ + public interface AfterSale { + + /** 获取售后列表 */ + String AFTER_SALE_LIST_URL = "https://api.weixin.qq.com/channels/ec/aftersale/getaftersalelist"; + /** 获取售后单 */ + String AFTER_SALE_GET_URL = "https://api.weixin.qq.com/channels/ec/aftersale/getaftersaleorder"; + /** 同意售后 */ + String AFTER_SALE_ACCEPT_URL = "https://api.weixin.qq.com/channels/ec/aftersale/acceptapply"; + /** 拒绝售后 */ + String AFTER_SALE_REJECT_URL = "https://api.weixin.qq.com/channels/ec/aftersale/rejectapply"; + /** 上传退款凭证 */ + String AFTER_SALE_UPLOAD_URL = "https://api.weixin.qq.com/channels/ec/aftersale/uploadrefundcertificate"; + /** 获取全量售后原因*/ + 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"; + } + + /** 纠纷相关接口 */ + public interface Complaint { + + /** 商家补充纠纷单留言 */ + String ADD_COMPLAINT_MATERIAL_URL = "https://api.weixin.qq.com/channels/ec/aftersale/addcomplaintmaterial"; + /** 商家举证 */ + String ADD_COMPLAINT_PROOF_URL = "https://api.weixin.qq.com/channels/ec/aftersale/addcomplaintproof"; + /** 获取纠纷单 */ + String GET_COMPLAINT_ORDER_URL = "https://api.weixin.qq.com/channels/ec/aftersale/getcomplaintorder"; + } + + /** 物流相关接口 */ + 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"; + } + + /** 运费模板相关接口 */ + public interface FreightTemplate { + + /** 获取运费模板列表 */ + String LIST_TEMPLATE_URL = "https://api.weixin.qq.com/channels/ec/merchant/getfreighttemplatelist"; + /** 查询运费模版 */ + String GET_TEMPLATE_URL = "https://api.weixin.qq.com/channels/ec/merchant/getfreighttemplatedetail"; + /** 增加运费模版 */ + String ADD_TEMPLATE_URL = "https://api.weixin.qq.com/channels/ec/merchant/addfreighttemplate"; + /** 更新运费模版 */ + String UPDATE_TEMPLATE_URL = "https://api.weixin.qq.com/channels/ec/merchant/updatefreighttemplate"; + } + + /** 地址管理相关接口 */ + public interface Address { + + /** 增加地址 */ + String ADD_ADDRESS_URL = "https://api.weixin.qq.com/channels/ec/merchant/address/add"; + /** 获取地址列表 */ + String LIST_ADDRESS_URL = "https://api.weixin.qq.com/channels/ec/merchant/address/list"; + /** 获取地址详情 */ + String GET_ADDRESS_URL = "https://api.weixin.qq.com/channels/ec/merchant/address/get"; + /** 更新地址 */ + String UPDATE_ADDRESS_URL = "https://api.weixin.qq.com/channels/ec/merchant/address/update"; + /** 删除地址 */ + String DELETE_ADDRESS_URL = "https://api.weixin.qq.com/channels/ec/merchant/address/delete"; + } + + /** 优惠券相关接口 */ + public interface Coupon { + + /** 创建优惠券 */ + String CREATE_COUPON_URL = "https://api.weixin.qq.com/channels/ec/coupon/create"; + /** 更新优惠券 */ + String UPDATE_COUPON_URL = "https://api.weixin.qq.com/channels/ec/coupon/update"; + /** 更新优惠券状态 */ + String UPDATE_COUPON_STATUS_URL = "https://api.weixin.qq.com/channels/ec/coupon/update_status"; + /** 获取优惠券详情 */ + String GET_COUPON_URL = "https://api.weixin.qq.com/channels/ec/coupon/get"; + /** 获取优惠券ID列表 */ + String LIST_COUPON_URL = "https://api.weixin.qq.com/channels/ec/coupon/get_list"; + /** 获取用户优惠券ID列表 */ + String LIST_USER_COUPON_URL = "https://api.weixin.qq.com/channels/ec/coupon/get_user_coupon_list"; + /** 获取用户优惠券详情 */ + String GET_USER_COUPON_URL = "https://api.weixin.qq.com/channels/ec/coupon/get_user_coupon"; + } + + /** 分享员相关接口 */ + public interface Share { + + /** 邀请分享员 */ + String BIND_SHARER_URL = "https://api.weixin.qq.com/channels/ec/sharer/bind"; + /** 获取绑定的分享员 */ + String SEARCH_SHARER_URL = "https://api.weixin.qq.com/channels/ec/sharer/search_sharer"; + /** 获取绑定的分享员列表 */ + String LIST_SHARER_URL = "https://api.weixin.qq.com/channels/ec/sharer/get_sharer_list"; + /** 获取分享员订单列表 */ + String LIST_SHARER_ORDER_URL = "https://api.weixin.qq.com/channels/ec/sharer/get_sharer_order_list"; + /** 解绑分享员 */ + String UNBIND_SHARER_URL = "https://api.weixin.qq.com/channels/ec/sharer/unbind"; + } + + /** 合作账号相关接口 */ + public interface Cooperation { + /** 获取合作账号列表 */ + String LIST_COOPERATION_URL = "https://api.weixin.qq.com/channels/ec/cooperation/list"; + /** 查看合作账号邀请状态 */ + String GET_COOPERATION_STATUS_URL = "https://api.weixin.qq.com/channels/ec/cooperation/invitation/get"; + /** 邀请合作账号 */ + String GENERATE_QRCODE_COOPERATION_URL = "https://api.weixin.qq.com/channels/ec/cooperation/invitation/qrcode/generate"; + /** 取消合作账号邀请 */ + String CANCEL_COOPERATION_URL = "https://api.weixin.qq.com/channels/ec/cooperation/invitation/cancel"; + /** 解绑合作账号 */ + String UNBIND_COOPERATION_URL = "https://api.weixin.qq.com/channels/ec/cooperation/unbind"; + } + + /** 资金相关接口 */ + public interface Fund { + + /** 获取账户余额 */ + String GET_BALANCE_URL = "https://api.weixin.qq.com/channels/ec/funds/getbalance"; + /** 获取结算账户 */ + String GET_BANK_ACCOUNT_URL = "https://api.weixin.qq.com/channels/ec/funds/getbankacct"; + /** 获取资金流水详情 */ + String GET_BALANCE_FLOW_DETAIL_URL = "https://api.weixin.qq.com/channels/ec/funds/getfundsflowdetail"; + /** 获取资金流水列表 */ + String GET_BALANCE_FLOW_LIST_URL = "https://api.weixin.qq.com/channels/ec/funds/getfundsflowlist"; + /** 获取提现记录 */ + String GET_WITHDRAW_DETAIL_URL = "https://api.weixin.qq.com/channels/ec/funds/getwithdrawdetail"; + /** 获取提现记录列表 */ + String GET_WITHDRAW_LIST_URL = "https://api.weixin.qq.com/channels/ec/funds/getwithdrawlist"; + /** 修改结算账户 */ + String SET_BANK_ACCOUNT_URL = "https://api.weixin.qq.com/channels/ec/funds/setbankacct"; + /** 商户提现 */ + String WITHDRAW_URL = "https://api.weixin.qq.com/channels/ec/funds/submitwithdraw"; + /** 根据卡号查银行信息 */ + String GET_BANK_BY_NUM_URL = "https://api.weixin.qq.com/shop/funds/getbankbynum"; + /** 搜索银行列表 */ + String GET_BANK_LIST_URL = "https://api.weixin.qq.com/shop/funds/getbanklist"; + /** 查询城市列表 */ + String GET_CITY_URL = "https://api.weixin.qq.com/shop/funds/getcity"; + /** 查询大陆银行省份列表 */ + String GET_PROVINCE_URL = "https://api.weixin.qq.com/shop/funds/getprovince"; + /** 查询支行列表 */ + String GET_SUB_BANK_URL = "https://api.weixin.qq.com/shop/funds/getsubbranch"; + /** 获取二维码 */ + String GET_QRCODE_URL = "https://api.weixin.qq.com/shop/funds/qrcode/get"; + /** 查询扫码状态 */ + String CHECK_QRCODE_URL = "https://api.weixin.qq.com/shop/funds/qrcode/check"; + } + + /** 优选联盟相关接口 */ + public interface League { + + /** 添加团长商品到橱窗 */ + String ADD_SUPPLIER_GOODS_URL = "https://api.weixin.qq.com/channels/ec/league/headsupplier/window/add"; + /** 查询橱窗上团长商品列表 */ + String LIST_SUPPLIER_GOODS_URL = "https://api.weixin.qq.com/channels/ec/league/headsupplier/window/getall"; + /** 从橱窗移除团长商品 */ + String REMOVE_SUPPLIER_GOODS_URL = "https://api.weixin.qq.com/channels/ec/league/headsupplier/window/remove"; + /** 查询橱窗上团长商品详情 */ + String GET_SUPPLIER_GOODS_URL = "https://api.weixin.qq.com/channels/ec/league/headsupplier/window/getdetail"; + /** 获取达人橱窗授权链接 */ + String GET_SUPPLIER_AUTH_URL = "https://api.weixin.qq.com/channels/ec/league/headsupplier/windowauth/get"; + /** 获取达人橱窗授权状态 */ + String GET_SUPPLIER_AUTH_STATUS_URL = "https://api.weixin.qq.com/channels/ec/league/headsupplier/windowauth/status/get"; + /** 获取团长账户余额 */ + String GET_SUPPLIER_BALANCE_URL = "https://api.weixin.qq.com/channels/ec/league/headsupplier/funds/balance/get"; + /** 获取资金流水详情 */ + String GET_SUPPLIER_BALANCE_FLOW_DETAIL_URL = "https://api.weixin.qq.com/channels/ec/league/headsupplier/funds/flowdetail/get"; + /** 获取资金流水列表 */ + String GET_SUPPLIER_BALANCE_FLOW_LIST_URL = "https://api.weixin.qq.com/channels/ec/league/headsupplier/funds/flowlist/get"; + /** 获取合作商品详情 */ + String GET_SUPPLIER_ITEM_URL = "https://api.weixin.qq.com/channels/ec/league/headsupplier/item/get"; + /** 获取合作商品列表 */ + String GET_SUPPLIER_ITEM_LIST_URL = "https://api.weixin.qq.com/channels/ec/league/headsupplier/item/list/get"; + /** 获取佣金单详情 */ + String GET_SUPPLIER_ORDER_URL = "https://api.weixin.qq.com/channels/ec/league/headsupplier/order/get"; + /** 获取佣金单列表 */ + String GET_SUPPLIER_ORDER_LIST_URL = "https://api.weixin.qq.com/channels/ec/league/headsupplier/order/list/get"; + /** 获取合作小店详情 */ + String GET_SUPPLIER_SHOP_URL = "https://api.weixin.qq.com/channels/ec/league/headsupplier/shop/get"; + /** 获取合作小店列表 */ + String GET_SUPPLIER_SHOP_LIST_URL = "https://api.weixin.qq.com/channels/ec/league/headsupplier/shop/list/get"; + /** 新增达人 */ + String ADD_PROMOTER_URL = "https://api.weixin.qq.com/channels/ec/league/promoter/add"; + /** 编辑达人 */ + String EDIT_PROMOTER_URL = "https://api.weixin.qq.com/channels/ec/league/promoter/upd"; + /** 删除达人 */ + String DELETE_PROMOTER_URL = "https://api.weixin.qq.com/channels/ec/league/promoter/delete"; + /** 获取达人详情信息 */ + String GET_PROMOTER_URL = "https://api.weixin.qq.com/channels/ec/league/promoter/get"; + /** 拉取商店达人列表 */ + String GET_PROMOTER_LIST_URL = "https://api.weixin.qq.com/channels/ec/league/promoter/list/get"; + /** 批量新增联盟商品 */ + String BATCH_ADD_LEAGUE_ITEM_URL = "https://api.weixin.qq.com/channels/ec/league/item/batchadd"; + /** 更新联盟商品信息 */ + String UPDATE_LEAGUE_ITEM_URL = "https://api.weixin.qq.com/channels/ec/league/item/upd"; + /** 删除联盟商品 */ + String DELETE_LEAGUE_ITEM_URL = "https://api.weixin.qq.com/channels/ec/league/item/delete"; + /** 拉取联盟商品详情 */ + String GET_LEAGUE_ITEM_URL = "https://api.weixin.qq.com/channels/ec/league/item/get"; + /** 拉取联盟商品推广列表 */ + String GET_LEAGUE_ITEM_LIST_URL = "https://api.weixin.qq.com/channels/ec/league/item/list/get"; + } + + /** 视频号助手开放接口 */ + public interface Assistant { + + /** 上架商品到橱窗 */ + String ADD_WINDOW_PRODUCT_URL = "https://api.weixin.qq.com/channels/ec/window/product/add"; + /** 获取橱窗商品详情 */ + String GET_WINDOW_PRODUCT_URL = "https://api.weixin.qq.com/channels/ec/window/product/get"; + /** 获取已添加到橱窗的商品列表 */ + String LIST_WINDOW_PRODUCT_URL = "https://api.weixin.qq.com/channels/ec/window/product/list/get"; + /** 下架橱窗商品 */ + String OFF_WINDOW_PRODUCT_URL = "https://api.weixin.qq.com/channels/ec/window/product/off"; + } + + /** + * 留资组件管理 + */ + public interface LeadComponent { + + /** + * 按时间获取留资信息详情 + */ + String GET_LEADS_INFO_BY_COMPONENT_ID = "https://api.weixin.qq.com/channels/leads/get_leads_info_by_component_id"; + + /** + * 按直播场次获取留资信息详情 + */ + String GET_LEADS_INFO_BY_REQUEST_ID = "https://api.weixin.qq.com/channels/leads/get_leads_info_by_request_id"; + + /** + * 获取留资request_id列表详情 + */ + String GET_LEADS_REQUEST_ID = "https://api.weixin.qq.com/channels/leads/get_leads_request_id"; + + /** + * 获取留资组件直播推广记录信息详情 + */ + String GET_LEADS_COMPONENT_PROMOTE_RECORD = "https://api.weixin.qq.com/channels/leads/get_leads_component_promote_record"; + + /** + * 获取留资组件Id列表详情 + */ + String GET_LEADS_COMPONENT_ID = "https://api.weixin.qq.com/channels/leads/get_leads_component_id"; + } + + /** + * 留资服务的直播数据 + */ + public interface FinderLive { + /** + * 获取视频号账号信息 + */ + String GET_FINDER_ATTR_BY_APPID = "https://api.weixin.qq.com/channels/finderlive/get_finder_attr_by_appid"; + /** + * 获取留资直播间数据详情 + */ + String GET_FINDER_LIVE_DATA_LIST = "https://api.weixin.qq.com/channels/finderlive/get_finder_live_data_list"; + /** + * 获取账号收集的留资数量 + */ + String GET_FINDER_LIVE_LEADS_DATA = "https://api.weixin.qq.com/channels/finderlive/get_finder_live_leads_data"; + } + + + /** 会员功能接口 */ + public interface Vip { + /** 拉取用户详情 */ + String VIP_USER_INFO_URL = "https://api.weixin.qq.com/channels/ec/vip/user/info/get"; + /** 拉取用户列表 */ + String VIP_USER_LIST_URL = "https://api.weixin.qq.com/channels/ec/vip/user/list/get"; + + /** 获取用户积分 */ + String VIP_SCORE_URL = "https://api.weixin.qq.com/channels/ec/vip/user/score/get"; + /** 增加用户积分 */ + String SCORE_INCREASE_URL = "https://api.weixin.qq.com/channels/ec/vip/user/score/increase"; + /** 减少用户积分 */ + String SCORE_DECREASE_URL = "https://api.weixin.qq.com/channels/ec/vip/user/score/decrease"; + + /** 更新用户等级 */ + String GRADE_UPDATE_URL = "https://api.weixin.qq.com/channels/ec/vip/user/grade/update"; + } + + /** + * 直播大屏数据 + */ + public interface LiveDashboard { + /** + * 获取直播大屏直播列表 + */ + String GET_LIVE_LIST_URL = "https://api.weixin.qq.com/channels/livedashboard/getlivelist"; + + /** + * 获取直播大屏数据 + */ + String GET_LIVE_DATA_URL = "https://api.weixin.qq.com/channels/livedashboard/getlivedata"; + } + + /** + * 罗盘达人版API + */ + public interface CompassFinder { + /** + * 获取电商概览数据 + */ + String GET_OVERALL_URL = "https://api.weixin.qq.com/channels/ec/compass/finder/overall/get"; + + /** + * 获取带货商品数据 + */ + String GET_PRODUCT_DATA_URL = "https://api.weixin.qq.com/channels/ec/compass/finder/product/data/get"; + + /** + * 获取带货商品列表 + */ + String GET_PRODUCT_LIST_URL = "https://api.weixin.qq.com/channels/ec/compass/finder/product/list/get"; + + /** + * 获取带货人群数据 + */ + String GET_SALE_PROFILE_DATA_URL = "https://api.weixin.qq.com/channels/ec/compass/finder/sale/profile/data/get"; + } + + /** + * 罗盘商家版API + */ + public interface CompassShop { + + /** 获取电商数据概览 */ + String GET_SHOP_OVERALL_URL = "https://api.weixin.qq.com/channels/ec/compass/shop/overall/get"; + /** 获取授权视频号列表 */ + String FINDER_AUTH_LIST_URL = "https://api.weixin.qq.com/channels/ec/compass/shop/finder/authorization/list/get"; + /** 获取带货达人列表 */ + String FINDER_LIST_URL = "https://api.weixin.qq.com/channels/ec/compass/shop/finder/list/get"; + /** 获取带货数据概览 */ + String GET_FINDER_OVERALL_URL = "https://api.weixin.qq.com/channels/ec/compass/shop/finder/overall/get"; + /** 获取带货达人商品列表 */ + String GET_FINDER_PRODUCT_LIST_URL = "https://api.weixin.qq.com/channels/ec/compass/shop/finder/product/list/get"; + /** 获取带货达人商品数据 */ + String GET_FINDER_PRODUCT_OVERALL_URL = "https://api.weixin.qq.com/channels/ec/compass/shop/finder/product/overall/get"; + /** 获取店铺开播列表 */ + String GET_LIVE_LIST_URL = "https://api.weixin.qq.com/channels/ec/compass/shop/live/list/get"; + /** 获取商品详细信息 */ + String GET_SHOP_PRODUCT_DATA_URL = "https://api.weixin.qq.com/channels/ec/compass/shop/product/data/get"; + /** 获取商品列表 */ + String GET_SHOP_PRODUCT_LIST_URL = "https://api.weixin.qq.com/channels/ec/compass/shop/product/list/get"; + /** 获取店铺人群数据 */ + String GET_SHOP_SALE_PROFILE_DATA_URL = "https://api.weixin.qq.com/channels/ec/compass/shop/sale/profile/data/get"; + } +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/enums/AccountType.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/enums/AccountType.java new file mode 100644 index 0000000000..96cfc43956 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/enums/AccountType.java @@ -0,0 +1,43 @@ +package me.chanjar.weixin.channel.enums; + +import com.fasterxml.jackson.annotation.JsonFormat; + +/** + * 视频号小店 账户类型 + * + * @author Zeyes + */ +@JsonFormat(shape = JsonFormat.Shape.OBJECT) +public enum AccountType { + /** 对公银行账户 */ + ACCOUNT_TYPE_BUSINESS("ACCOUNT_TYPE_BUSINESS", "对公银行账户"), + /** 经营者个人银行卡 */ + ACCOUNT_TYPE_PRIVATE("ACCOUNT_TYPE_PRIVATE", "经营者个人银行卡"), + + ; + + private final String key; + private final String value; + + AccountType(String key, String value) { + this.key = key; + this.value = value; + } + + public static AccountType getByKey(String key) { + for (AccountType reason : AccountType.values()) { + if (reason.getKey().equals(key)) { + return reason; + } + } + return ACCOUNT_TYPE_PRIVATE; + } + + public String getKey() { + return key; + } + + public String getValue() { + return value; + } +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/enums/AfterSaleStatus.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/enums/AfterSaleStatus.java new file mode 100644 index 0000000000..b249c96bcb --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/enums/AfterSaleStatus.java @@ -0,0 +1,64 @@ +package me.chanjar.weixin.channel.enums; + +import com.fasterxml.jackson.annotation.JsonFormat; + +/** + * 视频号小店 售后单状态 + * + * @author Zeyes + */ +@JsonFormat(shape = JsonFormat.Shape.OBJECT) +public enum AfterSaleStatus { + /** 用户取消申请 */ + USER_CANCELD("USER_CANCELD", "用户取消申请"), + /** 商家受理中 */ + MERCHANT_PROCESSING("MERCHANT_PROCESSING", "商家受理中"), + /** 商家拒绝退款 */ + MERCHANT_REJECT_REFUND("MERCHANT_REJECT_REFUND", "商家拒绝退款"), + /** 商家拒绝退货退款 */ + MERCHANT_REJECT_RETURN("MERCHANT_REJECT_RETURN", "商家拒绝退货退款"), + /** 待买家退货 */ + USER_WAIT_RETURN("USER_WAIT_RETURN", "待买家退货"), + /** 7 售后单关闭 */ + RETURN_CLOSED("RETURN_CLOSED", "退货退款关闭"), + /** 8 待商家收货 */ + MERCHANT_WAIT_RECEIPT("MERCHANT_WAIT_RECEIPT", "待商家收货"), + /** 商家逾期未退款 */ + MERCHANT_OVERDUE_REFUND("MERCHANT_OVERDUE_REFUND", "商家逾期未退款"), + /** 退款完成 */ + MERCHANT_REFUND_SUCCESS("MERCHANT_REFUND_SUCCESS", "退款完成"), + /** 退货退款完成 */ + MERCHANT_RETURN_SUCCESS("MERCHANT_RETURN_SUCCESS", "退货退款完成"), + /** 11 平台退款中 */ + PLATFORM_REFUNDING("PLATFORM_REFUNDING", "平台退款中"), + /** 25 平台退款失败 */ + PLATFORM_REFUND_FAIL("PLATFORM_REFUND_FAIL", "平台退款失败"), + /** 待用户确认 */ + USER_WAIT_CONFIRM("USER_WAIT_CONFIRM", "待用户确认"), + /** 商家打款失败,客服关闭售后 */ + MERCHANT_REFUND_RETRY_FAIL("MERCHANT_REFUND_RETRY_FAIL", "商家打款失败,客服关闭售后"), + /** 售后关闭 */ + MERCHANT_FAIL("MERCHANT_FAIL", "售后关闭"), + /** 待用户处理商家协商 */ + USER_WAIT_CONFIRM_UPDATE("USER_WAIT_CONFIRM_UPDATE", "待用户处理商家协商"), + /** 待用户处理商家代发起的售后申请 */ + USER_WAIT_HANDLE_MERCHANT_AFTER_SALE("USER_WAIT_HANDLE_MERCHANT_AFTER_SALE", "待用户处理商家代发起的售后申请"), + ; + + private final String key; + private final String value; + + AfterSaleStatus(String key, String value) { + this.key = key; + this.value = value; + } + + public String getKey() { + return key; + } + + public String getValue() { + return value; + } + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/enums/AfterSaleType.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/enums/AfterSaleType.java new file mode 100644 index 0000000000..8cbdf521f5 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/enums/AfterSaleType.java @@ -0,0 +1,32 @@ +package me.chanjar.weixin.channel.enums; + +import com.fasterxml.jackson.annotation.JsonFormat; + +/** + * 售后类型 + * + * @author Zeyes + */ +@JsonFormat(shape = JsonFormat.Shape.OBJECT) +public enum AfterSaleType { + /** 1 仅退款 */ + REFUND_ONLY("REFUND", "仅退款"), + /** 2 退货退款 */ + REFUND_GOODS("RETURN", "退货退款"); + + private final String key; + private final String value; + + AfterSaleType(String key, String value) { + this.key = key; + this.value = value; + } + + public String getKey() { + return key; + } + + public String getValue() { + return value; + } +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/enums/AfterSalesReason.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/enums/AfterSalesReason.java new file mode 100644 index 0000000000..c3409a05cd --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/enums/AfterSalesReason.java @@ -0,0 +1,63 @@ +package me.chanjar.weixin.channel.enums; + +import com.fasterxml.jackson.annotation.JsonFormat; + +/** + * 视频号小店 售后原因 + * + * @author Zeyes + */ +@JsonFormat(shape = JsonFormat.Shape.OBJECT) +public enum AfterSalesReason { + /** 拍错/多拍 */ + INCORRECT_SELECTION("INCORRECT_SELECTION", "拍错/多拍"), + /** 不想要了 */ + NO_LONGER_WANT("NO_LONGER_WANT", "不想要了"), + /** 无快递信息 */ + NO_EXPRESS_INFO("NO_EXPRESS_INFO", "无快递信息"), + /** 包裹为空 */ + EMPTY_PACKAGE("EMPTY_PACKAGE", "包裹为空"), + /** 已拒签包裹 */ + REJECT_RECEIVE_PACKAGE("REJECT_RECEIVE_PACKAGE", "已拒签包裹"), + /** 快递长时间未送达 */ + NOT_DELIVERED_TOO_LONG("NOT_DELIVERED_TOO_LONG", "快递长时间未送达了"), + /** 与商品描述不符 */ + NOT_MATCH_PRODUCT_DESC("NOT_MATCH_PRODUCT_DESC", "与商品描述不符"), + /** 质量问题 */ + QUALITY_ISSUE("QUALITY_ISSUE", "质量问题"), + /** 卖家发错货 */ + SEND_WRONG_GOODS("SEND_WRONG_GOODS", "卖家发错货"), + /** 三无产品 */ + THREE_NO_PRODUCT("THREE_NO_PRODUCT", "三无产品"), + /** 假冒产品 */ + FAKE_PRODUCT("FAKE_PRODUCT", "假冒产品"), + /** 其它 */ + OTHERS("OTHERS", "其它"), + ; + + private final String key; + private final String value; + + AfterSalesReason(String key, String value) { + this.key = key; + this.value = value; + } + + public static AfterSalesReason getByKey(String key) { + for (AfterSalesReason reason : AfterSalesReason.values()) { + if (reason.getKey().equals(key)) { + return reason; + } + } + // 找不到就返回其他了 + return OTHERS; + } + + public String getKey() { + return key; + } + + public String getValue() { + return value; + } +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/enums/BannerType.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/enums/BannerType.java new file mode 100644 index 0000000000..7cf11eb9ce --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/enums/BannerType.java @@ -0,0 +1,37 @@ +package me.chanjar.weixin.channel.enums; + +import com.fasterxml.jackson.annotation.JsonFormat; + +/** + * 展示位类型 + * + * @author Zeyes + */ +@JsonFormat(shape = JsonFormat.Shape.OBJECT) +public enum BannerType { + + /** 1 商品 */ + PRODUCT(1, "商品"), + /** 3 视频号 */ + CHANNEL(3, "视频号"), + /** 4 公众号 */ + MP(4, "公众号"); + + ; + + private final int key; + private final String value; + + BannerType(int key, String value) { + this.key = key; + this.value = value; + } + + public int getKey() { + return key; + } + + public String getValue() { + return value; + } +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/enums/CommissionOrderStatus.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/enums/CommissionOrderStatus.java new file mode 100644 index 0000000000..bdf29d2765 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/enums/CommissionOrderStatus.java @@ -0,0 +1,37 @@ +package me.chanjar.weixin.channel.enums; + +import com.fasterxml.jackson.annotation.JsonFormat; + +/** + * 佣金订单状态 + * + * @author Zeyes + */ +@JsonFormat(shape = JsonFormat.Shape.OBJECT) +public enum CommissionOrderStatus { + + /** 20 未结算 */ + NOT_SETTLED(20, "未结算"), + /** 100 已结算 */ + SETTLED(100, "已结算"), + /** 200 取消结算 */ + CANCEL_SETTLED(200, "取消结算"), + + ; + + private final int key; + private final String val; + + CommissionOrderStatus(int key, String val) { + this.key = key; + this.val = val; + } + + public int getKey() { + return key; + } + + public String getVal() { + return val; + } +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/enums/ComplaintItemType.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/enums/ComplaintItemType.java new file mode 100644 index 0000000000..d7f1aae8ec --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/enums/ComplaintItemType.java @@ -0,0 +1,112 @@ +package me.chanjar.weixin.channel.enums; + +import com.fasterxml.jackson.annotation.JsonFormat; + +/** + * 纠纷历史操作类型 + * + * @author Zeyes + */ +@JsonFormat(shape = JsonFormat.Shape.OBJECT) +public enum ComplaintItemType { + /** 1 申请平台介入 */ + APPLY_PLATFORM_INTERVENTION(1, "申请平台介入"), + /** 2 用户留言 */ + USER_MESSAGE(2, "用户留言"), + /** 3 商家留言 */ + MERCHANT_MESSAGE(3, "商家留言"), + /** 4 提交投诉成功 */ + SUBMIT_COMPLAINT_SUCCESS(4, "提交投诉成功"), + /** 5 投诉已取消 */ + COMPLAINT_CANCELLED(5, "投诉已取消"), + /** 6 商家已超时 */ + MERCHANT_TIMEOUT(6, "商家已超时"), + /** 7 用户补充凭证 */ + USER_SUPPLEMENTARY_EVIDENCE(7, "用户补充凭证"), + /** 8 商家补充凭证 */ + MERCHANT_SUPPLEMENTARY_EVIDENCE(8, "商家补充凭证"), + /** 10 待商家处理纠纷 */ + WAIT_MERCHANT_HANDLE_DISPUTE(10, "待商家处理纠纷"), + /** 11 待平台处理 */ + WAIT_PLATFORM_HANDLE(11, "待平台处理"), + /** 12 取消平台介入 */ + CANCEL_PLATFORM_INTERVENTION(12, "取消平台介入"), + /** 13 平台处理中 */ + PLATFORM_PROCESSING(13, "平台处理中"), + /** 14 待用户补充凭证 */ + WAIT_USER_SUPPLEMENTARY_EVIDENCE(14, "待用户补充凭证"), + /** 16 待商家补充凭证 */ + WAIT_MERCHANT_SUPPLEMENTARY_EVIDENCE(16, "待商家补充凭证"), + /** 18 待双方补充凭证 */ + WAIT_BOTH_PARTIES_SUPPLEMENTARY_EVIDENCE(18, "待双方补充凭证"), + /** 20 待商家确认 */ + WAIT_MERCHANT_CONFIRM(20, "待商家确认"), + /** 21 商家申诉中 */ + MERCHANT_APPEALING(21, "商家申诉中"), + /** 22 调解完成 */ + MEDIATION_COMPLETE(22, "调解完成"), + /** 23 待平台核实 */ + WAIT_PLATFORM_VERIFY(23, "待平台核实"), + /** 24 重新退款中 */ + REFUNDING_AGAIN(24, "重新退款中"), + /** 26 调解关闭 */ + MEDIATION_CLOSED(26, "调解关闭"), + /** 30 平台判定用户责任 */ + PLATFORM_JUDGMENT_USER_RESPONSIBILITY(30, "平台判定用户责任"), + /** 31 平台判定商家责任 */ + PLATFORM_JUDGMENT_MERCHANT_RESPONSIBILITY(31, "平台判定商家责任"), + /** 32 平台判定双方责任 */ + PLATFORM_JUDGMENT_BOTH_PARTIES_RESPONSIBILITY(32, "平台判定双方责任"), + /** 33 平台判定无责任 */ + PLATFORM_JUDGMENT_NO_RESPONSIBILITY(33, "平台判定无责任"), + /** 34 平台判定申诉无效 */ + PLATFORM_JUDGMENT_APPEAL_INVALID(34, "平台判定申诉无效"), + /** 35 平台判定申诉生效 */ + PLATFORM_JUDGMENT_APPEAL_EFFECTIVE(35, "平台判定申诉生效"), + /** 36 平台判定退款有效 */ + PLATFORM_JUDGMENT_REFUND_EFFECTIVE(36, "平台判定退款有效"), + /** 37 平台判定退款无效 */ + PLATFORM_JUDGMENT_REFUND_INVALID(37, "平台判定退款无效"), + /** 50 用户发起退款 */ + USER_INITIATE_REFUND(50, "用户发起退款"), + /** 51 商家拒绝退款 */ + MERCHANT_REFUSE_REFUND(51, "商家拒绝退款"), + /** 52 用户取消申请 */ + USER_CANCEL_APPLICATION(52, "用户取消申请"), + /** 56 待买家退货 */ + WAIT_BUYER_RETURN_GOODS(56, "待买家退货"), + /** 57 退货退款关闭 */ + REFUND_CLOSED(57, "退货退款关闭"), + /** 58 待商家收货 */ + WAIT_MERCHANT_RECEIVE_GOODS(58, "待商家收货"), + /** 59 商家逾期未退款 */ + MERCHANT_OVERDUE_REFUND(59, "商家逾期未退款"), + /** 60 退款完成 */ + REFUND_COMPLETE(60, "退款完成"), + /** 61 退货退款完成 */ + REFUND_GOODS_COMPLETE(61, "退货退款完成"), + /** 62 平台退款中 */ + PLATFORM_REFUNDING(62, "平台退款中"), + /** 63 平台退款失败 */ + PLATFORM_REFUND_FAILED(63, "平台退款失败"), + /** 64 待用户确认 */ + WAIT_USER_CONFIRM(64, "待用户确认"), + + ; + + private final int key; + private final String value; + + ComplaintItemType(int key, String value) { + this.key = key; + this.value = value; + } + + public int getKey() { + return key; + } + + public String getValue() { + return value; + } +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/enums/ComplaintStatus.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/enums/ComplaintStatus.java new file mode 100644 index 0000000000..c11c5fc432 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/enums/ComplaintStatus.java @@ -0,0 +1,30 @@ +package me.chanjar.weixin.channel.enums; + +import com.fasterxml.jackson.annotation.JsonFormat; + +/** + * 纠纷单状态 + * + * @author Zeyes + */ +@JsonFormat(shape = JsonFormat.Shape.OBJECT) +public enum ComplaintStatus { + + ; + + private final int key; + private final String value; + + ComplaintStatus(int key, String value) { + this.key = key; + this.value = value; + } + + public int getKey() { + return key; + } + + public String getValue() { + return value; + } +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/enums/CouponType.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/enums/CouponType.java new file mode 100644 index 0000000000..9001f286ab --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/enums/CouponType.java @@ -0,0 +1,45 @@ +package me.chanjar.weixin.channel.enums; + +import com.fasterxml.jackson.annotation.JsonFormat; + +/** + * 视频号小店 优惠券 推广类型 + * + * @author Zeyes + */ +@JsonFormat(shape = JsonFormat.Shape.OBJECT) +public enum CouponType { + /** 1 商品条件折券 */ + C_1(1, "商品条件折券"), + /** 2 商品满减券 */ + C_2(2, "商品满减券"), + /** 3 商品统一折扣券 */ + C_3(3, "商品统一折扣券"), + /** 4 商品直减券 */ + C_4(4, "商品直减券"), + /** 101 店铺条件折扣券 */ + C_101(101, "店铺条件折扣券"), + /** 102 店铺满减券 */ + C_102(102, "店铺满减券"), + /** 103 店铺统一折扣券 */ + C_103(103, "店铺统一折扣券"), + /** 104 店铺直减券 */ + C_104(104, "店铺直减券"), + ; + + private final int key; + private final String val; + + CouponType(int key, String val) { + this.key = key; + this.val = val; + } + + public int getKey() { + return key; + } + + public String getVal() { + return val; + } +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/enums/CouponValidType.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/enums/CouponValidType.java new file mode 100644 index 0000000000..bb037cbbdf --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/enums/CouponValidType.java @@ -0,0 +1,34 @@ +package me.chanjar.weixin.channel.enums; + +import com.fasterxml.jackson.annotation.JsonFormat; + +/** + * 视频号小店 优惠券 推广类型 + * + * @author Zeyes + */ +@JsonFormat(shape = JsonFormat.Shape.OBJECT) +public enum CouponValidType { + /** 指定时间范围生效 */ + COUPON_VALID_TYPE_TIME(1, "指定时间范围生效"), + /** 生效天数 */ + COUPON_VALID_TYPE_DAY(2, "生效天数"), + + ; + + private final int key; + private final String val; + + CouponValidType(int key, String val) { + this.key = key; + this.val = val; + } + + public int getKey() { + return key; + } + + public String getVal() { + return val; + } +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/enums/DeliveryType.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/enums/DeliveryType.java new file mode 100644 index 0000000000..8a024ca6f7 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/enums/DeliveryType.java @@ -0,0 +1,42 @@ +package me.chanjar.weixin.channel.enums; + +/** + * 快递类型 + * + * @author Zeyes + */ +public enum DeliveryType { + /** 1 自寄快递 */ + SELF_DELIVERY(1, "自寄快递"), + /** 2 在线签约快递单 */ + ONLINE_DELIVERY(2, "在线签约快递单"), + /** 3 虚拟商品无需物流发货 */ + VIRTUAL_DELIVERY(3, "虚拟商品无需物流发货"), + /** 4 在线快递散单 */ + ONLINE_DELIVERY_SCATTER(4, "在线快递散单"); + + private final Integer key; + private final String value; + + DeliveryType(Integer key, String value) { + this.key = key; + this.value = value; + } + + public static DeliveryType getDeliveryType(Integer key) { + for (DeliveryType deliveryType : DeliveryType.values()) { + if (deliveryType.getKey().equals(key)) { + return deliveryType; + } + } + return null; + } + + public Integer getKey() { + return key; + } + + public String getValue() { + return value; + } +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/enums/DimensionType.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/enums/DimensionType.java new file mode 100644 index 0000000000..b713f518ab --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/enums/DimensionType.java @@ -0,0 +1,80 @@ +package me.chanjar.weixin.channel.enums; + +import com.fasterxml.jackson.annotation.JsonFormat; + +/** + * 视频号助手 直播数据维度类型 + * + * @author Winnie + */ +@JsonFormat(shape = JsonFormat.Shape.OBJECT) +public enum DimensionType { + + /** + * 一级渠道 + */ + PRIMARY_CHANNEL(1, "一级渠道"), + /** + * 年龄段 + */ + AGE(2, "年龄段"), + /** + * 性别 + */ + SEX(3, "性别"), + /** + * 关注关系 + */ + FOLLOW(5, "关注关系"), + /** + * 二级渠道 + */ + SECONDARY_CHANNEL(7, "二级渠道"), + /** + * 策略人群 + */ + CATE(9, "策略人群"), + /** + * 省级行政区 + */ + PROVINCE(10, "省级行政区"), + /** + * 地级行政区 + */ + CITY(11, "地级行政区"), + /** + * 消费者商品类目偏好 + */ + ECOM_USER_LEVEL(12, "消费者商品类目偏好"), + /** + * 客单价区间 + */ + GMV_PER_CNT(13, "客单价区间"), +// /** +// * 关注关系 +// */ +// FOLLOW(15, "关注关系"), + /** + * 流量类型(自然流量、直播加热、广告投流) + */ + FLOW(16, "流量类型(自然流量、直播加热、广告投流)"), + + ; + + private final Integer key; + private final String value; + + DimensionType(Integer key, String value) { + this.key = key; + this.value = value; + } + + public Integer getKey() { + return key; + } + + public String getValue() { + return value; + } + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/enums/EcProfileDataNodeKey.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/enums/EcProfileDataNodeKey.java new file mode 100644 index 0000000000..2374576557 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/enums/EcProfileDataNodeKey.java @@ -0,0 +1,64 @@ +package me.chanjar.weixin.channel.enums; + +import com.fasterxml.jackson.annotation.JsonFormat; + +/** + * 视频号助手 用户群体数据节点键 + * + * @author Winnie + */ +@JsonFormat(shape = JsonFormat.Shape.OBJECT) +public enum EcProfileDataNodeKey { + + /** + * 性别分布 + */ + SEX("sex_distribution", "性别分布"), + /** + * 年龄分布 + */ + AGE("age_distribution", "年龄分布"), + /** + * 省份分布 + */ + PROVINCE("province_distribution", "省份分布"), + /** + * 城市分布 + */ + CITY("city_distribution", "城市分布"), + /** + * 关注关系分布 + */ + FOLLOW("follow_distribution", "关注关系分布"), + /** + * 策略人群分布 + */ + CATE("cate_distribution", "策略人群分布"), + /** + * 商品类目偏好分布 + */ + ECOM_USER_LEVEL("ecom_user_level_distribution", "商品类目偏好分布"), + /** + * 平均客单价分布 + */ + GMV_PER_CNT("gmv_per_cnt_distribution", "平均客单价分布"), + + ; + + private final String key; + private final String value; + + EcProfileDataNodeKey(String key, String value) { + this.key = key; + this.value = value; + } + + public String getKey() { + return key; + } + + public String getValue() { + return value; + } + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/enums/FundsType.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/enums/FundsType.java new file mode 100644 index 0000000000..125e8c39c7 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/enums/FundsType.java @@ -0,0 +1,61 @@ +package me.chanjar.weixin.channel.enums; + +/** + * 资金类型 + * + * @author Zeyes + */ +public enum FundsType { + + /** 1 订单支付收入 */ + ORDER_PAY_INCOME(1, "订单支付收入"), + /** 2 订单手续费 */ + ORDER_FEE(2, "订单手续费"), + /** 3 退款 */ + REFUND(3, "退款"), + /** 4 提现 */ + WITHDRAW(4, "提现"), + /** 5 提现失败退票 */ + WITHDRAW_FAIL(5, "提现失败退票"), + /** 6 导购分账 */ + GUIDE_SHARE(6, "导购分账"), + /** 7 联盟分账 */ + LEAGUE_SHARE(7, "联盟分账"), + /** 8 运费险分账 */ + FREIGHT_SHARE(8, "运费险分账"), + /** 9 联盟平台抽佣 */ + LEAGUE_PLAT_COMMISSION(9, "联盟平台抽佣"), + /** 10 联盟抽佣 */ + LEAGUE_COMMISSION(10, "联盟抽佣"), + /** 11台抽佣 */ + PLATFORM_COMMISSION(11, "平台抽佣"), + /** 12 团长抽佣 */ + LEADER_COMMISSION(12, "团长抽佣"), + /** 13 返佣人气卡 */ + POPULARITY_CARD(13, "返佣人气卡"), + /** 14 极速退款垫资金 */ + FAST_REFUND(14, "极速退款垫资金"), + /** 15 极速退款垫资回补 */ + FAST_REFUND_REPLENISHMENT(15, "极速退款垫资回补"), + /** 16 运费险 */ + FREIGHT_INSURANCE(16, "运费险"), + /** 99 分账 */ + SHARE(99, "分账"), + ; + + private final int key; + private final String value; + + FundsType(int key, String value) { + this.key = key; + this.value = value; + } + + public int getKey() { + return key; + } + + public String getValue() { + return value; + } +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/enums/LiveDistributionFlowType.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/enums/LiveDistributionFlowType.java new file mode 100644 index 0000000000..b086d02c34 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/enums/LiveDistributionFlowType.java @@ -0,0 +1,56 @@ +package me.chanjar.weixin.channel.enums; + +import com.fasterxml.jackson.annotation.JsonFormat; + +/** + * 视频号助手 直播分布流量类型 + * + * @author Winnie + */ +@JsonFormat(shape = JsonFormat.Shape.OBJECT) +public enum LiveDistributionFlowType { + + /** + * 无效值 + */ + INVALID(0, "无效值"), + /** + * 自然流量 + */ + NATURAL(1, "自然流量"), + /** + * 加热流量 + */ + PROMOTE(2, "加热流量"), + /** + * 广告流量 + */ + ADS(3, "广告流量"), + /** + * 公域流量 + */ + COMMON_DOMAIN(4, "公域流量"), + /** + * 私域流量 + */ + PRIVATE_DOMAIN(5, "私域流量"), + + ; + + private final Integer key; + private final String value; + + LiveDistributionFlowType(Integer key, String value) { + this.key = key; + this.value = value; + } + + public Integer getKey() { + return key; + } + + public String getValue() { + return value; + } + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/enums/LiveDistributionSceneType.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/enums/LiveDistributionSceneType.java new file mode 100644 index 0000000000..46a65f02b4 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/enums/LiveDistributionSceneType.java @@ -0,0 +1,52 @@ +package me.chanjar.weixin.channel.enums; + +import com.fasterxml.jackson.annotation.JsonFormat; + +/** + * 视频号助手 直播分布场景类型 + * + * @author Winnie + */ +@JsonFormat(shape = JsonFormat.Shape.OBJECT) +public enum LiveDistributionSceneType { + + /** + * 商品曝光 + */ + PRODUCT_IMPRESSION(6, "商品曝光"), + /** + * 直播间曝光次数 + */ + LIVE_ROOM_IMPRESSION_PV(7, "直播间曝光次数"), + /** + * 商品点击次数 + */ + PRODUCT_CLICK_PV(8, "商品点击次数"), + /** + * 创建订单数按渠道统计 + */ + CHANNEL_TOTAL_CREATE_PV(9, "创建订单数按渠道统计"), + /** + * 成交订单数按渠道统计 + */ + CHANNEL_TOTAL_PAY_PV(10, "成交订单数按渠道统计"), + + ; + + private final Integer key; + private final String value; + + LiveDistributionSceneType(Integer key, String value) { + this.key = key; + this.value = value; + } + + public Integer getKey() { + return key; + } + + public String getValue() { + return value; + } + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/enums/MessageType.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/enums/MessageType.java new file mode 100644 index 0000000000..4406104d9f --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/enums/MessageType.java @@ -0,0 +1,21 @@ +package me.chanjar.weixin.channel.enums; + +/** + * 消息类型 + * + * @author Zeyes + */ +public enum MessageType { + EVENT("event"), + ; + + private final String key; + + MessageType(String key) { + this.key = key; + } + + public String getKey() { + return key; + } +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/enums/OrderScene.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/enums/OrderScene.java new file mode 100644 index 0000000000..c00f6a8002 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/enums/OrderScene.java @@ -0,0 +1,52 @@ +package me.chanjar.weixin.channel.enums; + +/** + * 下单场景 + * + * @author lizhengwu + * @description + */ +public enum OrderScene { + /** + * 其他 + */ + OTHER(1, "其他"), + /** + * 直播间下单 + */ + LIVE(2, "直播间"), + /** + * 短视频 + */ + VIDEO(3, "短视频"), + /** + * 商品分享 + */ + SHARE(4, "商品分享"), + /** + * 商品橱窗主页 + */ + SHOW_CASE(5, "商品橱窗主页"), + /** + * 公众号文章商品卡片 + */ + ARTICLE_CARD(6, "公众号文章商品卡片"), + ; + + private final int key; + private final String value; + + OrderScene(int key, String value) { + this.key = key; + this.value = value; + } + + public int getKey() { + return key; + } + + public String getValue() { + return value; + } + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/enums/PackageAuditItemType.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/enums/PackageAuditItemType.java new file mode 100644 index 0000000000..2929b2d381 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/enums/PackageAuditItemType.java @@ -0,0 +1,37 @@ +package me.chanjar.weixin.channel.enums; + +import com.fasterxml.jackson.annotation.JsonFormat; + +/** + * 商品打包审核项 + * + * @author Zeyes + */ +@JsonFormat(shape = JsonFormat.Shape.OBJECT) +public enum PackageAuditItemType { + /** 商品快递单图片url */ + EXPRESS_PIC("product_express_pic_url", "商品快递单图片url"), + /** 商品包装箱图片url */ + BOX_PIC("product_packaging_box_pic_url", "商品包装箱图片url"), + /** 商品开箱图片url */ + UNBOXING_PIC("product_unboxing_pic_url", "商品开箱图片url"), + /** 商品单个细节图片url */ + DETAIL_PIC("single_product_detail_pic_url", "商品单个细节图片url"), + ; + + public final String key; + public final String value; + + PackageAuditItemType(String key, String value) { + this.key = key; + this.value = value; + } + + public String getKey() { + return key; + } + + public String getValue() { + return value; + } +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/enums/PromoteType.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/enums/PromoteType.java new file mode 100644 index 0000000000..c1ba1a3561 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/enums/PromoteType.java @@ -0,0 +1,36 @@ +package me.chanjar.weixin.channel.enums; + +import com.fasterxml.jackson.annotation.JsonFormat; + +/** + * 视频号小店 优惠券 推广类型 + * + * @author Zeyes + */ +@JsonFormat(shape = JsonFormat.Shape.OBJECT) +public enum PromoteType { + /** 1 小店内推广 */ + PROMOTE_TYPE_SHOP(1, "小店内推广"), + /** 9 会员券 */ + MEMBER(9, "会员券"), + /** 10 会员开卡礼券 */ + MEMBER_CARD(10, "会员开卡礼券"), + + ; + + private final int key; + private final String val; + + PromoteType(int key, String val) { + this.key = key; + this.val = val; + } + + public int getKey() { + return key; + } + + public String getVal() { + return val; + } +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/enums/QrCheckStatus.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/enums/QrCheckStatus.java new file mode 100644 index 0000000000..b0b2d64e31 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/enums/QrCheckStatus.java @@ -0,0 +1,46 @@ +package me.chanjar.weixin.channel.enums; + +/** + * 二维码核销状态 + * + * @author Zeyes + */ +public enum QrCheckStatus { + /** 0 未扫码 */ + NOT_SCAN(0, "未扫码"), + /** 1 已确认 */ + CONFIRMED(1, "已确认"), + /** 2 已取消 */ + CANCEL(2, "已取消"), + /** 3 已失效 */ + INVALID(3, "已失效"), + /** 4 已扫码 */ + SCAN(4, "已扫码"), + + ; + + private final int key; + private final String value; + + QrCheckStatus(int key, String value) { + this.key = key; + this.value = value; + } + + public static QrCheckStatus getByKey(Integer key) { + for (QrCheckStatus status : QrCheckStatus.values()) { + if (status.getKey() == key) { + return status; + } + } + return NOT_SCAN; + } + + public int getKey() { + return key; + } + + public String getValue() { + return value; + } +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/enums/RefundReason.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/enums/RefundReason.java new file mode 100644 index 0000000000..8a2825c28c --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/enums/RefundReason.java @@ -0,0 +1,51 @@ +package me.chanjar.weixin.channel.enums; + +import com.fasterxml.jackson.annotation.JsonFormat; + +/** + * 售后单退款直接原因 + * + * @author lizhengwu + */ +@JsonFormat(shape = JsonFormat.Shape.OBJECT) +public enum RefundReason { + /** 1 商家通过店铺管理页或者小助手发起退款 */ + MERCHANT_INITIATED_REFUND(1, "商家通过店铺管理页或者小助手发起退款"), + /** 2 退货退款场景,商家同意买家未上传物流单号情况下确认收货并退款,该场景限于订单无运费险 */ + MERCHANT_AGREES_NO_TRACKING_REFUND(2, "退货退款场景,商家同意买家未上传物流单号情况下确认收货并退款,该场景限于订单无运费险"), + /** 3 商家通过后台api发起退款 */ + MERCHANT_API_INITIATED_REFUND(3, "商家通过后台api发起退款"), + /** 4 未发货售后平台自动同意 */ + PRE_SHIPMENT_AUTOMATIC_REFUND(4, "未发货售后平台自动同意"), + /** 5 平台介入纠纷退款 */ + PLATFORM_INTERVENED_DISPUTE_REFUND(5, "平台介入纠纷退款"), + /** 6 特殊场景下平台强制退款 */ + PLATFORM_FORCED_REFUND(6, "特殊场景下平台强制退款"), + /** 7 退货退款场景,买家同意没有上传物流单号情况下,商家确认收货并退款,该场景限于订单包含运费险,并无法理赔 */ + BUYER_AGREES_NO_TRACKING_REFUND(7, "退货退款场景,买家同意没有上传物流单号情况下,商家确认收货并退款,该场景限于订单包含运费险,并无法理赔"), + /** 8 商家发货超时,平台退款 */ + LATE_SHIPMENT_PLATFORM_REFUND(8, "商家发货超时,平台退款"), + /** 9 商家处理买家售后申请超时,平台自动同意退款 */ + MERCHANT_OVERDUE_AUTO_REFUND(9, "商家处理买家售后申请超时,平台自动同意退款"), + /** 10 用户确认收货超时,平台退款 */ + BUYER_OVERDUE_AUTO_REFUND(10, "用户确认收货超时,平台退款"), + /** 11 商家确认收货超时,平台退款 */ + MERCHANT_OVERDUE_CONFIRMATION_REFUND(11, "商家确认收货超时,平台退款"), + ; + + private final int key; + private final String value; + + RefundReason(int key, String value) { + this.key = key; + this.value = value; + } + + public int getKey() { + return key; + } + + public String getValue() { + return value; + } +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/enums/SaleProfileUserType.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/enums/SaleProfileUserType.java new file mode 100644 index 0000000000..4bef97641e --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/enums/SaleProfileUserType.java @@ -0,0 +1,56 @@ +package me.chanjar.weixin.channel.enums; + +import com.fasterxml.jackson.annotation.JsonFormat; + +/** + * 带货人群用户类型 + * + * @author Winnie + */ +@JsonFormat(shape = JsonFormat.Shape.OBJECT) +public enum SaleProfileUserType { + + /** + * 商品曝光用户 + */ + PRODUCT_IMPRESSION_USER(1, "商品曝光用户"), + /** + * 商品点击用户 + */ + PRODUCT_CLICK_USER(2, "商品点击用户"), + /** + * 购买用户 + */ + PURCHASING_USER(3, "购买用户"), + /** + * 首购用户 + */ + FIRST_PURCHASE_USER(4, "首购用户"), + /** + * 复购用户 + */ + REPURCHASE_USER(5, "复购用户"), + /** + * 直播观看用户 + */ + LIVE_WATCHER_USER(6, "直播观看用户"), + + ; + + private final Integer key; + private final String value; + + SaleProfileUserType(Integer key, String value) { + this.key = key; + this.value = value; + } + + public Integer getKey() { + return key; + } + + public String getValue() { + return value; + } + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/enums/SendTime.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/enums/SendTime.java new file mode 100644 index 0000000000..9b5bc6e809 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/enums/SendTime.java @@ -0,0 +1,71 @@ +package me.chanjar.weixin.channel.enums; + +import com.fasterxml.jackson.annotation.JsonFormat; + +/** + * 视频号小店 发货时间 + * + * @author Zeyes + */ +@JsonFormat(shape = JsonFormat.Shape.OBJECT) +public enum SendTime { +// /** 4小时内发货 */ +// FOUR_HOUR("SendTime_FOUR_HOUR", "4小时内发货"), +// /** 8小时内发货 */ +// EIGHT_HOUR("SendTime_EIGHT_HOUR", "8小时内发货"), +// /** 12小时内发货 */ +// TWELVE_HOUR("SendTime_TWELVE_HOUR", "12小时内发货"), +// /** 16小时内发货 */ +// SIXTEEN_HOUR("SendTime_SIXTEEN_HOUR", "16小时内发货"), +// /** 20小时内发货 */ +// TWENTY_HOUR("SendTime_TWENTY_HOUR", "20小时内发货"), + /** 24小时内发货 */ + TWENTYFOUR_HOUR("SendTime_TWENTYFOUR_HOUR", "24小时内发货"), + /** 48小时内发货 */ + FOUTYEIGHT_HOUR("SendTime_FOUTYEIGHT_HOUR", "48小时内发货"), + /** + * 3天内发货 + * @deprecated 已不支持,微信小店发货管理规则调整 + */ + @Deprecated + THREE_DAY("SendTime_THREE_DAY", "3天内发货"), +// /** 5天内发货 */ +// FIVE_DAY("SendTime_FIVE_DAY", "5天内发货"), +// /** 7天内发货 */ +// SEVEN_DAY("SendTime_SEVEN_DAY", "7天内发货"), +// /** 10天内发货 */ +// TEN_DAY("SendTime_TEN_DAY", "10天内发货"), +// /** 12天内发货 */ +// TWELVE_DAY("SendTime_TWELVE_DAY", "12天内发货"), +// /** 14天内发货 */ +// FOUTEEN_DAY("SendTime_FOUTEEN_DAY", "14天内发货"), +// /** 16天内发货 */ +// SIXTEEN_DAY("SendTime_SIXTEEN_DAY", "16天内发货"), +// /** 20天内发货 */ +// TWENTY_DAY("SendTime_TWENTY_DAY", "20天内发货"), +// /** 25天内发货 */ +// TWENTYFIVE_DAY("SendTime_TWENTYFIVE_DAY", "25天内发货"), +// /** 30天内发货 */ +// THIRY_DAY("SendTime_THIRY_DAY", "30天内发货"), +// /** 35天内发货 */ +// THIRYFIVE_DAY("SendTime_THIRYFIVE_DAY", "35天内发货"), +// /** 45天内发货 */ +// FOURTYFIVE_DAY("SendTime_FOURTYFIVE_DAY", "45天内发货"), + ; + + private final String key; + private final String value; + + SendTime(String key, String value) { + this.key = key; + this.value = value; + } + + public String getKey() { + return key; + } + + public String getValue() { + return value; + } +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/enums/ShareScene.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/enums/ShareScene.java new file mode 100644 index 0000000000..e2fb09d132 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/enums/ShareScene.java @@ -0,0 +1,52 @@ +package me.chanjar.weixin.channel.enums; + +import com.fasterxml.jackson.annotation.JsonFormat; + +/** + * 分享场景 + * + * @author Zeyes + */ +@JsonFormat(shape = JsonFormat.Shape.OBJECT) +public enum ShareScene { + /** 1 直播间 */ + LIVE_ROOM(1, "直播间"), + /** 2 橱窗 */ + WINDOW(2, "橱窗"), + /** 3 短视频 */ + SHORT_VIDEO(3, "短视频"), + /** 4 视频号主页 */ + CHANNEL_HOME(4, "视频号主页"), + /** 5 商品详情页 */ + PRODUCT_DETAIL(5, "商品详情页"), + /** 6 带商品的公众号文章 */ + MP_ARTICLE(6, "带商品的公众号文章"), + /** 7 商品链接 */ + PRODUCT_LINK(7, "商品链接"), + /** 8 商品二维码 */ + PRODUCT_QR_CODE(8, "商品二维码"), + /** 9 商品口令 */ + PRODUCT_TAG_LINK(9, "商品口令"), + /** 12 视频号橱窗链接 */ + WINDOW_LINK(12, "视频号橱窗链接"), + /** 13 视频号橱窗二维码 */ + WINDOW_QR_CODE(13, "视频号橱窗二维码"), + ; + + + private final Integer key; + private final String value; + + ShareScene(Integer key, String value) { + this.key = key; + this.value = value; + } + + public Integer getKey() { + return key; + } + + public String getValue() { + return value; + } +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/enums/SharerType.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/enums/SharerType.java new file mode 100644 index 0000000000..8f0da3d760 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/enums/SharerType.java @@ -0,0 +1,35 @@ +package me.chanjar.weixin.channel.enums; + +import com.fasterxml.jackson.annotation.JsonFormat; + +/** + * 分享员类型 + * + * @author Zeyes + */ +@JsonFormat(shape = JsonFormat.Shape.OBJECT) +public enum SharerType { + /** 0 普通分享员 */ + NORMAL(0, "普通分享员"), + /** 1 企业分享员 */ + ENTERPRISE(1, "企业分享员"), + + ; + + + private final Integer key; + private final String value; + + SharerType(Integer key, String value) { + this.key = key; + this.value = value; + } + + public Integer getKey() { + return key; + } + + public String getValue() { + return value; + } +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/enums/SpuEditStatus.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/enums/SpuEditStatus.java new file mode 100644 index 0000000000..71c71d5a26 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/enums/SpuEditStatus.java @@ -0,0 +1,46 @@ +package me.chanjar.weixin.channel.enums; + +import com.fasterxml.jackson.annotation.JsonFormat; + +/** + * 商品编辑状态 + * + * @author Zeyes + */ +@JsonFormat(shape = JsonFormat.Shape.OBJECT) +public enum SpuEditStatus { + /** 0 初始值 */ + INIT(0, "初始值"), + /** 1 编辑中 */ + SUBMIT(1, "编辑中"), + /** 2 审核中 */ + ING(2, "审核中"), + /** 3 审核失败 */ + FAIL(3, "审核失败"), + /** 4 审核成功 */ + SUCCESS(4, "审核成功"), + /** 5 商品信息写入中 */ + WRITING(5, "商品信息写入中"), + /** 7 商品异步提交,上传中(处于该状态的商品调用上架商品接口会返回10020067) */ + ASYNC_WRITING(7, "商品异步提交,上传中"), + /** 8 商品异步提交,上传失败(请重新提交) */ + ASYNC_FAIL(8, "商品异步提交,上传失败"), + + ; + + private final int status; + private final String desc; + + SpuEditStatus(int status, String desc) { + this.status = status; + this.desc = desc; + } + + public int getStatus() { + return status; + } + + public String getDesc() { + return desc; + } +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/enums/SpuStatus.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/enums/SpuStatus.java new file mode 100644 index 0000000000..8f88d5ffac --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/enums/SpuStatus.java @@ -0,0 +1,49 @@ +package me.chanjar.weixin.channel.enums; + +import com.fasterxml.jackson.annotation.JsonFormat; + +/** + * 视频号小店 商品上下架状态 + * + * @author Zeyes + */ +@JsonFormat(shape = JsonFormat.Shape.OBJECT) +public enum SpuStatus { + + /** 0 初始值 */ + INIT(0, "未上架"), + /** 5 上架 */ + UP(5, "上架"), + /** 6 回收站 */ + TRASH(6, "回收站"), + /** 9 彻底删除,商品无法再进行任何操作 */ + DELETE(9, "彻底删除"), + /** 11 自主下架 */ + DOWN(11, "自主下架"), + /** 13 违规下架/风控系统下架 */ + SYSTEM_DOWN(13, "违规下架/风控系统下架"), + /** 14 保证金不足下架 */ + DEPOSIT_INSUFFICIENT(14, "保证金不足下架"), + /** 15 品牌过期下架 */ + BRAND_EXPIRED(15, "品牌过期下架"), + /** 20 商品被封禁 */ + BAN(20, "商品被封禁"), + +; + + private final int status; + private final String desc; + + SpuStatus(int status, String desc) { + this.status = status; + this.desc = desc; + } + + public int getStatus() { + return status; + } + + public String getDesc() { + return desc; + } +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/enums/UserCouponStatus.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/enums/UserCouponStatus.java new file mode 100644 index 0000000000..ce7e97df6c --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/enums/UserCouponStatus.java @@ -0,0 +1,33 @@ +package me.chanjar.weixin.channel.enums; + +/** + * 视频号小店 用户优惠券状态 + * + * @author Zeyes + */ +public enum UserCouponStatus { + /** 100 生效中 */ + VALID(100, "生效中"), + /** 101 已过期 */ + EXPIRED(101, "已过期"), + /** 102 已使用 */ + USED(102, "已使用"), + + ; + + private final int key; + private final String val; + + UserCouponStatus(int key, String val) { + this.key = key; + this.val = val; + } + + public int getKey() { + return key; + } + + public String getVal() { + return val; + } +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/enums/WithdrawStatus.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/enums/WithdrawStatus.java new file mode 100644 index 0000000000..2d1737cbd0 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/enums/WithdrawStatus.java @@ -0,0 +1,51 @@ +package me.chanjar.weixin.channel.enums; + +import com.fasterxml.jackson.annotation.JsonFormat; + +/** + * 视频号小店 提现状态 + * + * @author Zeyes + */ +@JsonFormat(shape = JsonFormat.Shape.OBJECT) +public enum WithdrawStatus { + /** 受理成功 */ + CREATE_SUCCESS("CREATE_SUCCESS", "受理成功"), + /** 提现成功 */ + SUCCESS("SUCCESS", "提现成功"), + /** 提现失败 */ + FAIL("FAIL", "提现失败"), + /** 提现退票 */ + REFUND("REFUND", "提现退票"), + /** 关单 */ + CLOSE("CLOSE", "关单"), + /** 业务单已创建 */ + INIT("INIT", "业务单已创建"), + ; + + private final String key; + private final String value; + + WithdrawStatus(String key, String value) { + this.key = key; + this.value = value; + } + + public static WithdrawStatus getByKey(String key) { + for (WithdrawStatus reason : WithdrawStatus.values()) { + if (reason.getKey().equals(key)) { + return reason; + } + } + // 找不到就返回其他了 + return FAIL; + } + + public String getKey() { + return key; + } + + public String getValue() { + return value; + } +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/enums/WxChannelErrorMsgEnum.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/enums/WxChannelErrorMsgEnum.java new file mode 100644 index 0000000000..18e88c0b08 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/enums/WxChannelErrorMsgEnum.java @@ -0,0 +1,65 @@ +package me.chanjar.weixin.channel.enums; + +import com.google.common.collect.Maps; +import java.util.Map; +import lombok.Getter; + +/** + * 微信视频号全局返回码 + * + * @author Zeyes + * @deprecated 请使用 {@link me.chanjar.weixin.common.error.WxChannelErrorMsgEnum} 替代 + */ +@Deprecated +@Getter +public enum WxChannelErrorMsgEnum { + /** + * 系统繁忙,此时请开发者稍候再试 system error + */ + CODE_1(-1, "系统繁忙,此时请开发者稍候再试"), + + /** + * 请求成功 ok + */ + CODE_0(0, "请求成功"), + + /** + * AppSecret 错误或者 AppSecret 不属于这个小店,请开发者确认 AppSecret 的正确性 + */ + CODE_40001(40001, "AppSecret 错误或者 AppSecret 不属于这个小店,请开发者确认 AppSecret 的正确性"), + + /** + * 请确保 grant_type 字段值为 client_credential + */ + CODE_40002(40002, "请确保 grant_type 字段值为 client_credential"), + + /** + * 不合法的 AppID,请开发者检查 AppID 的正确性,避免异常字符,注意大小写 + */ + CODE_40013(40013, "不合法的 AppID,请开发者检查 AppID 的正确性,避免异常字符,注意大小写"), + + ; + + private final int code; + private final String msg; + + WxChannelErrorMsgEnum(int code, String msg) { + this.code = code; + this.msg = msg; + } + + static final Map valueMap = Maps.newHashMap(); + + static { + for (WxChannelErrorMsgEnum value : WxChannelErrorMsgEnum.values()) { + valueMap.put(value.code, value.msg); + } + } + + /** + * 通过错误代码查找其中文含义. + */ + public static String findMsgByCode(int code) { + return valueMap.getOrDefault(code, null); + } +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/enums/WxCouponStatus.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/enums/WxCouponStatus.java new file mode 100644 index 0000000000..593873fbe4 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/enums/WxCouponStatus.java @@ -0,0 +1,35 @@ +package me.chanjar.weixin.channel.enums; + +/** + * 视频号小店 优惠券状态 + * + * @author Zeyes + */ +public enum WxCouponStatus { + /** 1 初始 */ + INIT(1, "初始"), + /** 2 生效 */ + VALID(2, "生效"), + /** 4 已作废 */ + INVALID(4, "已作废"), + /** 5 删除 */ + DELETE(5, "删除"), + + ; + + private final int key; + private final String val; + + WxCouponStatus(int key, String val) { + this.key = key; + this.val = val; + } + + public int getKey() { + return key; + } + + public String getVal() { + return val; + } +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/enums/WxOrderStatus.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/enums/WxOrderStatus.java new file mode 100644 index 0000000000..b064335221 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/enums/WxOrderStatus.java @@ -0,0 +1,73 @@ +package me.chanjar.weixin.channel.enums; + +/** + * 视频号小店 订单状态 + * + * @author Zeyes + */ +public enum WxOrderStatus { + /** 10 待付款 */ + UNPAID(10, "待付款"), + /** 20 待发货(已付款/用户已付尾款) */ + PAID(20, "待发货"), + /** 21 部分发货 */ + PART_DELIVERY(21, "部分发货"), + /** 30 待收货 */ + DELIVERY(30, "待收货"), + /** 100 完成 */ + COMPLETED(100, "已完成"), + /** 190 商品超卖商家取消订单 */ + UNPAID_CANCEL(190, "已取消"), + /** 200 全部商品售后之后,订单取消 */ + ALL_AFTER_SALE(200, "已取消"), + /** 250 用户主动取消/待付款超时取消/商家取消 */ + CANCEL(250, "已取消"); + + private final int key; + + private final String val; + + WxOrderStatus(int key, String val) { + this.key = key; + this.val = val; + } + + public int getKey() { + return key; + } + + public String getVal() { + return val; + } + + /** + * 获取状态中文 + * + * @param key 状态码 + * @return 状态 + */ + public static String getStatusStr(Integer key) { + if (key == null) { + return "未知"; + } + for (WxOrderStatus status : WxOrderStatus.values()) { + if (key.equals(status.getKey())) { + return status.getVal(); + } + } + return String.valueOf(key); + } + + /** + * 判断是否在取消状态 + * + * @param key key + * @return boolean + */ + public static boolean isCancel(Integer key) { + if (key == null) { + return false; + } + return key.equals(UNPAID_CANCEL.key) || key.equals(ALL_AFTER_SALE.key) || key.equals(CANCEL.key); + } +} 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 new file mode 100644 index 0000000000..78a6735192 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/executor/ChannelFileUploadRequestExecutor.java @@ -0,0 +1,35 @@ +package me.chanjar.weixin.channel.executor; + +import me.chanjar.weixin.common.util.http.RequestExecutor; +import me.chanjar.weixin.common.util.http.RequestHttp; + +import java.io.File; + +/** + * 视频号小店 图片上传接口 请求的参数是File, 返回的结果是String + * + * @author Zeyes + */ +public abstract class ChannelFileUploadRequestExecutor implements RequestExecutor { + + protected RequestHttp requestHttp; + + public ChannelFileUploadRequestExecutor(RequestHttp requestHttp) { + this.requestHttp = requestHttp; + } + + @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()); + } + } + +} 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 new file mode 100644 index 0000000000..dd4bf0ba89 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/executor/ChannelMediaDownloadRequestExecutor.java @@ -0,0 +1,82 @@ +package me.chanjar.weixin.channel.executor; + +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; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.UUID; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import static org.apache.commons.io.FileUtils.openOutputStream; + +/** + * 下载媒体文件请求执行器 + * + * @author Zeyes + */ +public abstract class ChannelMediaDownloadRequestExecutor implements RequestExecutor { + + protected RequestHttp requestHttp; + protected File tmpDirFile; + + private static final Pattern PATTERN = Pattern.compile(".*filename=\"(.*)\""); + + public ChannelMediaDownloadRequestExecutor(RequestHttp requestHttp, File tmpDirFile) { + this.requestHttp = requestHttp; + this.tmpDirFile = tmpDirFile; + } + + @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()); + } + } + + /** + * 创建临时文件 + * + * @param inputStream 输入文件流 + * @param name 文件名 + * @param ext 扩展名 + * @param tmpDirFile 临时文件夹目录 + */ + public static File createTmpFile(InputStream inputStream, String name, String ext, File tmpDirFile) + throws IOException { + File resultFile = File.createTempFile(name, '.' + ext, tmpDirFile); + resultFile.deleteOnExit(); + try (InputStream in = inputStream; OutputStream out = openOutputStream(resultFile)) { + IOUtils.copy(in, out); + } + return resultFile; + } + + protected String createDefaultFileName() { + return UUID.randomUUID().toString(); + } + + protected String extractFileNameFromContentString(String content) { + if (content == null || content.isEmpty()) { + return createDefaultFileName(); + } + Matcher m = PATTERN.matcher(content); + if (m.matches()) { + return m.group(1); + } + return createDefaultFileName(); + } + +} 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/WxChannelMessage.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/message/WxChannelMessage.java new file mode 100644 index 0000000000..f6e3386a5a --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/message/WxChannelMessage.java @@ -0,0 +1,125 @@ +package me.chanjar.weixin.channel.message; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlCData; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement; +import java.io.Serializable; +import me.chanjar.weixin.channel.util.JsonUtils; + +/** + * 视频号 消息 兼容Json和xml + * + * @author Zeyes + */ +@JacksonXmlRootElement(localName = "xml") +public class WxChannelMessage implements Serializable { + + private static final long serialVersionUID = -6929595548318897649L; + + @JsonProperty("ToUserName") + @JacksonXmlProperty(localName = "ToUserName") + @JacksonXmlCData + private String toUser; + + @JsonProperty("FromUserName") + @JacksonXmlProperty(localName = "FromUserName") + @JacksonXmlCData + private String fromUser; + + @JsonProperty("CreateTime") + @JacksonXmlProperty(localName = "CreateTime") + private Long createTime; + + @JsonProperty("MsgType") + @JacksonXmlProperty(localName = "MsgType") + @JacksonXmlCData + private String msgType; + + @JsonProperty("Event") + @JacksonXmlProperty(localName = "Event") + @JacksonXmlCData + private String event; + + @JsonProperty("Encrypt") + @JacksonXmlProperty(localName = "Encrypt") + @JacksonXmlCData + private String encrypt; + + @JsonProperty("MsgId") + @JacksonXmlProperty(localName = "MsgId") + private Long msgId; + + @JsonProperty("MsgID") + @JacksonXmlProperty(localName = "MsgID") + private void msgIdFill(Long msgId) { + if (msgId != null) { + this.msgId = msgId; + } + } + + @Override + public String toString() { + return this.toJson(); + } + + public String toJson() { + return JsonUtils.encode(this); + } + + public String getToUser() { + return toUser; + } + + public String getFromUser() { + return fromUser; + } + + public Long getCreateTime() { + return createTime; + } + + public String getMsgType() { + return msgType; + } + + public String getEvent() { + return event; + } + + public String getEncrypt() { + return encrypt; + } + + public Long getMsgId() { + return msgId; + } + + public void setToUser(String toUser) { + this.toUser = toUser; + } + + public void setFromUser(String fromUser) { + this.fromUser = fromUser; + } + + public void setCreateTime(Long createTime) { + this.createTime = createTime; + } + + public void setMsgType(String msgType) { + this.msgType = msgType; + } + + public void setEvent(String event) { + this.event = event; + } + + public void setEncrypt(String encrypt) { + this.encrypt = encrypt; + } + + public void setMsgId(Long msgId) { + this.msgId = msgId; + } +} 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 new file mode 100644 index 0000000000..3236e18303 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/message/WxChannelMessageRouter.java @@ -0,0 +1,226 @@ +package me.chanjar.weixin.channel.message; + +import com.google.common.util.concurrent.ThreadFactoryBuilder; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Future; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import lombok.Data; +import lombok.extern.slf4j.Slf4j; +import me.chanjar.weixin.channel.api.WxChannelService; +import me.chanjar.weixin.common.api.WxErrorExceptionHandler; +import me.chanjar.weixin.common.api.WxMessageDuplicateChecker; +import me.chanjar.weixin.common.api.WxMessageInMemoryDuplicateCheckerSingleton; +import me.chanjar.weixin.common.session.InternalSession; +import me.chanjar.weixin.common.session.InternalSessionManager; +import me.chanjar.weixin.common.session.StandardSessionManager; +import me.chanjar.weixin.common.session.WxSessionManager; +import me.chanjar.weixin.common.util.LogExceptionHandler; +import org.apache.commons.lang3.StringUtils; + +/** + * 消息路由器 + * + * @author Zeyes + */ +@Data +@Slf4j +public class WxChannelMessageRouter { + /** 规则列表 */ + private final List> rules = new ArrayList<>(); + /** 线程池 */ + private ExecutorService executorService; + /** 异常处理器 */ + private WxErrorExceptionHandler exceptionHandler; + /** 消息重复检查器 */ + private WxMessageDuplicateChecker messageDuplicateChecker; + + public WxChannelMessageRouter() { + ThreadFactory namedThreadFactory = new ThreadFactoryBuilder().setNameFormat("WxChMsgRouter-pool-%d").build(); + this.executorService = new ThreadPoolExecutor(2, 100, + 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(), namedThreadFactory); + //this.sessionManager = new StandardSessionManager(); + this.exceptionHandler = new LogExceptionHandler(); + this.messageDuplicateChecker = WxMessageInMemoryDuplicateCheckerSingleton.getInstance(); + } + + /** + * 使用自定义的 {@link ExecutorService}. + */ + public WxChannelMessageRouter(ExecutorService executorService) { + this.executorService = executorService; + this.exceptionHandler = new LogExceptionHandler(); + this.messageDuplicateChecker = WxMessageInMemoryDuplicateCheckerSingleton.getInstance(); + } + + /** + * 系统退出前,应该调用该方法 + */ + public void shutDownExecutorService() { + this.executorService.shutdown(); + } + + /** + * 系统退出前,应该调用该方法,增加了超时时间检测 + */ + public void shutDownExecutorService(Integer second) { + this.executorService.shutdown(); + try { + if (!this.executorService.awaitTermination(second, TimeUnit.SECONDS)) { + this.executorService.shutdownNow(); + if (!this.executorService.awaitTermination(second, TimeUnit.SECONDS)) { + log.error("线程池未关闭!"); + } + } + } catch (InterruptedException ie) { + this.executorService.shutdownNow(); + Thread.currentThread().interrupt(); + } + } + + /** + *
+   * 设置自定义的 {@link ExecutorService}
+   * 如果不调用该方法,默认使用内置的
+   * 
+ */ + public void setExecutorService(ExecutorService executorService) { + this.executorService = executorService; + } + + /** + * 消息路由入口 + * + * @param message 消息 + * @param content 原始消息(解密之后的) + * @param appId appId + * @param service 服务实例 + * @return 返回值 + */ + public Object route(final WxChannelMessage message, final String content, final String appId, + final WxChannelService service) { + return this.route(message, content, appId, new HashMap<>(2), service, new StandardSessionManager()); + } + + /** + * 路由微信消息 + * + * @param message 消息 + * @param content 消息原始内容 + * @param context 上下文 + * @return Object + */ + public Object route(final WxChannelMessage message, final String content, final String appId, + final Map context, final WxChannelService service, final WxSessionManager sessionManager) { + // 如果是重复消息,那么就不做处理 + if (isMsgDuplicated(message)) { + log.info("收到重复消息,{}", content); + return null; + } + + final List> matchRules = new ArrayList<>(); + + // 收集匹配的规则 + for (final WxChannelMessageRouterRule rule : this.rules) { + if (rule.isMatch(message)) { + matchRules.add(rule); + if (!rule.isNext()) { + break; + } + } + } + + if (matchRules.isEmpty()) { + return null; + } + final List> futures = new ArrayList<>(); + Object result = null; + for (final WxChannelMessageRouterRule rule : matchRules) { + // 返回最后一个非异步的rule的执行结果 + if (rule.isAsync()) { + futures.add( + this.executorService.submit(() -> { + rule.process(message, content, appId, context, service, sessionManager, exceptionHandler); + }) + ); + } else { + result = rule.process(message, content, appId, context, service, sessionManager, exceptionHandler); + // 在同步操作结束,session访问结束 + sessionEndAccess(sessionManager, message, false); + } + } + + if (!futures.isEmpty()) { + this.executorService.submit(() -> { + for (Future future : futures) { + try { + future.get(); + // 异步操作结束,session访问结束 + sessionEndAccess(sessionManager, message, true); + } catch (InterruptedException | ExecutionException e) { + log.error("Error happened when wait task finish", e); + } + } + }); + } + return result; + } + + /** + * 判断消息是否重复 + * + * @param wxMessage 消息 + * @return 是否重复 + */ + protected boolean isMsgDuplicated(WxChannelMessage wxMessage) { + String messageId = this.generateMessageId(wxMessage); + return this.messageDuplicateChecker.isDuplicate(messageId); + } + + /** + * 生成消息id + * + * @return 消息id + */ + protected String generateMessageId(WxChannelMessage wxMessage) { + StringBuilder sb = new StringBuilder(); + if (wxMessage.getMsgId() == null) { + sb.append(wxMessage.getCreateTime()) + .append("-").append(wxMessage.getFromUser()) + .append("-").append(StringUtils.trimToEmpty(wxMessage.getEvent())); + } else { + sb.append(wxMessage.getMsgId()) + .append("-").append(wxMessage.getCreateTime()) + .append("-").append(wxMessage.getFromUser()); + } + + if (StringUtils.isNotEmpty(wxMessage.getToUser())) { + sb.append("-").append(wxMessage.getToUser()); + } + return sb.toString(); + } + + /** + * 对session的访问结束 + * + * @param sessionManager session管理器 + * @param message 消息 + * @param async 是否异步 打印log用 + */ + private void sessionEndAccess(WxSessionManager sessionManager, WxChannelMessage message, boolean async) { + log.debug("End session access: async={}, sessionId={}", async, message.getFromUser()); + InternalSession session = ((InternalSessionManager) sessionManager).findSession(message.getFromUser()); + if (session != null) { + session.endAccess(); + } + } + + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/message/WxChannelMessageRouterRule.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/message/WxChannelMessageRouterRule.java new file mode 100644 index 0000000000..60c5e9f83e --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/message/WxChannelMessageRouterRule.java @@ -0,0 +1,172 @@ +package me.chanjar.weixin.channel.message; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import lombok.Data; +import lombok.Singular; +import lombok.experimental.Accessors; +import lombok.extern.slf4j.Slf4j; +import me.chanjar.weixin.channel.api.WxChannelService; +import me.chanjar.weixin.channel.enums.MessageType; +import me.chanjar.weixin.channel.message.rule.WxChannelMessageHandler; +import me.chanjar.weixin.channel.message.rule.WxChannelMessageInterceptor; +import me.chanjar.weixin.channel.message.rule.WxChannelMessageMatcher; +import me.chanjar.weixin.channel.util.JsonUtils; +import me.chanjar.weixin.channel.util.XmlUtils; +import me.chanjar.weixin.common.api.WxErrorExceptionHandler; +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.common.session.WxSessionManager; +import org.apache.commons.lang3.StringUtils; + +/** + * @author Zeyes + */ +@Data +@Accessors(chain = true) +@Slf4j +public class WxChannelMessageRouterRule { + /** 是否异步, 默认是true */ + private boolean async = true; + /** 消息类型 */ + private String msgType; + /** 事件类型 */ + private String event; + /** 自定义匹配器 */ + private WxChannelMessageMatcher matcher; + /** 进入下一个rule,默认是false */ + private boolean next = false; + /** 消息处理器 */ + @Singular + private List> handlers = new ArrayList<>(4); + /** 消息拦截器 */ + @Singular + private List interceptors = new ArrayList<>(4); + /** 消息类型 */ + private Class messageClass; + + public WxChannelMessageRouterRule() { + } + + /** + * 设置事件 + * + * @param event 事件 + * @return this + */ + public WxChannelMessageRouterRule setEvent(String event) { + this.msgType = MessageType.EVENT.getKey(); + this.event = event; + return this; + } + + /** + * 测试消息是否匹配规则 + * + * @param message 消息 + * @return 是否匹配 + */ + protected boolean isMatch(WxChannelMessage message) { + String msgType = message.getMsgType() == null ? null : message.getMsgType().toLowerCase(); + String event = message.getEvent() == null ? null : message.getEvent().toLowerCase(); + + boolean matchMsgType = this.msgType == null || this.msgType.toLowerCase().equals(msgType); + boolean matchEvent = this.event == null || this.event.toLowerCase().equals(event); + boolean matchMatcher = this.matcher == null || this.matcher.match(message); + + return matchMsgType && matchEvent && matchMatcher; + } + + /** + * 处理微信推送过来的消息 + * + * @param message 消息 + * @param content 消息原始内容 + * @param appId appId + * @param context 上下文,如果handler或interceptor之间有信息要传递,可以用这个 + * @param service 服务实例 + * @param sessionManager session管理器 + * @param exceptionHandler 异常处理器 + * @return 返回消息 + */ + protected Object process(WxChannelMessage message, String content, String appId, Map context, + WxChannelService service, WxSessionManager sessionManager, WxErrorExceptionHandler exceptionHandler) { + if (context == null) { + context = new HashMap<>(16); + } + // 重新反序列化消息 + T tempMessage = deserialize(content, messageClass, service); + if (tempMessage == null) { + log.error("消息重新反序列化失败,请检查消息格式是否正确或者指定正确的messageClass"); + return null; + } + + Object outMessage = null; + try { + // 如果拦截器不通过,返回null + for (WxChannelMessageInterceptor interceptor : this.interceptors) { + if (!interceptor.intercept(message, content, context, service, sessionManager)) { + return null; + } + } + + // 交给handler处理 + for (WxChannelMessageHandler handler : this.handlers) { + // 返回最后handler的结果 + if (handler == null) { + continue; + } + + outMessage = handler.handle(tempMessage, content, appId, context, sessionManager); + } + } catch (WxErrorException e) { + exceptionHandler.handle(e); + } + return outMessage; + } + + /** + * 重新反序列化消息 + * + * @param content 消息内容 + * @param clazz 消息类型 + * @param service 服务实例 + * @return T + */ + private T deserialize(String content, Class clazz, WxChannelService service) { + String msgFormat = service.getConfig().getMsgDataFormat(); + T t = deserialize(content, clazz, msgFormat); + if (t != null) { + return t; + } + // 如果指定的消息格式不正确,根据内容猜猜格式 + if (StringUtils.isNotBlank(content)) { + if (content.startsWith("")) { + t = deserialize(content, clazz, "XML"); + } else if (content.startsWith("{")){ + t = deserialize(content, clazz, "JSON"); + } + } + return t; + } + + /** + * 重新反序列化消息 + * + * @param content 消息内容 + * @param clazz 消息类型 + * @param msgFormat 消息格式 + * @return T + */ + private T deserialize(String content, Class clazz, String msgFormat) { + T message = null; + // 重新反序列化原始消息 + if (msgFormat == null || msgFormat.equalsIgnoreCase("JSON")) { + message = JsonUtils.decode(content, clazz); + } else { + message = XmlUtils.decode(content, clazz); + } + return message; + } +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/message/rule/HandlerConsumer.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/message/rule/HandlerConsumer.java new file mode 100644 index 0000000000..522290b178 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/message/rule/HandlerConsumer.java @@ -0,0 +1,12 @@ +package me.chanjar.weixin.channel.message.rule; + +import me.chanjar.weixin.channel.message.WxChannelMessage; + +/** + * @author Zeyes + */ +@FunctionalInterface +public interface HandlerConsumer { + + void accept(T t, U u, V v, W w, X x); +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/message/rule/WxChannelMessageHandler.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/message/rule/WxChannelMessageHandler.java new file mode 100644 index 0000000000..4918365bf9 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/message/rule/WxChannelMessageHandler.java @@ -0,0 +1,30 @@ +package me.chanjar.weixin.channel.message.rule; + +import java.util.Map; +import me.chanjar.weixin.channel.message.WxChannelMessage; +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.common.session.WxSessionManager; + +/** + * 处理视频号推送消息的处理器 + * + * @author Zeyes + */ +public interface WxChannelMessageHandler { + + /** + * 处理消息 + * + * @param message 消息 + * @param content 消息原始内容 + * @param appId appId + * @param context 上下文 + * @param sessionManager session管理器 + * @return 输出消息 格式可能是String/Xml/Json,视情况而定 + * + * @throws WxErrorException 异常 + */ + Object handle(T message, String content, String appId, Map context, WxSessionManager sessionManager) + throws WxErrorException; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/message/rule/WxChannelMessageInterceptor.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/message/rule/WxChannelMessageInterceptor.java new file mode 100644 index 0000000000..dbb06cb6cc --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/message/rule/WxChannelMessageInterceptor.java @@ -0,0 +1,31 @@ +package me.chanjar.weixin.channel.message.rule; + +import java.util.Map; +import me.chanjar.weixin.channel.api.WxChannelService; +import me.chanjar.weixin.channel.message.WxChannelMessage; +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.common.session.WxSessionManager; + +/** + * 微信消息拦截器,可以用来做验证 + * + * @author Zeyes + */ +public interface WxChannelMessageInterceptor { + + /** + * 拦截微信消息 + * + * @param message 消息 + * @param content 消息原始内容 + * @param context 上下文,如果handler或interceptor之间有信息要传递,可以用这个 + * @param service 服务实例 + * @param sessionManager session管理器 + * @return true代表OK,false代表不OK + * + * @throws WxErrorException 异常 + */ + boolean intercept(WxChannelMessage message, String content, Map context, WxChannelService service, + WxSessionManager sessionManager) throws WxErrorException; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/message/rule/WxChannelMessageMatcher.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/message/rule/WxChannelMessageMatcher.java new file mode 100644 index 0000000000..c6e6824ca0 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/message/rule/WxChannelMessageMatcher.java @@ -0,0 +1,20 @@ +package me.chanjar.weixin.channel.message.rule; + +import me.chanjar.weixin.channel.message.WxChannelMessage; + +/** + * 消息匹配器,用在消息路由的时候 + * + * @author Zeyes + */ +public interface WxChannelMessageMatcher { + + /** + * 消息是否匹配某种模式 + * + * @param message 消息 + * @return 是否匹配 + */ + boolean match(WxChannelMessage message); + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/util/JsonUtils.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/util/JsonUtils.java new file mode 100644 index 0000000000..ce7e754e00 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/util/JsonUtils.java @@ -0,0 +1,100 @@ +package me.chanjar.weixin.channel.util; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.core.json.JsonReadFeature; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.json.JsonMapper; +import java.io.IOException; +import lombok.extern.slf4j.Slf4j; + +/** + * Json序列化/反序列化工具类 + * + * @author Zeyes + */ +@Slf4j +public class JsonUtils { + + private static final JsonMapper JSON_MAPPER = new JsonMapper(); + + static { + JSON_MAPPER.enable(JsonReadFeature.ALLOW_JAVA_COMMENTS.mappedFeature()); + JSON_MAPPER.enable(JsonReadFeature.ALLOW_UNQUOTED_FIELD_NAMES.mappedFeature()); + JSON_MAPPER.enable(JsonReadFeature.ALLOW_SINGLE_QUOTES.mappedFeature()); + JSON_MAPPER.enable(JsonReadFeature.ALLOW_UNESCAPED_CONTROL_CHARS.mappedFeature()); + JSON_MAPPER.setSerializationInclusion(JsonInclude.Include.NON_NULL); + JSON_MAPPER.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); + JSON_MAPPER.disable(DeserializationFeature.FAIL_ON_IGNORED_PROPERTIES); + } + + private JsonUtils() { + } + + /** + * 对象序列化 + * + * @param obj 对象 + * @return json + */ + public static String encode(Object obj) { + try { + return JSON_MAPPER.writeValueAsString(obj); + } catch (IOException e) { + log.error("encode(Object)", e); + } + return null; + } + + /** + * 对象序列化 + * + * @param objectMapper ObjectMapper + * @param obj obj + * @return json + */ + public static String encode(ObjectMapper objectMapper, Object obj) { + try { + return objectMapper.writeValueAsString(obj); + } catch (IOException e) { + log.error("encode(Object)", e); + } + return null; + } + + /** + * 将json反序列化成对象 + * + * @param json json + * @param valueType Class + * @return T + */ + public static T decode(String json, Class valueType) { + if (json == null || json.length() <= 0) { + return null; + } + try { + return JSON_MAPPER.readValue(json, valueType); + } catch (IOException e) { + log.info("decode(String, Class)", e); + } + return null; + } + + /** + * 将json反序列化为对象 + * + * @param json json + * @param typeReference TypeReference + * @return T + */ + public static T decode(String json, TypeReference typeReference) { + try { + return (T) JSON_MAPPER.readValue(json, typeReference); + } catch (IOException e) { + log.info("decode(String, JsonTypeReference)", e); + } + return null; + } +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/util/ResponseUtils.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/util/ResponseUtils.java new file mode 100644 index 0000000000..865eab6354 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/util/ResponseUtils.java @@ -0,0 +1,63 @@ +package me.chanjar.weixin.channel.util; + + +import static me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse.INTERNAL_ERROR_CODE; + +import java.lang.reflect.InvocationTargetException; +import lombok.experimental.UtilityClass; +import lombok.extern.slf4j.Slf4j; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; +import org.apache.commons.lang3.StringUtils; + +/** + * 响应工具类 + * + * @author Zeyes + */ +@Slf4j +@UtilityClass +public class ResponseUtils { + + /** + * 将json反序列化成对象 + * + * @param json json + * @param valueType Class + * @return T + */ + public static T decode(String json, Class valueType) { + T t = null; + try { + if (StringUtils.isNotBlank(json)) { + t = JsonUtils.decode(json, valueType); + } + } catch (Exception e) { + log.error("decode", e); + } + if (t == null) { + t = internalError(valueType); + } + return t; + } + + /** + * 设置系统内部错误 + * + * @param clazz 类 + * @param T + * @return 错误 + */ + public static T internalError(Class clazz) { + try { + T t = clazz.getDeclaredConstructor().newInstance(); + t.setErrCode(INTERNAL_ERROR_CODE); + t.setErrMsg("内部错误"); + return t; + } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) { + log.error("internalError", e); + } + // 正常情况下不会执行到这里 + return null; + } + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/util/WxChCryptUtils.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/util/WxChCryptUtils.java new file mode 100644 index 0000000000..3e6062f308 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/util/WxChCryptUtils.java @@ -0,0 +1,51 @@ +package me.chanjar.weixin.channel.util; + +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.security.AlgorithmParameters; +import javax.crypto.Cipher; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; +import lombok.extern.slf4j.Slf4j; +import me.chanjar.weixin.channel.config.WxChannelConfig; +import me.chanjar.weixin.common.util.crypto.PKCS7Encoder; +import me.chanjar.weixin.common.util.crypto.WxCryptUtil; +import org.apache.commons.codec.binary.Base64; +import org.apache.commons.lang3.StringUtils; + +/** + * @author Zeyes + */ +@Slf4j +public class WxChCryptUtils extends WxCryptUtil { + + protected static final Charset UTF_8 = StandardCharsets.UTF_8; + + public WxChCryptUtils(WxChannelConfig config) { + this.appidOrCorpid = config.getAppid(); + this.token = config.getToken(); + this.aesKey = Base64.decodeBase64(StringUtils.trim(config.getAesKey()) + "="); + } + + /** + * AES解密 + * + * @param sessionKey session_key + * @param encryptedData 消息密文 + * @param ivStr iv字符串 + */ + public static String decrypt(String sessionKey, String encryptedData, String ivStr) { + try { + AlgorithmParameters params = AlgorithmParameters.getInstance("AES"); + params.init(new IvParameterSpec(Base64.decodeBase64(ivStr))); + + Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding"); + cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(Base64.decodeBase64(sessionKey), "AES"), params); + + return new String(PKCS7Encoder.decode(cipher.doFinal(Base64.decodeBase64(encryptedData))), UTF_8); + } catch (Exception e) { + throw new RuntimeException("AES解密失败!", e); + } + } + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/util/XmlUtils.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/util/XmlUtils.java new file mode 100644 index 0000000000..2e34fef571 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/util/XmlUtils.java @@ -0,0 +1,113 @@ +package me.chanjar.weixin.channel.util; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.dataformat.xml.XmlMapper; +import com.fasterxml.jackson.dataformat.xml.ser.ToXmlGenerator; +import java.io.IOException; +import java.io.InputStream; +import lombok.extern.slf4j.Slf4j; + +/** + * Xml序列化/反序列化工具类 + * + * @author Zeyes + */ +@Slf4j +public class XmlUtils { + + private static final XmlMapper XML_MAPPER = new XmlMapper(); + + static { + XML_MAPPER.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); + XML_MAPPER.disable(DeserializationFeature.FAIL_ON_IGNORED_PROPERTIES); + // 带有xml文件头, + XML_MAPPER.disable(ToXmlGenerator.Feature.WRITE_XML_DECLARATION); + } + + private XmlUtils() { + } + + /** + * 对象序列化 + * + * @param obj 对象 + * @return json + */ + public static String encode(Object obj) { + try { + return XML_MAPPER.writeValueAsString(obj); + } catch (IOException e) { + log.error("encode(Object)", e); + } + return null; + } + + /** + * 对象序列化 + * + * @param objectMapper ObjectMapper + * @param obj obj + * @return json + */ + public static String encode(ObjectMapper objectMapper, Object obj) { + try { + return objectMapper.writeValueAsString(obj); + } catch (IOException e) { + log.error("encode(Object)", e); + } + return null; + } + + /** + * 将xml反序列化成对象 + * + * @param xml xml + * @param valueType Class + * @return T + */ + public static T decode(String xml, Class valueType) { + if (xml == null || xml.length() <= 0) { + return null; + } + try { + return XML_MAPPER.readValue(xml, valueType); + } catch (IOException e) { + log.info("decode(String, Class)", e); + } + return null; + } + + /** + * 将xml反序列化为对象 + * + * @param xml xml + * @param typeReference TypeReference + * @return T + */ + public static T decode(String xml, TypeReference typeReference) { + try { + return (T) XML_MAPPER.readValue(xml, typeReference); + } catch (IOException e) { + log.info("decode(String, TypeReference)", e); + } + return null; + } + + /** + * 将xml反序列化为对象 + * + * @param is InputStream + * @param valueType Class + * @return T + */ + public static T decode(InputStream is, Class valueType) { + try { + return (T) XML_MAPPER.readValue(is, valueType); + } catch (IOException e) { + log.info("decode(InputStream, Class)", e); + } + return null; + } +} diff --git a/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/api/impl/WxAssistantServiceImplTest.java b/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/api/impl/WxAssistantServiceImplTest.java new file mode 100644 index 0000000000..d2d4ec1dac --- /dev/null +++ b/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/api/impl/WxAssistantServiceImplTest.java @@ -0,0 +1,65 @@ +package me.chanjar.weixin.channel.api.impl; + +import com.google.inject.Inject; +import me.chanjar.weixin.channel.api.WxChannelService; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; +import me.chanjar.weixin.channel.bean.window.request.AddWindowProductRequest; +import me.chanjar.weixin.channel.bean.window.request.GetWindowProductListRequest; +import me.chanjar.weixin.channel.bean.window.request.WindowProductRequest; +import me.chanjar.weixin.channel.test.ApiTestModule; +import me.chanjar.weixin.common.error.WxErrorException; +import org.testng.annotations.Guice; +import org.testng.annotations.Test; + +import static org.testng.Assert.assertNotNull; +import static org.testng.Assert.assertTrue; + +/** + * @author imyzt + */ +@Guice(modules = ApiTestModule.class) +public class WxAssistantServiceImplTest { + + @Inject + private WxChannelService channelService; + + @Test + public void testAddWindowProduct() throws WxErrorException { + AddWindowProductRequest req = new AddWindowProductRequest(); + req.setProductId("123"); + req.setAppid(channelService.getConfig().getAppid()); + req.setIsHideForWindow(true); + WxChannelBaseResponse response = channelService.getAssistantService().addWindowProduct(req); + assertNotNull(response); + assertTrue(response.isSuccess()); + } + + @Test + public void testGetWindowProduct() throws WxErrorException { + WindowProductRequest req = new WindowProductRequest(); + req.setProductId("123"); + req.setAppid(channelService.getConfig().getAppid()); + WxChannelBaseResponse response = channelService.getAssistantService().getWindowProduct(req); + assertNotNull(response); + assertTrue(response.isSuccess()); + } + + @Test + public void testGetWindowProductList() throws WxErrorException { + GetWindowProductListRequest req = new GetWindowProductListRequest(); + req.setAppid(channelService.getConfig().getAppid()); + WxChannelBaseResponse response = channelService.getAssistantService().getWindowProductList(req); + assertNotNull(response); + assertTrue(response.isSuccess()); + } + + @Test + public void testOffWindowProduct() throws WxErrorException { + WindowProductRequest req = new WindowProductRequest(); + req.setProductId("123"); + req.setAppid(channelService.getConfig().getAppid()); + WxChannelBaseResponse response = channelService.getAssistantService().offWindowProduct(req); + assertNotNull(response); + assertTrue(response.isSuccess()); + } +} diff --git a/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/api/impl/WxChannelAddressServiceImplTest.java b/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/api/impl/WxChannelAddressServiceImplTest.java new file mode 100644 index 0000000000..04c08bf698 --- /dev/null +++ b/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/api/impl/WxChannelAddressServiceImplTest.java @@ -0,0 +1,73 @@ +package me.chanjar.weixin.channel.api.impl; + +import static org.testng.Assert.assertNotNull; +import static org.testng.Assert.assertTrue; + +import com.google.inject.Inject; +import me.chanjar.weixin.channel.api.WxChannelAddressService; +import me.chanjar.weixin.channel.api.WxChannelService; +import me.chanjar.weixin.channel.bean.address.AddressDetail; +import me.chanjar.weixin.channel.bean.address.AddressIdResponse; +import me.chanjar.weixin.channel.bean.address.AddressInfoResponse; +import me.chanjar.weixin.channel.bean.address.AddressListResponse; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; +import me.chanjar.weixin.channel.test.ApiTestModule; +import me.chanjar.weixin.common.error.WxErrorException; +import org.testng.annotations.Guice; +import org.testng.annotations.Test; + +/** + * @author Zeyes + */ +@Guice(modules = ApiTestModule.class) +public class WxChannelAddressServiceImplTest { + + @Inject + private WxChannelService channelService; + + @Test + public void testListAddress() throws WxErrorException { + WxChannelAddressService addressService = channelService.getAddressService(); + AddressListResponse response = addressService.listAddress(0, 10); + assertNotNull(response); + assertTrue(response.isSuccess()); + } + + @Test + public void testGetAddress() throws WxErrorException { + WxChannelAddressService addressService = channelService.getAddressService(); + String addressId = ""; + AddressInfoResponse response = addressService.getAddress(addressId); + assertNotNull(response); + assertTrue(response.isSuccess()); + } + + @Test + public void testAddAddress() throws WxErrorException { + WxChannelAddressService addressService = channelService.getAddressService(); + AddressDetail addressDetail = new AddressDetail(); + // ... + AddressIdResponse response = addressService.addAddress(addressDetail); + assertNotNull(response); + assertTrue(response.isSuccess()); + } + + @Test + public void testUpdateAddress() throws WxErrorException { + WxChannelAddressService addressService = channelService.getAddressService(); + AddressDetail addressDetail = new AddressDetail(); + // ... + WxChannelBaseResponse response = addressService.updateAddress(addressDetail); + assertNotNull(response); + assertTrue(response.isSuccess()); + } + + @Test + public void testDeleteAddress() throws WxErrorException { + WxChannelAddressService addressService = channelService.getAddressService(); + String addressId = ""; + WxChannelBaseResponse response = addressService.deleteAddress(addressId); + assertNotNull(response); + assertTrue(response.isSuccess()); + } +} diff --git a/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/api/impl/WxChannelAfterSaleServiceImplTest.java b/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/api/impl/WxChannelAfterSaleServiceImplTest.java new file mode 100644 index 0000000000..81122f7a03 --- /dev/null +++ b/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/api/impl/WxChannelAfterSaleServiceImplTest.java @@ -0,0 +1,131 @@ +package me.chanjar.weixin.channel.api.impl; + +import static org.testng.Assert.assertNotNull; +import static org.testng.Assert.assertTrue; + +import com.google.inject.Inject; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.util.ArrayList; +import java.util.List; +import me.chanjar.weixin.channel.api.WxChannelAfterSaleService; +import me.chanjar.weixin.channel.api.WxChannelService; +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.base.WxChannelBaseResponse; +import me.chanjar.weixin.channel.bean.complaint.ComplaintOrderResponse; +import me.chanjar.weixin.channel.test.ApiTestModule; +import me.chanjar.weixin.common.error.WxErrorException; +import org.testng.annotations.Guice; +import org.testng.annotations.Test; + +/** + * @author Zeyes + */ +@Guice(modules = ApiTestModule.class) +public class WxChannelAfterSaleServiceImplTest { + + @Inject + private WxChannelService channelService; + + @Test + public void testListIds() throws WxErrorException { + WxChannelAfterSaleService afterSaleService = channelService.getAfterSaleService(); + Long beginCreateTime = LocalDateTime.now().minusDays(7).atZone(ZoneId.systemDefault()).toEpochSecond(); + Long endCreateTime = LocalDateTime.now().atZone(ZoneId.systemDefault()).toEpochSecond(); + String nextKey = null; + AfterSaleListResponse response = afterSaleService.listIds(beginCreateTime, endCreateTime, nextKey); + assertNotNull(response); + assertTrue(response.isSuccess()); + } + + @Test + public void testGet() throws WxErrorException { + WxChannelAfterSaleService afterSaleService = channelService.getAfterSaleService(); + String afterSaleOrderId = ""; + AfterSaleInfoResponse response = afterSaleService.get(afterSaleOrderId); + assertNotNull(response); + assertTrue(response.isSuccess()); + } + + @Test + public void testAccept() throws WxErrorException { + WxChannelAfterSaleService afterSaleService = channelService.getAfterSaleService(); + String afterSaleOrderId = ""; + String addressId = null; + WxChannelBaseResponse response = afterSaleService.accept(afterSaleOrderId, addressId, 2); + assertNotNull(response); + assertTrue(response.isSuccess()); + } + + @Test + public void testReject() throws WxErrorException { + WxChannelAfterSaleService afterSaleService = channelService.getAfterSaleService(); + String afterSaleOrderId = ""; + String rejectReason = null; + WxChannelBaseResponse response = afterSaleService.reject(afterSaleOrderId, rejectReason,1); + assertNotNull(response); + assertTrue(response.isSuccess()); + } + + @Test + public void testUploadRefundEvidence() throws WxErrorException { + WxChannelAfterSaleService afterSaleService = channelService.getAfterSaleService(); + String afterSaleOrderId = ""; + String desc = ""; + List certificates = new ArrayList<>(4); + WxChannelBaseResponse response = afterSaleService.uploadRefundEvidence(afterSaleOrderId, desc, certificates); + assertNotNull(response); + assertTrue(response.isSuccess()); + } + + @Test + public void testAddComplaintMaterial() throws WxErrorException { + WxChannelAfterSaleService afterSaleService = channelService.getAfterSaleService(); + String complaintId = ""; + String content = ""; + List mediaIds = new ArrayList<>(4); + WxChannelBaseResponse response = afterSaleService.addComplaintMaterial(complaintId, content, mediaIds); + assertNotNull(response); + assertTrue(response.isSuccess()); + } + + @Test + public void testAddComplaintEvidence() throws WxErrorException { + WxChannelAfterSaleService afterSaleService = channelService.getAfterSaleService(); + String complaintId = ""; + String content = ""; + List mediaIds = new ArrayList<>(4); + WxChannelBaseResponse response = afterSaleService.addComplaintEvidence(complaintId, content, mediaIds); + assertNotNull(response); + assertTrue(response.isSuccess()); + } + + @Test + public void testGetComplaint() throws WxErrorException { + WxChannelAfterSaleService afterSaleService = channelService.getAfterSaleService(); + String complaintId = ""; + ComplaintOrderResponse response = afterSaleService.getComplaint(complaintId); + assertNotNull(response); + assertTrue(response.isSuccess()); + } + + + @Test + public void testGetAllReason() throws WxErrorException { + WxChannelAfterSaleService afterSaleService = channelService.getAfterSaleService(); + AfterSaleReasonResponse allReason = afterSaleService.getAllReason(); + assertNotNull(allReason); + assertTrue(allReason.isSuccess()); + } + + @Test + public void testGetRejectReason() throws WxErrorException { + WxChannelAfterSaleService afterSaleService = channelService.getAfterSaleService(); + AfterSaleRejectReasonResponse rejectReason = afterSaleService.getRejectReason(); + assertNotNull(rejectReason); + assertTrue(rejectReason.isSuccess()); + } +} diff --git a/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/api/impl/WxChannelBasicServiceImplTest.java b/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/api/impl/WxChannelBasicServiceImplTest.java new file mode 100644 index 0000000000..120e11245a --- /dev/null +++ b/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/api/impl/WxChannelBasicServiceImplTest.java @@ -0,0 +1,120 @@ +package me.chanjar.weixin.channel.api.impl; + +import static org.testng.Assert.assertNotNull; +import static org.testng.Assert.assertTrue; + +import com.google.inject.Inject; +import java.io.File; +import java.io.IOException; +import java.net.URL; +import java.util.UUID; +import me.chanjar.weixin.channel.api.WxChannelBasicService; +import me.chanjar.weixin.channel.api.WxChannelService; +import me.chanjar.weixin.channel.bean.address.AddressCodeResponse; +import me.chanjar.weixin.channel.bean.image.ChannelImageInfo; +import me.chanjar.weixin.channel.bean.image.ChannelImageResponse; +import me.chanjar.weixin.channel.bean.image.QualificationFileResponse; +import me.chanjar.weixin.channel.bean.shop.ShopInfo; +import me.chanjar.weixin.channel.bean.shop.ShopInfoResponse; +import me.chanjar.weixin.channel.test.ApiTestModule; +import me.chanjar.weixin.channel.util.JsonUtils; +import me.chanjar.weixin.common.error.WxErrorException; +import org.apache.commons.io.FileUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.testng.annotations.Guice; +import org.testng.annotations.Test; + +/** + * @author Zeyes + */ +@Guice(modules = ApiTestModule.class) +public class WxChannelBasicServiceImplTest { + + private static final Logger log = LoggerFactory.getLogger(WxChannelBasicServiceImplTest.class); + + @Inject + private WxChannelService channelService; + + @Test + public void testGetAccessToken() throws WxErrorException { + String accessToken = channelService.getAccessToken(); + assertNotNull(accessToken); + log.info("accessToken: \n{}\n\n", accessToken); + System.out.println(accessToken); + } + + @Test + public void testGetShopInfo() throws WxErrorException { + WxChannelBasicService basicService = channelService.getBasicService(); + ShopInfoResponse response = basicService.getShopInfo(); + assertNotNull(response); + assertTrue(response.isSuccess()); + ShopInfo info = response.getInfo(); + assertNotNull(info); + System.out.println(JsonUtils.encode(response)); + } + + @Test + public void testUploadImg() throws WxErrorException, IOException { + String imgUrl = "https://github.githubassets.com/images/icons/emoji/octocat.png"; + WxChannelBasicService basicService = channelService.getBasicService(); + // 获取mediaId + ChannelImageInfo info = basicService.uploadImg(0, imgUrl); + assertNotNull(info); + assertNotNull(info.getMediaId()); + System.out.println(info.getMediaId()); + // 获取临时链接 + info = basicService.uploadImg(1, imgUrl); + assertNotNull(info); + assertNotNull(info.getUrl()); + System.out.println(info.getUrl()); + // 本地文件的格式上传 + String fileName = "tmp.png"; + URL url = ClassLoader.getSystemResource(fileName); + File f = File.createTempFile(UUID.randomUUID().toString(), ".png"); + FileUtils.copyURLToFile(url, f); + info = basicService.uploadImg(1, f, 253, 253); + assertNotNull(info); + assertNotNull(info.getUrl()); + System.out.println(info.getUrl()); + FileUtils.forceDeleteOnExit(f); + } + + @Test + public void testUploadQualificationFile() throws IOException, WxErrorException { + WxChannelBasicService basicService = channelService.getBasicService(); + // 本地文件的格式上传 + String fileName = "tmp.png"; + URL url = ClassLoader.getSystemResource(fileName); + File f = File.createTempFile(UUID.randomUUID().toString(), ".png"); + FileUtils.copyURLToFile(url, f); + QualificationFileResponse response = basicService.uploadQualificationFile(f); + assertNotNull(response); + assertTrue(response.isSuccess()); + assertNotNull(response.getData()); + assertNotNull(response.getData().getId()); + System.out.println(response.getData().getId()); + FileUtils.forceDeleteOnExit(f); + } + + @Test + public void testGetImg() throws WxErrorException { + WxChannelBasicService basicService = channelService.getBasicService(); + String mediaId = "ttRiex0K2utmlhR-IWcfaWP6deE5Gzo48Hq8abLEoVtTY618jAGtEmDDRPimKpTP8vlgTMwZokXHgm4fBVw82Q"; + ChannelImageResponse response = basicService.getImg(mediaId); + assertNotNull(response); + assertTrue(response.isSuccess()); + System.out.println(response.getContentType()); + } + + @Test + public void testGetAddressCode() throws WxErrorException { + WxChannelBasicService basicService = channelService.getBasicService(); + Integer rootCode = 0; + AddressCodeResponse response = basicService.getAddressCode(rootCode); + assertNotNull(response); + assertTrue(response.isSuccess()); + System.out.println(response); + } +} diff --git a/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/api/impl/WxChannelBrandServiceImplTest.java b/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/api/impl/WxChannelBrandServiceImplTest.java new file mode 100644 index 0000000000..f944a3b197 --- /dev/null +++ b/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/api/impl/WxChannelBrandServiceImplTest.java @@ -0,0 +1,108 @@ +package me.chanjar.weixin.channel.api.impl; + +import static org.testng.Assert.assertNotNull; +import static org.testng.Assert.assertTrue; + +import com.google.inject.Inject; +import java.util.List; +import me.chanjar.weixin.channel.api.WxChannelBrandService; +import me.chanjar.weixin.channel.api.WxChannelService; +import me.chanjar.weixin.channel.bean.audit.AuditApplyResponse; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; +import me.chanjar.weixin.channel.bean.brand.BasicBrand; +import me.chanjar.weixin.channel.bean.brand.Brand; +import me.chanjar.weixin.channel.bean.brand.BrandApplyListResponse; +import me.chanjar.weixin.channel.bean.brand.BrandInfoResponse; +import me.chanjar.weixin.channel.bean.brand.BrandListResponse; +import me.chanjar.weixin.channel.test.ApiTestModule; +import me.chanjar.weixin.channel.util.JsonUtils; +import me.chanjar.weixin.common.error.WxErrorException; +import org.testng.annotations.Guice; +import org.testng.annotations.Test; +import org.testng.collections.CollectionUtils; + +/** + * @author Zeyes + */ +@Guice(modules = ApiTestModule.class) +public class WxChannelBrandServiceImplTest { + + @Inject + private WxChannelService channelService; + + @Test + public void testListAllBrand() throws WxErrorException { + WxChannelBrandService brandService = channelService.getBrandService(); + + BrandListResponse response = brandService.listAllBrand(100, null); + assertNotNull(response); + assertTrue(response.isSuccess()); + List brands = response.getBrands(); + assertTrue(CollectionUtils.hasElements(brands)); + System.out.println(JsonUtils.encode(response)); + } + + @Test + public void testAddBrandApply() throws WxErrorException { + WxChannelBrandService brandService = channelService.getBrandService(); + Brand brand = new Brand(); + // ... + AuditApplyResponse response = brandService.addBrandApply(brand); + assertNotNull(response); + assertTrue(response.isSuccess()); + } + + @Test + public void testUpdateBrandApply() throws WxErrorException { + WxChannelBrandService brandService = channelService.getBrandService(); + Brand brand = new Brand(); + // ... + AuditApplyResponse response = brandService.updateBrandApply(brand); + assertNotNull(response); + assertTrue(response.isSuccess()); + } + + @Test + public void testCancelBrandApply() throws WxErrorException { + WxChannelBrandService brandService = channelService.getBrandService(); + String brandId = "10000000"; + String auditId = "12345566"; + WxChannelBaseResponse response = brandService.cancelBrandApply(brandId, auditId); + assertNotNull(response); + assertTrue(response.isSuccess()); + } + + @Test + public void testDeleteBrandApply() throws WxErrorException { + WxChannelBrandService brandService = channelService.getBrandService(); + String brandId = "10000000"; + WxChannelBaseResponse response = brandService.deleteBrandApply(brandId); + assertNotNull(response); + assertTrue(response.isSuccess()); + } + + @Test + public void testGetBrandApply() throws WxErrorException { + WxChannelBrandService brandService = channelService.getBrandService(); + String brandId = "10000000"; + BrandInfoResponse response = brandService.getBrandApply(brandId); + assertNotNull(response); + assertTrue(response.isSuccess()); + } + + @Test + public void testListBrandApply() throws WxErrorException { + WxChannelBrandService brandService = channelService.getBrandService(); + BrandApplyListResponse response = brandService.listBrandApply(10, null, null); + assertNotNull(response); + assertTrue(response.isSuccess()); + } + + @Test + public void testListValidBrandApply() throws WxErrorException { + WxChannelBrandService brandService = channelService.getBrandService(); + BrandApplyListResponse response = brandService.listValidBrandApply(10, null); + assertNotNull(response); + assertTrue(response.isSuccess()); + } +} diff --git a/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/api/impl/WxChannelCategoryServiceImplTest.java b/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/api/impl/WxChannelCategoryServiceImplTest.java new file mode 100644 index 0000000000..06afde2993 --- /dev/null +++ b/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/api/impl/WxChannelCategoryServiceImplTest.java @@ -0,0 +1,171 @@ +package me.chanjar.weixin.channel.api.impl; + +import static org.testng.Assert.assertNotNull; +import static org.testng.Assert.assertTrue; + +import com.google.inject.Inject; +import java.util.List; +import lombok.extern.slf4j.Slf4j; +import me.chanjar.weixin.channel.api.WxChannelCategoryService; +import me.chanjar.weixin.channel.api.WxChannelService; +import me.chanjar.weixin.channel.bean.audit.AuditApplyResponse; +import me.chanjar.weixin.channel.bean.audit.AuditResponse; +import me.chanjar.weixin.channel.bean.audit.CategoryAuditInfo; +import me.chanjar.weixin.channel.bean.audit.CategoryAuditRequest; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; +import me.chanjar.weixin.channel.bean.category.CategoryAndQualificationList; +import me.chanjar.weixin.channel.bean.category.CategoryDetailResult; +import me.chanjar.weixin.channel.bean.category.CategoryQualification; +import me.chanjar.weixin.channel.bean.category.CategoryQualificationResponse; +import me.chanjar.weixin.channel.bean.category.PassCategoryResponse; +import me.chanjar.weixin.channel.bean.category.ShopCategory; +import me.chanjar.weixin.channel.bean.category.ShopCategoryResponse; +import me.chanjar.weixin.channel.test.ApiTestModule; +import me.chanjar.weixin.channel.util.JsonUtils; +import me.chanjar.weixin.common.error.WxErrorException; +import org.testng.annotations.Guice; +import org.testng.annotations.Test; +import org.testng.collections.CollectionUtils; + +/** + * @author Zeyes + */ +@Slf4j +@Guice(modules = ApiTestModule.class) +public class WxChannelCategoryServiceImplTest { + + @Inject + private WxChannelService channelService; + + @Test + public void testListAllCategory() throws WxErrorException { + WxChannelCategoryService categoryService = channelService.getCategoryService(); + CategoryQualificationResponse response = categoryService.listAllCategory(); + assertNotNull(response); + assertTrue(response.isSuccess()); + //System.out.println(response); + // 测试分类:7231 瑜伽服上衣 + for (CategoryAndQualificationList cat : response.getCatsV2()) { + if (cat.getList() == null) { + continue; + } + for (CategoryQualification qua : cat.getList()) { + if (qua.getCategory() == null) { + log.error("category is null"); + } + if ("7231".equals(qua.getCategory().getId())) { + log.info("qua: {}", JsonUtils.encode(qua)); + } + } + } + } + + @Test + public void testListAvailableCategories() throws WxErrorException { + WxChannelCategoryService categoryService = channelService.getCategoryService(); + ShopCategoryResponse response = categoryService.listAvailableCategories("0"); + assertNotNull(response); + assertTrue(response.isSuccess()); + List v1 = response.getCategories(); + List v2 = response.getCatListV2(); + assertTrue(CollectionUtils.hasElements(v1) || CollectionUtils.hasElements(v2)); + v1.forEach(System.out::println); + v2.forEach(System.out::println); + } + + @Test + public void testGetCategoryDetail() throws WxErrorException { + WxChannelCategoryService categoryService = channelService.getCategoryService(); + CategoryDetailResult categoryDetail = categoryService.getCategoryDetail("7231"); + assertNotNull(categoryDetail); + assertTrue(categoryDetail.isSuccess()); + System.out.println(categoryDetail); + } + + @Test + public void testAddCategory() throws WxErrorException { +// WxChannelCategoryService categoryService = channelService.getCategoryService(); +// List certificates = new ArrayList<>(); +// certificates.add( +// "hWNith8iZSrxfN7W2tXOoWSXYWi1qADRJxwImvQl2DC6wqqJrl4g8i/UEZfd59yiiEKAnqy0WETFrOcGZFcJDfpH2ccmNZddzesR1/OpAC7bbfmEiDFBK2QL7MBjhR2m"); +// AuditApplyResponse response = categoryService.addCategory("1001", "1002", "1005", certificates); +// assertNotNull(response); +// assertTrue(response.isSuccess()); +// System.out.println(response); + String json = "{\n" + + " \"category_info\": {\n" + + " \"cats_v2\":[\n" + + " {\n" + + " \"cat_id\": 6033\n" + + " },\n" + + " {\n" + + " \"cat_id\": 6057\n" + + " },\n" + + " {\n" + + " \"cat_id\": 6091\n" + + " },\n" + + " {\n" + + " \"cat_id\": 6093\n" + + " }\n" + + " ],\n" + + " \"certificate\": [\n" + + " \"THE_FILE_ID_1\",\n" + + " \"THE_FILE_ID_2\"\n" + + " ],\n" + + " \"brand_list\" : [\n" + + " { \"brand_id\": 1001 },\n" + + " { \"brand_id\": 1002 }\n" + + " ]\n" + + " }\n" + + "}"; + CategoryAuditRequest param = JsonUtils.decode(json, CategoryAuditRequest.class); + CategoryAuditInfo info = null; + if (info != null) { + info = param.getCategoryInfo(); + } + WxChannelCategoryService categoryService = channelService.getCategoryService(); + AuditApplyResponse response = categoryService.addCategory(info); + assertNotNull(response); + assertTrue(response.isSuccess()); + System.out.println(response); + } + + @Test + public void testCancelCategoryAudit() throws WxErrorException { + WxChannelCategoryService categoryService = channelService.getCategoryService(); + String auditId = "4403159413"; + WxChannelBaseResponse response = categoryService.cancelCategoryAudit(auditId); + assertNotNull(response); + assertTrue(response.isSuccess()); + System.out.println(response); + } + + @Test + public void testGetAudit() throws WxErrorException { + WxChannelCategoryService categoryService = channelService.getCategoryService(); + String auditId = "4403159413"; + AuditResponse response = categoryService.getAudit(auditId); + assertNotNull(response); + assertTrue(response.isSuccess()); + System.out.println(response); + } + + @Test + public void testListPassCategory() throws WxErrorException { + WxChannelCategoryService categoryService = channelService.getCategoryService(); + PassCategoryResponse response = categoryService.listPassCategory(); + assertNotNull(response); + assertTrue(response.isSuccess()); + System.out.println(response); + } + + @Test + public void testListRelationCategory() throws WxErrorException { + WxChannelCategoryService categoryService = channelService.getCategoryService(); + me.chanjar.weixin.channel.bean.category.RelationCategoryResponse response = + categoryService.listRelationCategory(true, 1); + assertNotNull(response); + assertTrue(response.isSuccess()); + System.out.println(response); + } +} diff --git a/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/api/impl/WxChannelCompassFinderServiceImplTest.java b/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/api/impl/WxChannelCompassFinderServiceImplTest.java new file mode 100644 index 0000000000..be178ba947 --- /dev/null +++ b/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/api/impl/WxChannelCompassFinderServiceImplTest.java @@ -0,0 +1,66 @@ +package me.chanjar.weixin.channel.api.impl; + +import com.google.inject.Inject; +import me.chanjar.weixin.channel.api.WxChannelCompassFinderService; +import me.chanjar.weixin.channel.api.WxChannelService; +import me.chanjar.weixin.channel.bean.compass.finder.OverallResponse; +import me.chanjar.weixin.channel.bean.compass.finder.ProductDataResponse; +import me.chanjar.weixin.channel.bean.compass.finder.ProductListResponse; +import me.chanjar.weixin.channel.bean.compass.finder.SaleProfileDataResponse; +import me.chanjar.weixin.channel.enums.SaleProfileUserType; +import me.chanjar.weixin.channel.test.ApiTestModule; +import me.chanjar.weixin.common.error.WxErrorException; +import org.testng.annotations.Guice; +import org.testng.annotations.Test; + +import static org.testng.Assert.assertNotNull; +import static org.testng.Assert.assertTrue; + +/** + * @author Winnie + */ +@Guice(modules = ApiTestModule.class) +public class WxChannelCompassFinderServiceImplTest { + + @Inject + private WxChannelService channelService; + + @Test + public void testGetOverAll() throws WxErrorException { + WxChannelCompassFinderService compassFinderService = channelService.getCompassFinderService(); + String ds = "20240630"; + OverallResponse response = compassFinderService.getOverall(ds); + assertNotNull(response); + assertTrue(response.isSuccess()); + } + + @Test + public void testGetProductData() throws WxErrorException { + WxChannelCompassFinderService compassFinderService = channelService.getCompassFinderService(); + String ds = "20240630"; + String productId = "10000017457793"; + ProductDataResponse response = compassFinderService.getProductData(ds, productId); + assertNotNull(response); + assertTrue(response.isSuccess()); + } + + @Test + public void testGetProductList() throws WxErrorException { + WxChannelCompassFinderService compassFinderService = channelService.getCompassFinderService(); + String ds = "20240630"; + ProductListResponse response = compassFinderService.getProductList(ds); + assertNotNull(response); + assertTrue(response.isSuccess()); + } + + @Test + public void testGetSaleProfileData() throws WxErrorException { + WxChannelCompassFinderService compassFinderService = channelService.getCompassFinderService(); + String ds = "20240630"; + Integer type = SaleProfileUserType.PRODUCT_IMPRESSION_USER.getKey(); + SaleProfileDataResponse response = compassFinderService.getSaleProfileData(ds, type); + assertNotNull(response); + assertTrue(response.isSuccess()); + } + +} diff --git a/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/api/impl/WxChannelCompassShopServiceImplTest.java b/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/api/impl/WxChannelCompassShopServiceImplTest.java new file mode 100644 index 0000000000..cae4d23067 --- /dev/null +++ b/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/api/impl/WxChannelCompassShopServiceImplTest.java @@ -0,0 +1,125 @@ +package me.chanjar.weixin.channel.api.impl; + +import static org.testng.Assert.assertNotNull; +import static org.testng.Assert.assertTrue; + +import com.google.inject.Inject; +import me.chanjar.weixin.channel.api.WxChannelCompassShopService; +import me.chanjar.weixin.channel.api.WxChannelService; +import me.chanjar.weixin.channel.bean.compass.shop.FinderAuthListResponse; +import me.chanjar.weixin.channel.bean.compass.shop.FinderListResponse; +import me.chanjar.weixin.channel.bean.compass.shop.FinderOverallResponse; +import me.chanjar.weixin.channel.bean.compass.shop.FinderProductListResponse; +import me.chanjar.weixin.channel.bean.compass.shop.FinderProductOverallResponse; +import me.chanjar.weixin.channel.bean.compass.shop.ShopLiveListResponse; +import me.chanjar.weixin.channel.bean.compass.shop.ShopOverallResponse; +import me.chanjar.weixin.channel.bean.compass.shop.ShopProductDataResponse; +import me.chanjar.weixin.channel.bean.compass.shop.ShopProductListResponse; +import me.chanjar.weixin.channel.bean.compass.shop.ShopSaleProfileDataResponse; +import me.chanjar.weixin.channel.test.ApiTestModule; +import me.chanjar.weixin.common.error.WxErrorException; +import org.testng.annotations.Guice; +import org.testng.annotations.Test; + +/** + * @author Zeyes + */ +@Guice(modules = ApiTestModule.class) +public class WxChannelCompassShopServiceImplTest { + + @Inject + private WxChannelService channelService; + + @Test + public void testGetShopOverall() throws WxErrorException { + WxChannelCompassShopService service = channelService.getCompassShopService(); + String ds = "20240306"; + ShopOverallResponse response = service.getShopOverall(ds); + assertNotNull(response); + assertTrue(response.isSuccess()); + } + + @Test + public void testGetFinderAuthorizationList() throws WxErrorException { + WxChannelCompassShopService service = channelService.getCompassShopService(); + FinderAuthListResponse response = service.getFinderAuthorizationList(); + assertNotNull(response); + assertTrue(response.isSuccess()); + } + + @Test + public void testGetFinderList() throws WxErrorException { + WxChannelCompassShopService service = channelService.getCompassShopService(); + String ds = "20240306"; + FinderListResponse response = service.getFinderList(ds); + assertNotNull(response); + assertTrue(response.isSuccess()); + } + + @Test + public void testGetFinderOverall() throws WxErrorException { + WxChannelCompassShopService service = channelService.getCompassShopService(); + String ds = "20240306"; + FinderOverallResponse response = service.getFinderOverall(ds); + assertNotNull(response); + assertTrue(response.isSuccess()); + } + + @Test + public void testGetFinderProductList() throws WxErrorException { + WxChannelCompassShopService service = channelService.getCompassShopService(); + String ds = ""; + String finderId = ""; + FinderProductListResponse response = service.getFinderProductList(ds, finderId); + assertNotNull(response); + assertTrue(response.isSuccess()); + } + + @Test + public void testGetFinderProductOverall() throws WxErrorException { + WxChannelCompassShopService service = channelService.getCompassShopService(); + String ds = ""; + String finderId = ""; + FinderProductOverallResponse response = service.getFinderProductOverall(ds, finderId); + assertNotNull(response); + assertTrue(response.isSuccess()); + } + + @Test + public void testGetShopLiveList() throws WxErrorException { + WxChannelCompassShopService service = channelService.getCompassShopService(); + String ds = ""; + String finderId = ""; + ShopLiveListResponse response = service.getShopLiveList(ds, finderId); + assertNotNull(response); + assertTrue(response.isSuccess()); + } + + @Test + public void testGetShopProductData() throws WxErrorException { + WxChannelCompassShopService service = channelService.getCompassShopService(); + String ds = "20240306"; + String productId = ""; + ShopProductDataResponse response = service.getShopProductData(ds, productId); + assertNotNull(response); + assertTrue(response.isSuccess()); + } + + @Test + public void testGetShopProductList() throws WxErrorException { + WxChannelCompassShopService service = channelService.getCompassShopService(); + String ds = "20240306"; + ShopProductListResponse response = service.getShopProductList(ds); + assertNotNull(response); + assertTrue(response.isSuccess()); + } + + @Test + public void testGetShopSaleProfileData() throws WxErrorException { + WxChannelCompassShopService service = channelService.getCompassShopService(); + String ds = "20240306"; + ShopSaleProfileDataResponse response = service.getShopSaleProfileData(ds, 3); + assertNotNull(response); + assertTrue(response.isSuccess()); + } +} diff --git a/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/api/impl/WxChannelCouponServiceImplTest.java b/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/api/impl/WxChannelCouponServiceImplTest.java new file mode 100644 index 0000000000..2885e2e007 --- /dev/null +++ b/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/api/impl/WxChannelCouponServiceImplTest.java @@ -0,0 +1,97 @@ +package me.chanjar.weixin.channel.api.impl; + +import static org.testng.Assert.*; + +import com.google.inject.Inject; +import me.chanjar.weixin.channel.api.WxChannelCouponService; +import me.chanjar.weixin.channel.api.WxChannelService; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; +import me.chanjar.weixin.channel.bean.coupon.CouponIdResponse; +import me.chanjar.weixin.channel.bean.coupon.CouponInfoResponse; +import me.chanjar.weixin.channel.bean.coupon.CouponListParam; +import me.chanjar.weixin.channel.bean.coupon.CouponListResponse; +import me.chanjar.weixin.channel.bean.coupon.CouponParam; +import me.chanjar.weixin.channel.bean.coupon.UserCouponListParam; +import me.chanjar.weixin.channel.bean.coupon.UserCouponListResponse; +import me.chanjar.weixin.channel.bean.coupon.UserCouponResponse; +import me.chanjar.weixin.channel.test.ApiTestModule; +import me.chanjar.weixin.common.error.WxErrorException; +import org.testng.annotations.Guice; +import org.testng.annotations.Test; + +/** + * @author Zeyes + */ +@Guice(modules = ApiTestModule.class) +public class WxChannelCouponServiceImplTest { + + @Inject + private WxChannelService channelService; + + @Test + public void testCreateCoupon() throws WxErrorException { + WxChannelCouponService couponService = channelService.getCouponService(); + CouponParam param = new CouponParam(); + // ... + CouponIdResponse response = couponService.createCoupon(param); + assertNotNull(response); + assertTrue(response.isSuccess()); + } + + @Test + public void testUpdateCoupon() throws WxErrorException { + WxChannelCouponService couponService = channelService.getCouponService(); + CouponParam param = new CouponParam(); + // ... + CouponIdResponse response = couponService.updateCoupon(param); + assertNotNull(response); + assertTrue(response.isSuccess()); + } + + @Test + public void testUpdateCouponStatus() throws WxErrorException { + WxChannelCouponService couponService = channelService.getCouponService(); + String couponId = ""; + Integer status = 2; + WxChannelBaseResponse response = couponService.updateCouponStatus(couponId, status); + assertNotNull(response); + assertTrue(response.isSuccess()); + } + + @Test + public void testGetCoupon() throws WxErrorException { + WxChannelCouponService couponService = channelService.getCouponService(); + String couponId = ""; + CouponInfoResponse response = couponService.getCoupon(couponId); + assertNotNull(response); + assertTrue(response.isSuccess()); + } + + @Test + public void testGetCouponList() throws WxErrorException { + WxChannelCouponService couponService = channelService.getCouponService(); + CouponListParam param = new UserCouponListParam(); + CouponListResponse response = couponService.getCouponList(param); + assertNotNull(response); + assertTrue(response.isSuccess()); + } + + @Test + public void testGetUserCoupon() throws WxErrorException { + WxChannelCouponService couponService = channelService.getCouponService(); + String couponId = ""; + String userCouponId = ""; + UserCouponResponse response = couponService.getUserCoupon(couponId, userCouponId); + assertNotNull(response); + assertTrue(response.isSuccess()); + } + + @Test + public void testGetUserCouponList() throws WxErrorException { + WxChannelCouponService couponService = channelService.getCouponService(); + UserCouponListParam param = new UserCouponListParam(); + UserCouponListResponse response = couponService.getUserCouponList(param); + assertNotNull(response); + assertTrue(response.isSuccess()); + } +} diff --git a/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/api/impl/WxChannelFreightTemplateServiceImplTest.java b/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/api/impl/WxChannelFreightTemplateServiceImplTest.java new file mode 100644 index 0000000000..a936a9299e --- /dev/null +++ b/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/api/impl/WxChannelFreightTemplateServiceImplTest.java @@ -0,0 +1,64 @@ +package me.chanjar.weixin.channel.api.impl; + +import static org.testng.Assert.*; + +import com.google.inject.Inject; +import me.chanjar.weixin.channel.api.WxChannelFreightTemplateService; +import me.chanjar.weixin.channel.api.WxChannelService; +import me.chanjar.weixin.channel.bean.freight.FreightTemplate; +import me.chanjar.weixin.channel.bean.freight.TemplateIdResponse; +import me.chanjar.weixin.channel.bean.freight.TemplateInfoResponse; +import me.chanjar.weixin.channel.bean.freight.TemplateListResponse; +import me.chanjar.weixin.channel.test.ApiTestModule; +import me.chanjar.weixin.common.error.WxErrorException; +import org.testng.annotations.Guice; +import org.testng.annotations.Test; + +/** + * @author Zeyes + */ +@Guice(modules = ApiTestModule.class) +public class WxChannelFreightTemplateServiceImplTest { + + @Inject + private WxChannelService channelService; + + @Test + public void testListTemplate() throws WxErrorException { + WxChannelFreightTemplateService freightTemplateService = channelService.getFreightTemplateService(); + Integer offset = 0; + Integer limit = 20; + TemplateListResponse response = freightTemplateService.listTemplate(offset, limit); + assertNotNull(response); + assertTrue(response.isSuccess()); + } + + @Test + public void testGetTemplate() throws WxErrorException { + WxChannelFreightTemplateService freightTemplateService = channelService.getFreightTemplateService(); + String templateId = ""; + TemplateInfoResponse response = freightTemplateService.getTemplate(templateId); + assertNotNull(response); + assertTrue(response.isSuccess()); + } + + @Test + public void testAddTemplate() throws WxErrorException { + WxChannelFreightTemplateService freightTemplateService = channelService.getFreightTemplateService(); + FreightTemplate template = new FreightTemplate(); + // ... + TemplateIdResponse response = freightTemplateService.addTemplate(template); + assertNotNull(response); + assertTrue(response.isSuccess()); + } + + @Test + public void testUpdateTemplate() throws WxErrorException { + WxChannelFreightTemplateService freightTemplateService = channelService.getFreightTemplateService(); + FreightTemplate template = new FreightTemplate(); + // ... + TemplateIdResponse response = freightTemplateService.updateTemplate(template); + assertNotNull(response); + assertTrue(response.isSuccess()); + } +} diff --git a/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/api/impl/WxChannelFundServiceImplTest.java b/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/api/impl/WxChannelFundServiceImplTest.java new file mode 100644 index 0000000000..5bf86e3e25 --- /dev/null +++ b/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/api/impl/WxChannelFundServiceImplTest.java @@ -0,0 +1,184 @@ +package me.chanjar.weixin.channel.api.impl; + +import static org.testng.Assert.*; + +import com.google.inject.Inject; +import java.time.LocalDateTime; +import java.time.ZoneId; +import me.chanjar.weixin.channel.api.WxChannelFundService; +import me.chanjar.weixin.channel.api.WxChannelService; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; +import me.chanjar.weixin.channel.bean.fund.AccountInfo; +import me.chanjar.weixin.channel.bean.fund.AccountInfoResponse; +import me.chanjar.weixin.channel.bean.fund.BalanceInfoResponse; +import me.chanjar.weixin.channel.bean.fund.FlowListResponse; +import me.chanjar.weixin.channel.bean.fund.FundsFlowResponse; +import me.chanjar.weixin.channel.bean.fund.FundsListParam; +import me.chanjar.weixin.channel.bean.fund.WithdrawDetailResponse; +import me.chanjar.weixin.channel.bean.fund.WithdrawListResponse; +import me.chanjar.weixin.channel.bean.fund.WithdrawSubmitResponse; +import me.chanjar.weixin.channel.bean.fund.bank.BankCityResponse; +import me.chanjar.weixin.channel.bean.fund.bank.BankInfoResponse; +import me.chanjar.weixin.channel.bean.fund.bank.BankListResponse; +import me.chanjar.weixin.channel.bean.fund.bank.BankProvinceResponse; +import me.chanjar.weixin.channel.bean.fund.bank.BranchInfoResponse; +import me.chanjar.weixin.channel.bean.fund.qrcode.QrCheckResponse; +import me.chanjar.weixin.channel.bean.fund.qrcode.QrCodeResponse; +import me.chanjar.weixin.channel.test.ApiTestModule; +import me.chanjar.weixin.common.error.WxErrorException; +import org.testng.annotations.Guice; +import org.testng.annotations.Test; + +/** + * @author Zeyes + */ +@Guice(modules = ApiTestModule.class) +public class WxChannelFundServiceImplTest { + + @Inject + private WxChannelService channelService; + + @Test + public void testGetBalance() throws WxErrorException { + WxChannelFundService fundService = channelService.getFundService(); + BalanceInfoResponse response = fundService.getBalance(); + assertNotNull(response); + assertTrue(response.isSuccess()); + } + + @Test + public void testGetBankAccount() throws WxErrorException { + WxChannelFundService fundService = channelService.getFundService(); + AccountInfoResponse response = fundService.getBankAccount(); + assertNotNull(response); + assertTrue(response.isSuccess()); + } + + @Test + public void testGetFundsFlowDetail() throws WxErrorException { + WxChannelFundService fundService = channelService.getFundService(); + String flowId = ""; + FundsFlowResponse response = fundService.getFundsFlowDetail(flowId); + assertNotNull(response); + assertTrue(response.isSuccess()); + } + + @Test + public void testListFundsFlow() throws WxErrorException { + WxChannelFundService fundService = channelService.getFundService(); + FundsListParam param = new FundsListParam(); + FlowListResponse response = fundService.listFundsFlow(param); + assertNotNull(response); + assertTrue(response.isSuccess()); + } + + @Test + public void testGetWithdrawDetail() throws WxErrorException { + WxChannelFundService fundService = channelService.getFundService(); + String withdrawId = ""; + WithdrawDetailResponse response = fundService.getWithdrawDetail(withdrawId); + assertNotNull(response); + assertTrue(response.isSuccess()); + } + + @Test + public void testListWithdraw() throws WxErrorException { + WxChannelFundService fundService = channelService.getFundService(); + Integer pageNum = 1; + Integer pageSize = 10; + Long startTime = LocalDateTime.now().minusDays(7).atZone(ZoneId.systemDefault()).toEpochSecond(); + Long endTime = LocalDateTime.now().atZone(ZoneId.systemDefault()).toEpochSecond(); + WithdrawListResponse response = fundService.listWithdraw(pageNum, pageSize, startTime, endTime); + assertNotNull(response); + assertTrue(response.isSuccess()); + } + + @Test + public void testSetBankAccount() throws WxErrorException { + WxChannelFundService fundService = channelService.getFundService(); + AccountInfo accountInfo = new AccountInfo(); + // ... + WxChannelBaseResponse response = fundService.setBankAccount(accountInfo); + assertNotNull(response); + assertTrue(response.isSuccess()); + } + + @Test + public void testSubmitWithdraw() throws WxErrorException { + WxChannelFundService fundService = channelService.getFundService(); + Integer amount = 1; + String remark = "test"; + String bankMemo = "-"; + WithdrawSubmitResponse response = fundService.submitWithdraw(amount, remark, bankMemo); + assertNotNull(response); + assertTrue(response.isSuccess()); + } + + @Test + public void testGetBankInfoByCardNo() throws WxErrorException { + WxChannelFundService fundService = channelService.getFundService(); + String accountNumber = ""; + BankInfoResponse response = fundService.getBankInfoByCardNo(accountNumber); + assertNotNull(response); + assertTrue(response.isSuccess()); + } + + @Test + public void testSearchBankList() throws WxErrorException { + WxChannelFundService fundService = channelService.getFundService(); + Integer offset = 0; + Integer limit = 20; + String keywords = ""; + Integer bankType = 1; + BankListResponse response = fundService.searchBankList(offset, limit, keywords, bankType); + assertNotNull(response); + assertTrue(response.isSuccess()); + } + + @Test + public void testSearchCityList() throws WxErrorException { + WxChannelFundService fundService = channelService.getFundService(); + String provinceCode = "0"; + BankCityResponse response = fundService.searchCityList(provinceCode); + assertNotNull(response); + assertTrue(response.isSuccess()); + } + + @Test + public void testGetProvinceList() throws WxErrorException { + WxChannelFundService fundService = channelService.getFundService(); + BankProvinceResponse response = fundService.getProvinceList(); + assertNotNull(response); + assertTrue(response.isSuccess()); + } + + @Test + public void testSearchBranchList() throws WxErrorException { + WxChannelFundService fundService = channelService.getFundService(); + String bankCode = ""; + String cityCode = ""; + Integer offset = 0; + Integer limit = 20; + BranchInfoResponse response = fundService.searchBranchList(bankCode, cityCode, offset, limit); + assertNotNull(response); + assertTrue(response.isSuccess()); + } + + @Test + public void testGetQrCode() throws WxErrorException { + WxChannelFundService fundService = channelService.getFundService(); + String qrcodeTicket = ""; + QrCodeResponse response = fundService.getQrCode(qrcodeTicket); + assertNotNull(response); + assertTrue(response.isSuccess()); + } + + @Test + public void testCheckQrStatus() throws WxErrorException { + WxChannelFundService fundService = channelService.getFundService(); + String qrcodeTicket = ""; + QrCheckResponse response = fundService.checkQrStatus(qrcodeTicket); + assertNotNull(response); + assertTrue(response.isSuccess()); + } +} diff --git a/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/api/impl/WxChannelLiveDashboardServiceImplTest.java b/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/api/impl/WxChannelLiveDashboardServiceImplTest.java new file mode 100644 index 0000000000..ec2e161da1 --- /dev/null +++ b/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/api/impl/WxChannelLiveDashboardServiceImplTest.java @@ -0,0 +1,44 @@ +package me.chanjar.weixin.channel.api.impl; + +import com.google.inject.Inject; +import me.chanjar.weixin.channel.api.WxChannelLiveDashboardService; +import me.chanjar.weixin.channel.api.WxChannelService; +import me.chanjar.weixin.channel.bean.live.dashboard.LiveDataResponse; +import me.chanjar.weixin.channel.bean.live.dashboard.LiveListResponse; +import me.chanjar.weixin.channel.test.ApiTestModule; +import me.chanjar.weixin.common.error.WxErrorException; +import org.testng.annotations.Guice; +import org.testng.annotations.Test; + +import static org.testng.Assert.assertNotNull; +import static org.testng.Assert.assertTrue; + +/** + * @author Winnie + */ +@Guice(modules = ApiTestModule.class) +public class WxChannelLiveDashboardServiceImplTest { + + @Inject + private WxChannelService channelService; + + @Test + public void testGetLiveList() throws WxErrorException { + WxChannelLiveDashboardService liveDashboardService = channelService.getLiveDashboardService(); + // yyyyMMdd + Long ds = 20240630L; + LiveListResponse response = liveDashboardService.getLiveList(ds); + assertNotNull(response); + assertTrue(response.isSuccess()); + } + + @Test + public void testGetLiveData() throws WxErrorException { + WxChannelLiveDashboardService liveDashboardService = channelService.getLiveDashboardService(); + String exportId = "export/UzFf*****************************************************************************************64V"; + LiveDataResponse response = liveDashboardService.getLiveData(exportId); + assertNotNull(response); + assertTrue(response.isSuccess()); + } + +} 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 new file mode 100644 index 0000000000..2c70c7bde8 --- /dev/null +++ b/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/api/impl/WxChannelOrderServiceImplTest.java @@ -0,0 +1,201 @@ +package me.chanjar.weixin.channel.api.impl; + +import static org.testng.Assert.assertNotNull; +import static org.testng.Assert.assertTrue; + +import com.google.inject.Inject; +import java.util.ArrayList; +import java.util.List; +import me.chanjar.weixin.channel.api.WxChannelOrderService; +import me.chanjar.weixin.channel.api.WxChannelService; +import me.chanjar.weixin.channel.bean.base.AddressInfo; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; +import me.chanjar.weixin.channel.bean.delivery.DeliveryCompanyResponse; +import me.chanjar.weixin.channel.bean.delivery.DeliveryInfo; +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.OrderAddressInfo; +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; +import me.chanjar.weixin.common.error.WxErrorException; +import org.testng.annotations.Guice; +import org.testng.annotations.Test; + +/** + * @author Zeyes + */ +@Guice(modules = ApiTestModule.class) +public class WxChannelOrderServiceImplTest { + + @Inject + private WxChannelService channelService; + + @Test + public void testGetOrder() throws WxErrorException { + WxChannelOrderService orderService = channelService.getOrderService(); + String orderId = ""; + OrderInfoResponse response = orderService.getOrder(orderId); + assertNotNull(response); + 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(); + OrderListParam param = new OrderListParam(); + OrderListResponse response = orderService.getOrders(param); + assertNotNull(response); + assertTrue(response.isSuccess()); + } + + @Test + 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()); + } + + @Test + public void testUpdatePrice() throws WxErrorException { + WxChannelOrderService orderService = channelService.getOrderService(); + String orderId = ""; + Integer expressFee = 0; + List changeOrderInfos = new ArrayList<>(4); + ChangeOrderInfo changeOrderInfo = new ChangeOrderInfo(); + changeOrderInfos.add(changeOrderInfo); + WxChannelBaseResponse response = orderService.updatePrice(orderId, expressFee, changeOrderInfos); + assertNotNull(response); + assertTrue(response.isSuccess()); + } + + @Test + public void testUpdateRemark() throws WxErrorException { + WxChannelOrderService orderService = channelService.getOrderService(); + String orderId = ""; + String merchantNotes = ""; + WxChannelBaseResponse response = orderService.updateRemark(orderId, merchantNotes); + assertNotNull(response); + assertTrue(response.isSuccess()); + } + + @Test + public void testUpdateAddress() throws WxErrorException { + WxChannelOrderService orderService = channelService.getOrderService(); + String orderId = ""; + AddressInfo addressInfo = new OrderAddressInfo(); + WxChannelBaseResponse response = orderService.updateAddress(orderId, addressInfo); + assertNotNull(response); + assertTrue(response.isSuccess()); + } + + @Test + public void testUpdateDelivery() throws WxErrorException { + WxChannelOrderService orderService = channelService.getOrderService(); + DeliveryUpdateParam param = new DeliveryUpdateParam(); + WxChannelBaseResponse response = orderService.updateDelivery(param); + assertNotNull(response); + assertTrue(response.isSuccess()); + } + + @Test + public void testAcceptAddressModify() throws WxErrorException { + WxChannelOrderService orderService = channelService.getOrderService(); + String orderId = ""; + WxChannelBaseResponse response = orderService.acceptAddressModify(orderId); + assertNotNull(response); + assertTrue(response.isSuccess()); + } + + @Test + public void testRejectAddressModify() throws WxErrorException { + WxChannelOrderService orderService = channelService.getOrderService(); + String orderId = ""; + WxChannelBaseResponse response = orderService.rejectAddressModify(orderId); + assertNotNull(response); + assertTrue(response.isSuccess()); + } + + @Test + public void testCloseOrder() { + WxChannelOrderService orderService = channelService.getOrderService(); + String orderId = ""; + WxChannelBaseResponse response = orderService.closeOrder(orderId); + assertNotNull(response); + //assertTrue(response.isSuccess()); + } + + @Test + public void testListDeliveryCompany() throws WxErrorException { + WxChannelOrderService orderService = channelService.getOrderService(); + DeliveryCompanyResponse response = orderService.listDeliveryCompany(false); + assertNotNull(response); + assertTrue(response.isSuccess()); + } + + @Test + public void testDeliveryOrder() throws WxErrorException { + WxChannelOrderService orderService = channelService.getOrderService(); + String orderId = ""; + List deliveryList = new ArrayList<>(4); + DeliveryInfo deliveryInfo = new DeliveryInfo(); + deliveryList.add(deliveryInfo); + WxChannelBaseResponse response = orderService.deliveryOrder(orderId, deliveryList); + assertNotNull(response); + assertTrue(response.isSuccess()); + } + + @Test + public void testUploadFreshInspect() throws WxErrorException { + WxChannelOrderService orderService = channelService.getOrderService(); + String orderId = "123"; + List items = new ArrayList<>(); + items.add(new PackageAuditInfo("product_express_pic_url", "https://store.mp.video.tencent-cloud.com/x")); + items.add(new PackageAuditInfo("product_packaging_box_panoramic_video_url", "https://store.mp.video.tencent-cloud.com/y")); + items.add(new PackageAuditInfo("product_unboxing_panoramic_video_url", "https://store.mp.video.tencent-cloud.com/z")); + items.add(new PackageAuditInfo("single_product_detail_panoramic_video_url", "https://store.mp.video.tencent-cloud.com/a")); + WxChannelBaseResponse response = orderService.uploadFreshInspect(orderId, items); + assertNotNull(response); + assertTrue(response.isSuccess()); + } + + @Test + public void testGetVirtualTelNumber() throws WxErrorException { + WxChannelOrderService orderService = channelService.getOrderService(); + String orderId = "123"; + VirtualTelNumberResponse response = orderService.getVirtualTelNumber(orderId); + assertNotNull(response); + assertTrue(response.isSuccess()); + } + + @Test + public void testDecodeSensitiveInfo() throws WxErrorException { + WxChannelOrderService orderService = channelService.getOrderService(); + String orderId = "123"; + DecodeSensitiveInfoResponse response = orderService.decodeSensitiveInfo(orderId); + assertNotNull(response); + assertTrue(response.isSuccess()); + } +} diff --git a/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/api/impl/WxChannelProductServiceImplTest.java b/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/api/impl/WxChannelProductServiceImplTest.java new file mode 100644 index 0000000000..ed83434ee0 --- /dev/null +++ b/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/api/impl/WxChannelProductServiceImplTest.java @@ -0,0 +1,215 @@ +package me.chanjar.weixin.channel.api.impl; + +import static org.testng.Assert.assertNotNull; +import static org.testng.Assert.assertTrue; + +import com.google.inject.Inject; +import java.util.ArrayList; +import java.util.List; +import me.chanjar.weixin.channel.api.WxChannelProductService; +import me.chanjar.weixin.channel.api.WxChannelService; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; +import me.chanjar.weixin.channel.bean.limit.LimitTaskAddResponse; +import me.chanjar.weixin.channel.bean.limit.LimitTaskListResponse; +import me.chanjar.weixin.channel.bean.limit.LimitTaskParam; +import me.chanjar.weixin.channel.bean.product.SkuStockBatchResponse; +import me.chanjar.weixin.channel.bean.product.SkuStockResponse; +import me.chanjar.weixin.channel.bean.product.SpuGetResponse; +import me.chanjar.weixin.channel.bean.product.SpuListResponse; +import me.chanjar.weixin.channel.bean.product.SpuUpdateInfo; +import me.chanjar.weixin.channel.bean.product.SpuUpdateResponse; +import me.chanjar.weixin.channel.bean.product.link.ProductH5UrlResponse; +import me.chanjar.weixin.channel.bean.product.link.ProductQrCodeResponse; +import me.chanjar.weixin.channel.bean.product.link.ProductTagLinkResponse; +import me.chanjar.weixin.channel.test.ApiTestModule; +import me.chanjar.weixin.common.error.WxErrorException; +import org.testng.annotations.Guice; +import org.testng.annotations.Test; + +/** + * @author Zeyes + */ +@Guice(modules = ApiTestModule.class) +public class WxChannelProductServiceImplTest { + + @Inject + private WxChannelService channelService; + + @Test + public void testAddProduct() throws WxErrorException { + WxChannelProductService productService = channelService.getProductService(); + SpuUpdateInfo spuInfo = new SpuUpdateInfo(); + // ... + SpuUpdateResponse response = productService.addProduct(spuInfo); + assertNotNull(response); + assertTrue(response.isSuccess()); + } + + @Test + public void testUpdateProduct() throws WxErrorException { + WxChannelProductService productService = channelService.getProductService(); + SpuUpdateInfo spuInfo = new SpuUpdateInfo(); + // ... + SpuUpdateResponse response = productService.updateProduct(spuInfo); + assertNotNull(response); + assertTrue(response.isSuccess()); + } + + @Test + public void testUpdateStock() throws WxErrorException { + WxChannelProductService productService = channelService.getProductService(); + String productId = ""; + String skuId = ""; + Integer diffType = 1; + Integer num = 10; + WxChannelBaseResponse response = productService.updateStock(productId, skuId, diffType, num); + assertNotNull(response); + assertTrue(response.isSuccess()); + } + + @Test + public void testDeleteProduct() throws WxErrorException { + WxChannelProductService productService = channelService.getProductService(); + String productId = ""; + WxChannelBaseResponse response = productService.deleteProduct(productId); + assertNotNull(response); + assertTrue(response.isSuccess()); + } + + @Test + public void testCancelProductAudit() throws WxErrorException { + WxChannelProductService productService = channelService.getProductService(); + String productId = ""; + WxChannelBaseResponse response = productService.cancelProductAudit(productId); + assertNotNull(response); + assertTrue(response.isSuccess()); + } + + @Test + public void testGetProduct() throws WxErrorException { + WxChannelProductService productService = channelService.getProductService(); + String productId = "10000029995861"; + Integer dataType = 3; + SpuGetResponse response = productService.getProduct(productId, dataType); + assertNotNull(response); + assertTrue(response.isSuccess()); + } + + @Test + public void testListProduct() throws WxErrorException { + WxChannelProductService productService = channelService.getProductService(); + Integer pageSize = 10; + String nextKey = null; + Integer status = null; + SpuListResponse response = productService.listProduct(pageSize, nextKey, status); + assertNotNull(response); + assertTrue(response.isSuccess()); + } + + @Test + public void testUpProduct() throws WxErrorException { + WxChannelProductService productService = channelService.getProductService(); + String productId = ""; + WxChannelBaseResponse response = productService.upProduct(productId); + assertNotNull(response); + assertTrue(response.isSuccess()); + } + + @Test + public void testDownProduct() throws WxErrorException { + WxChannelProductService productService = channelService.getProductService(); + String productId = ""; + WxChannelBaseResponse response = productService.downProduct(productId); + assertNotNull(response); + assertTrue(response.isSuccess()); + } + + @Test + public void testGetSkuStock() throws WxErrorException { + WxChannelProductService productService = channelService.getProductService(); + String productId = "10000076089602"; + String skuId = "1918289111"; + SkuStockResponse response = productService.getSkuStock(productId, skuId); + assertNotNull(response); + assertTrue(response.isSuccess()); + } + + @Test + public void testGetSkuStockBatch() throws WxErrorException { + WxChannelProductService productService = channelService.getProductService(); + List productIds = new ArrayList<>(); + productIds.add("123"); + SkuStockBatchResponse response = productService.getSkuStockBatch(productIds); + assertNotNull(response); + assertTrue(response.isSuccess()); + } + + @Test + public void testGetProductH5Url() throws WxErrorException { + WxChannelProductService productService = channelService.getProductService(); + String productId = ""; + ProductH5UrlResponse response = productService.getProductH5Url(productId); + assertNotNull(response); + assertTrue(response.isSuccess()); + System.out.println(response.getProductH5url()); + } + + @Test + public void testGetProductQrCode() throws WxErrorException { + WxChannelProductService productService = channelService.getProductService(); + String productId = ""; + ProductQrCodeResponse response = productService.getProductQrCode(productId); + assertNotNull(response); + assertTrue(response.isSuccess()); + System.out.println(response.getProductQrcode()); + } + + @Test + public void testGetProductTagLink() throws WxErrorException { + WxChannelProductService productService = channelService.getProductService(); + String productId = ""; + ProductTagLinkResponse response = productService.getProductTagLink(productId); + assertNotNull(response); + assertTrue(response.isSuccess()); + System.out.println(response.getProductTaglink()); + } + + @Test + public void testAddLimitTask() throws WxErrorException { + WxChannelProductService productService = channelService.getProductService(); + LimitTaskParam param = new LimitTaskParam(); + // ... + LimitTaskAddResponse response = productService.addLimitTask(param); + assertNotNull(response); + assertTrue(response.isSuccess()); + } + + @Test + public void testListLimitTask() throws WxErrorException { + WxChannelProductService productService = channelService.getProductService(); + Integer pageSize = 1; + String nextKey = ""; + Integer status = null; + LimitTaskListResponse response = productService.listLimitTask(pageSize, nextKey, status); + assertNotNull(response); + assertTrue(response.isSuccess()); + } + + @Test + public void testStopLimitTask() throws WxErrorException { + WxChannelProductService productService = channelService.getProductService(); + String taskId = ""; + WxChannelBaseResponse response = productService.stopLimitTask(taskId); + assertNotNull(response); + assertTrue(response.isSuccess()); + } + + @Test + public void testDeleteLimitTask() throws WxErrorException { + WxChannelProductService productService = channelService.getProductService(); + String taskId = ""; + WxChannelBaseResponse response = productService.deleteLimitTask(taskId); + assertNotNull(response); + assertTrue(response.isSuccess()); + } +} diff --git a/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/api/impl/WxChannelSharerServiceImplTest.java b/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/api/impl/WxChannelSharerServiceImplTest.java new file mode 100644 index 0000000000..91eceac3ac --- /dev/null +++ b/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/api/impl/WxChannelSharerServiceImplTest.java @@ -0,0 +1,78 @@ +package me.chanjar.weixin.channel.api.impl; + +import static org.testng.Assert.*; + +import com.google.inject.Inject; +import java.util.ArrayList; +import java.util.List; +import me.chanjar.weixin.channel.api.WxChannelService; +import me.chanjar.weixin.channel.api.WxChannelSharerService; +import me.chanjar.weixin.channel.bean.sharer.SharerBindResponse; +import me.chanjar.weixin.channel.bean.sharer.SharerInfoResponse; +import me.chanjar.weixin.channel.bean.sharer.SharerOrderParam; +import me.chanjar.weixin.channel.bean.sharer.SharerOrderResponse; +import me.chanjar.weixin.channel.bean.sharer.SharerSearchResponse; +import me.chanjar.weixin.channel.bean.sharer.SharerUnbindResponse; +import me.chanjar.weixin.channel.test.ApiTestModule; +import me.chanjar.weixin.common.error.WxErrorException; +import org.testng.annotations.Guice; +import org.testng.annotations.Test; + +/** + * @author Zeyes + */ +@Guice(modules = ApiTestModule.class) +public class WxChannelSharerServiceImplTest { + + @Inject + private WxChannelService channelService; + + @Test + public void testBindSharer() throws WxErrorException { + WxChannelSharerService sharerService = channelService.getSharerService(); + String username = ""; + SharerBindResponse response = sharerService.bindSharer(username); + assertNotNull(response); + assertTrue(response.isSuccess()); + } + + @Test + public void testSearchSharer() throws WxErrorException { + WxChannelSharerService sharerService = channelService.getSharerService(); + String openid = ""; + String username = ""; + SharerSearchResponse response = sharerService.searchSharer(openid, username); + assertNotNull(response); + assertTrue(response.isSuccess()); + } + + @Test + public void testListSharer() throws WxErrorException { + WxChannelSharerService sharerService = channelService.getSharerService(); + Integer page = 1; + Integer pageSize = 10; + Integer sharerType = 1; + SharerInfoResponse response = sharerService.listSharer(page, pageSize, sharerType); + assertNotNull(response); + assertTrue(response.isSuccess()); + } + + @Test + public void testListSharerOrder() throws WxErrorException { + WxChannelSharerService sharerService = channelService.getSharerService(); + SharerOrderParam param = new SharerOrderParam(); + SharerOrderResponse response = sharerService.listSharerOrder(param); + assertNotNull(response); + assertTrue(response.isSuccess()); + } + + @Test + public void testUnbindSharer() throws WxErrorException { + WxChannelSharerService sharerService = channelService.getSharerService(); + List openIds = new ArrayList<>(4); + openIds.add(""); + SharerUnbindResponse response = sharerService.unbindSharer(openIds); + assertNotNull(response); + assertTrue(response.isSuccess()); + } +} diff --git a/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/api/impl/WxChannelVipServiceImplTest.java b/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/api/impl/WxChannelVipServiceImplTest.java new file mode 100644 index 0000000000..fdcf599e90 --- /dev/null +++ b/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/api/impl/WxChannelVipServiceImplTest.java @@ -0,0 +1,32 @@ +package me.chanjar.weixin.channel.api.impl; + +import static org.testng.Assert.assertNotNull; +import static org.testng.Assert.assertTrue; + +import com.google.inject.Inject; +import me.chanjar.weixin.channel.api.WxChannelService; +import me.chanjar.weixin.channel.bean.vip.VipInfoResponse; +import me.chanjar.weixin.channel.test.ApiTestModule; +import me.chanjar.weixin.channel.util.JsonUtils; +import me.chanjar.weixin.common.error.WxErrorException; +import org.testng.annotations.Guice; +import org.testng.annotations.Test; + +/** + * @author Zeyes + */ +@Guice(modules = ApiTestModule.class) +public class WxChannelVipServiceImplTest { + @Inject + private WxChannelService channelService; + + @Test + public void getVipInfo() throws WxErrorException { + String openId = ""; + Boolean needPhoneNumber = false; + VipInfoResponse response = channelService.getVipService().getVipInfo(openId, needPhoneNumber); + System.out.println(JsonUtils.encode(response)); + assertNotNull(response); + assertTrue(response.isSuccess()); + } +} diff --git a/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/api/impl/WxChannelWarehouseServiceImplTest.java b/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/api/impl/WxChannelWarehouseServiceImplTest.java new file mode 100644 index 0000000000..2210f765a2 --- /dev/null +++ b/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/api/impl/WxChannelWarehouseServiceImplTest.java @@ -0,0 +1,138 @@ +package me.chanjar.weixin.channel.api.impl; + +import static org.testng.Assert.assertNotNull; +import static org.testng.Assert.assertTrue; + +import com.google.inject.Inject; +import java.util.ArrayList; +import java.util.List; +import me.chanjar.weixin.channel.api.WxChannelService; +import me.chanjar.weixin.channel.api.WxChannelWarehouseService; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; +import me.chanjar.weixin.channel.bean.warehouse.LocationPriorityResponse; +import me.chanjar.weixin.channel.bean.warehouse.PriorityLocationParam; +import me.chanjar.weixin.channel.bean.warehouse.WarehouseIdsResponse; +import me.chanjar.weixin.channel.bean.warehouse.WarehouseLocation; +import me.chanjar.weixin.channel.bean.warehouse.WarehouseParam; +import me.chanjar.weixin.channel.bean.warehouse.WarehouseResponse; +import me.chanjar.weixin.channel.bean.warehouse.WarehouseStockParam; +import me.chanjar.weixin.channel.bean.warehouse.WarehouseStockResponse; +import me.chanjar.weixin.channel.test.ApiTestModule; +import me.chanjar.weixin.common.error.WxErrorException; +import org.testng.annotations.Guice; +import org.testng.annotations.Test; + +/** + * @author Zeyes + */ +@Guice(modules = ApiTestModule.class) +public class WxChannelWarehouseServiceImplTest { + + @Inject + private WxChannelService channelService; + + @Test + public void testCreateWarehouse() throws WxErrorException { + WxChannelWarehouseService warehouseService = channelService.getWarehouseService(); + WarehouseParam param = new WarehouseParam(); + // ... + WxChannelBaseResponse response = warehouseService.createWarehouse(param); + assertNotNull(response); + assertTrue(response.isSuccess()); + } + + @Test + public void testListWarehouse() throws WxErrorException { + WxChannelWarehouseService warehouseService = channelService.getWarehouseService(); + Integer pageSize = 10; + WarehouseIdsResponse response = warehouseService.listWarehouse(pageSize, null); + assertNotNull(response); + assertTrue(response.isSuccess()); + } + + @Test + public void testGetWarehouse() throws WxErrorException { + WxChannelWarehouseService warehouseService = channelService.getWarehouseService(); + String warehouseId = "123"; + WarehouseResponse response = warehouseService.getWarehouse(warehouseId); + assertNotNull(response); + assertTrue(response.isSuccess()); + } + + @Test + public void testUpdateWarehouse() throws WxErrorException { + WxChannelWarehouseService warehouseService = channelService.getWarehouseService(); + String outWarehouseId = ""; + String name = ""; + String intro = ""; + WxChannelBaseResponse response = warehouseService.updateWarehouse(outWarehouseId, name, intro); + assertNotNull(response); + assertTrue(response.isSuccess()); + } + + @Test + public void testAddWarehouseArea() throws WxErrorException { + WxChannelWarehouseService warehouseService = channelService.getWarehouseService(); + String outWarehouseId = ""; + List coverLocations = new ArrayList<>(4); + WarehouseLocation location = new WarehouseLocation(); + coverLocations.add(location); + WxChannelBaseResponse response = warehouseService.addWarehouseArea(outWarehouseId, coverLocations); + assertNotNull(response); + assertTrue(response.isSuccess()); + } + + @Test + public void testDeleteWarehouseArea() throws WxErrorException { + WxChannelWarehouseService warehouseService = channelService.getWarehouseService(); + String outWarehouseId = ""; + List coverLocations = new ArrayList<>(4); + WarehouseLocation location = new WarehouseLocation(); + coverLocations.add(location); + WxChannelBaseResponse response = warehouseService.deleteWarehouseArea(outWarehouseId, coverLocations); + assertNotNull(response); + assertTrue(response.isSuccess()); + } + + @Test + public void testSetWarehousePriority() throws WxErrorException { + WxChannelWarehouseService warehouseService = channelService.getWarehouseService(); + PriorityLocationParam param = new PriorityLocationParam(); + WxChannelBaseResponse response = warehouseService.setWarehousePriority(param); + assertNotNull(response); + assertTrue(response.isSuccess()); + } + + @Test + public void testGetWarehousePriority() throws WxErrorException { + WxChannelWarehouseService warehouseService = channelService.getWarehouseService(); + Integer addressId1 = 1; + Integer addressId2 = 2; + Integer addressId3 = 3; + Integer addressId4 = 4; + LocationPriorityResponse response = warehouseService + .getWarehousePriority(addressId1, addressId2, addressId3, addressId4); + assertNotNull(response); + assertTrue(response.isSuccess()); + } + + @Test + public void testUpdateWarehouseStock() throws WxErrorException { + WxChannelWarehouseService warehouseService = channelService.getWarehouseService(); + WarehouseStockParam param = new WarehouseStockParam(); + WxChannelBaseResponse response = warehouseService.updateWarehouseStock(param); + assertNotNull(response); + assertTrue(response.isSuccess()); + } + + @Test + public void testGetWarehouseStock() throws WxErrorException { + WxChannelWarehouseService warehouseService = channelService.getWarehouseService(); + String productId = ""; + String skuId = ""; + String outWarehouseId = ""; + WarehouseStockResponse response = warehouseService.getWarehouseStock(productId, skuId, outWarehouseId); + assertNotNull(response); + assertTrue(response.isSuccess()); + } +} diff --git a/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/api/impl/WxFinderLiveServiceImplTest.java b/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/api/impl/WxFinderLiveServiceImplTest.java new file mode 100644 index 0000000000..afb7484b3d --- /dev/null +++ b/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/api/impl/WxFinderLiveServiceImplTest.java @@ -0,0 +1,68 @@ +package me.chanjar.weixin.channel.api.impl; + +import com.google.inject.Inject; +import me.chanjar.weixin.channel.api.WxChannelService; +import me.chanjar.weixin.channel.bean.lead.component.request.GetFinderLiveDataListRequest; +import me.chanjar.weixin.channel.bean.lead.component.request.GetFinderLiveLeadsDataRequest; +import me.chanjar.weixin.channel.bean.lead.component.response.FinderAttrResponse; +import me.chanjar.weixin.channel.bean.lead.component.response.GetFinderLiveDataListResponse; +import me.chanjar.weixin.channel.bean.lead.component.response.GetFinderLiveLeadsDataResponse; +import me.chanjar.weixin.channel.test.ApiTestModule; +import me.chanjar.weixin.common.error.WxErrorException; +import org.testng.annotations.Guice; +import org.testng.annotations.Test; + +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.util.Objects; + +import static org.testng.Assert.assertNotNull; +import static org.testng.Assert.assertTrue; + +/** + * @author imyzt + */ +@Guice(modules = ApiTestModule.class) +public class WxFinderLiveServiceImplTest { + + @Inject + private WxChannelService channelService; + + @Test + public void testGetFinderAttrByAppid() throws WxErrorException { + FinderAttrResponse response = channelService.getFinderLiveService().getFinderAttrByAppid(); + System.out.println(response); + assertNotNull(response); + assertTrue(response.isSuccess()); + } + + @Test + public void testGetFinderLiveDataList() throws WxErrorException { + String lastBuffer = null; + for (; ; ) { + GetFinderLiveDataListRequest req = new GetFinderLiveDataListRequest(); + req.setLastBuffer(lastBuffer); + GetFinderLiveDataListResponse response = channelService.getFinderLiveService().getFinderLiveDataList(req); + System.out.println(response); + assertNotNull(response); + assertTrue(response.isSuccess()); + lastBuffer = response.getLastBuffer(); + if (Objects.isNull(lastBuffer)) { + break; + } + } + } + + @Test + public void testGetFinderLiveLeadsData() throws WxErrorException { + GetFinderLiveLeadsDataRequest req = new GetFinderLiveLeadsDataRequest(); + req.setStartTime(Instant.now().minus(1, ChronoUnit.DAYS).getEpochSecond()); + req.setEndTime(Instant.now().getEpochSecond()); + GetFinderLiveLeadsDataResponse response = channelService.getFinderLiveService().getFinderLiveLeadsData(req); + assertNotNull(response); + assertTrue(response.isSuccess()); + for (GetFinderLiveLeadsDataResponse.LeadCountItem item : response.getItems()) { + System.out.println(item.toString()); + } + } +} diff --git a/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/api/impl/WxLeadComponentServiceImplTest.java b/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/api/impl/WxLeadComponentServiceImplTest.java new file mode 100644 index 0000000000..b4088473c6 --- /dev/null +++ b/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/api/impl/WxLeadComponentServiceImplTest.java @@ -0,0 +1,133 @@ +package me.chanjar.weixin.channel.api.impl; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.inject.Inject; +import me.chanjar.weixin.channel.api.WxChannelService; +import me.chanjar.weixin.channel.bean.lead.component.request.GetLeadInfoByComponentRequest; +import me.chanjar.weixin.channel.bean.lead.component.request.GetLeadsComponentIdRequest; +import me.chanjar.weixin.channel.bean.lead.component.request.GetLeadsComponentPromoteRecordRequest; +import me.chanjar.weixin.channel.bean.lead.component.request.GetLeadsInfoByRequestIdRequest; +import me.chanjar.weixin.channel.bean.lead.component.request.GetLeadsRequestIdRequest; +import me.chanjar.weixin.channel.bean.lead.component.response.GetLeadsComponentIdResponse; +import me.chanjar.weixin.channel.bean.lead.component.response.GetLeadsComponentPromoteRecordResponse; +import me.chanjar.weixin.channel.bean.lead.component.response.GetLeadsRequestIdResponse; +import me.chanjar.weixin.channel.bean.lead.component.response.LeadInfoResponse; +import me.chanjar.weixin.channel.test.ApiTestModule; +import me.chanjar.weixin.common.error.WxErrorException; +import org.testng.annotations.Guice; +import org.testng.annotations.Test; + +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.util.Objects; + +import static org.testng.Assert.assertNotNull; +import static org.testng.Assert.assertTrue; +/** + * @author imyzt + */ +@Guice(modules = ApiTestModule.class) +public class WxLeadComponentServiceImplTest { + + private static final String LEADS_COMPONENT_ID = "123"; + private static final String REQUEST_ID = "123"; + private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + @Inject + private WxChannelService channelService; + + @Test + public void testGetLeadsInfoByComponentId() throws WxErrorException, JsonProcessingException { + String lastBuffer = null; + for (; ; ) { + GetLeadInfoByComponentRequest req = new GetLeadInfoByComponentRequest(); + req.setStartTime(Instant.now().minus(1, ChronoUnit.DAYS).getEpochSecond()); + req.setEndTime(Instant.now().getEpochSecond()); + req.setLeadsComponentId(LEADS_COMPONENT_ID); + req.setLastBuffer(lastBuffer); + req.setVersion(1); + LeadInfoResponse response = channelService.getLeadComponentService().getLeadsInfoByComponentId(req); + System.out.println(OBJECT_MAPPER.writeValueAsString(response)); + assertNotNull(response); + assertTrue(response.isSuccess()); + lastBuffer = response.getLastBuffer(); + if (Objects.isNull(lastBuffer)) { + break; + } + } + } + + @Test + public void testGetLeadsInfoByRequestId() throws WxErrorException, JsonProcessingException { + String lastBuffer = null; + for (; ; ) { + GetLeadsInfoByRequestIdRequest req = new GetLeadsInfoByRequestIdRequest(); + req.setLastBuffer(lastBuffer); + req.setRequestId(REQUEST_ID); + LeadInfoResponse response = channelService.getLeadComponentService().getLeadsInfoByRequestId(req); + System.out.println(OBJECT_MAPPER.writeValueAsString(response)); + assertNotNull(response); + assertTrue(response.isSuccess()); + lastBuffer = response.getLastBuffer(); + if (Objects.isNull(lastBuffer)) { + break; + } + } + } + + @Test + public void testGetLeadsRequestId() throws WxErrorException, JsonProcessingException { + String lastBuffer = null; + for (; ; ) { + GetLeadsRequestIdRequest req = new GetLeadsRequestIdRequest(); + req.setLastBuffer(lastBuffer); + req.setLeadsComponentId(LEADS_COMPONENT_ID); + GetLeadsRequestIdResponse response = channelService.getLeadComponentService().getLeadsRequestId(req); + System.out.println(OBJECT_MAPPER.writeValueAsString(response)); + assertNotNull(response); + assertTrue(response.isSuccess()); + lastBuffer = response.getLastBuffer(); + if (Objects.isNull(lastBuffer)) { + break; + } + } + } + + @Test + public void testGetLeadsComponentPromoteRecord() throws WxErrorException, JsonProcessingException { + String lastBuffer = null; + for (; ; ) { + GetLeadsComponentPromoteRecordRequest req = new GetLeadsComponentPromoteRecordRequest(); + req.setStartTime(Instant.now().minus(1, ChronoUnit.DAYS).getEpochSecond()); + req.setEndTime(Instant.now().getEpochSecond()); + req.setLeadsComponentId(LEADS_COMPONENT_ID); + req.setLastBuffer(lastBuffer); + GetLeadsComponentPromoteRecordResponse response = channelService.getLeadComponentService().getLeadsComponentPromoteRecord(req); + System.out.println(OBJECT_MAPPER.writeValueAsString(response)); + assertNotNull(response); + assertTrue(response.isSuccess()); + lastBuffer = response.getLastBuffer(); + if (Objects.isNull(lastBuffer)) { + break; + } + } + } + + @Test + public void testGetLeadsComponentId() throws WxErrorException, JsonProcessingException { + String lastBuffer = null; + for (; ; ) { + GetLeadsComponentIdRequest req = new GetLeadsComponentIdRequest(); + req.setLastBuffer(lastBuffer); + GetLeadsComponentIdResponse response = channelService.getLeadComponentService().getLeadsComponentId(req); + System.out.println(OBJECT_MAPPER.writeValueAsString(response)); + assertNotNull(response); + assertTrue(response.isSuccess()); + lastBuffer = response.getLastBuffer(); + if (Objects.isNull(lastBuffer)) { + break; + } + } + } + +} diff --git a/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/api/impl/WxLeagueProductServiceImplTest.java b/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/api/impl/WxLeagueProductServiceImplTest.java new file mode 100644 index 0000000000..762a7f45d6 --- /dev/null +++ b/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/api/impl/WxLeagueProductServiceImplTest.java @@ -0,0 +1,76 @@ +package me.chanjar.weixin.channel.api.impl; + +import static org.testng.Assert.*; + +import com.google.inject.Inject; +import me.chanjar.weixin.channel.api.WxChannelService; +import me.chanjar.weixin.channel.api.WxLeagueProductService; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; +import me.chanjar.weixin.channel.bean.league.product.BatchAddParam; +import me.chanjar.weixin.channel.bean.league.product.BatchAddResponse; +import me.chanjar.weixin.channel.bean.league.product.ProductDetailParam; +import me.chanjar.weixin.channel.bean.league.product.ProductDetailResponse; +import me.chanjar.weixin.channel.bean.league.product.ProductListParam; +import me.chanjar.weixin.channel.bean.league.product.ProductListResponse; +import me.chanjar.weixin.channel.bean.league.product.ProductUpdateParam; +import me.chanjar.weixin.channel.bean.league.product.ProductUpdateResponse; +import me.chanjar.weixin.channel.test.ApiTestModule; +import me.chanjar.weixin.common.error.WxErrorException; +import org.testng.annotations.Guice; +import org.testng.annotations.Test; + +/** + * @author Zeyes + */ +@Guice(modules = ApiTestModule.class) +public class WxLeagueProductServiceImplTest { + + @Inject + private WxChannelService channelService; + + @Test + public void testBatchAddProduct() throws WxErrorException { + WxLeagueProductService leagueProductService = channelService.getLeagueProductService(); + BatchAddParam param = new BatchAddParam(); + BatchAddResponse response = leagueProductService.batchAddProduct(param); + assertNotNull(response); + assertTrue(response.isSuccess()); + } + + @Test + public void testUpdateProduct() throws WxErrorException { + WxLeagueProductService leagueProductService = channelService.getLeagueProductService(); + ProductUpdateParam param = new ProductUpdateParam(); + ProductUpdateResponse response = leagueProductService.updateProduct(param); + assertNotNull(response); + assertTrue(response.isSuccess()); + } + + @Test + public void testDeleteProduct() throws WxErrorException { + WxLeagueProductService leagueProductService = channelService.getLeagueProductService(); + Integer type = 1; + String productId = ""; + WxChannelBaseResponse response = leagueProductService.deleteProduct(type, productId, null); + assertNotNull(response); + assertTrue(response.isSuccess()); + } + + @Test + public void testGetProductDetail() throws WxErrorException { + WxLeagueProductService leagueProductService = channelService.getLeagueProductService(); + ProductDetailParam param = new ProductDetailParam(); + ProductDetailResponse response = leagueProductService.getProductDetail(param); + assertNotNull(response); + assertTrue(response.isSuccess()); + } + + @Test + public void testListProduct() throws WxErrorException { + WxLeagueProductService leagueProductService = channelService.getLeagueProductService(); + ProductListParam param = new ProductListParam(); + ProductListResponse response = leagueProductService.listProduct(param); + assertNotNull(response); + assertTrue(response.isSuccess()); + } +} diff --git a/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/api/impl/WxLeaguePromoterServiceImplTest.java b/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/api/impl/WxLeaguePromoterServiceImplTest.java new file mode 100644 index 0000000000..aae33fdb46 --- /dev/null +++ b/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/api/impl/WxLeaguePromoterServiceImplTest.java @@ -0,0 +1,73 @@ +package me.chanjar.weixin.channel.api.impl; + +import static org.testng.Assert.assertNotNull; +import static org.testng.Assert.assertTrue; + +import com.google.inject.Inject; +import me.chanjar.weixin.channel.api.WxChannelService; +import me.chanjar.weixin.channel.api.WxLeaguePromoterService; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; +import me.chanjar.weixin.channel.bean.league.promoter.PromoterInfoResponse; +import me.chanjar.weixin.channel.bean.league.promoter.PromoterListResponse; +import me.chanjar.weixin.channel.test.ApiTestModule; +import me.chanjar.weixin.common.error.WxErrorException; +import org.testng.annotations.Guice; +import org.testng.annotations.Test; + +/** + * @author Zeyes + */ +@Guice(modules = ApiTestModule.class) +public class WxLeaguePromoterServiceImplTest { + + @Inject + private WxChannelService channelService; + + @Test + public void testAddPromoter() throws WxErrorException { + WxLeaguePromoterService leaguePromoterService = channelService.getLeaguePromoterService(); + String finderId = ""; + WxChannelBaseResponse response = leaguePromoterService.addPromoter(finderId); + assertNotNull(response); + assertTrue(response.isSuccess()); + } + + @Test + public void testUpdatePromoter() throws WxErrorException { + WxLeaguePromoterService leaguePromoterService = channelService.getLeaguePromoterService(); + String finderId = ""; + int type = 1; + WxChannelBaseResponse response = leaguePromoterService.updatePromoter(finderId, type); + assertNotNull(response); + assertTrue(response.isSuccess()); + } + + @Test + public void testDeletePromoter() throws WxErrorException { + WxLeaguePromoterService leaguePromoterService = channelService.getLeaguePromoterService(); + String finderId = ""; + WxChannelBaseResponse response = leaguePromoterService.deletePromoter(finderId); + assertNotNull(response); + assertTrue(response.isSuccess()); + } + + @Test + public void testGetPromoterInfo() throws WxErrorException { + WxLeaguePromoterService leaguePromoterService = channelService.getLeaguePromoterService(); + String finderId = ""; + PromoterInfoResponse response = leaguePromoterService.getPromoterInfo(finderId); + assertNotNull(response); + assertTrue(response.isSuccess()); + } + + @Test + public void testListPromoter() throws WxErrorException { + WxLeaguePromoterService leaguePromoterService = channelService.getLeaguePromoterService(); + Integer pageIndex = 1; + Integer pageSize = 10; + Integer status = null; + PromoterListResponse response = leaguePromoterService.listPromoter(pageIndex, pageSize, status); + assertNotNull(response); + assertTrue(response.isSuccess()); + } +} diff --git a/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/api/impl/WxLeagueSupplierServiceImplTest.java b/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/api/impl/WxLeagueSupplierServiceImplTest.java new file mode 100644 index 0000000000..798aac3560 --- /dev/null +++ b/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/api/impl/WxLeagueSupplierServiceImplTest.java @@ -0,0 +1,118 @@ +package me.chanjar.weixin.channel.api.impl; + +import static org.testng.Assert.assertNotNull; +import static org.testng.Assert.assertTrue; + +import com.google.inject.Inject; +import me.chanjar.weixin.channel.api.WxChannelService; +import me.chanjar.weixin.channel.api.WxLeagueSupplierService; +import me.chanjar.weixin.channel.bean.league.supplier.CommissionOrderListParam; +import me.chanjar.weixin.channel.bean.league.supplier.CommissionOrderListResponse; +import me.chanjar.weixin.channel.bean.league.supplier.CommissionOrderResponse; +import me.chanjar.weixin.channel.bean.league.supplier.CoopProductListResponse; +import me.chanjar.weixin.channel.bean.league.supplier.CoopProductResponse; +import me.chanjar.weixin.channel.bean.league.supplier.FlowListParam; +import me.chanjar.weixin.channel.bean.league.supplier.ShopDetailResponse; +import me.chanjar.weixin.channel.bean.league.supplier.ShopListResponse; +import me.chanjar.weixin.channel.bean.league.supplier.SupplierBalanceResponse; +import me.chanjar.weixin.channel.bean.league.supplier.SupplierFlowDetailResponse; +import me.chanjar.weixin.channel.bean.league.supplier.SupplierFlowListResponse; +import me.chanjar.weixin.channel.test.ApiTestModule; +import me.chanjar.weixin.common.error.WxErrorException; +import org.testng.annotations.Guice; +import org.testng.annotations.Test; + +/** + * @author Zeyes + */ +@Guice(modules = ApiTestModule.class) +public class WxLeagueSupplierServiceImplTest { + + @Inject + private WxChannelService channelService; + + @Test + public void testGetBalanceInfo() throws WxErrorException { + WxLeagueSupplierService leagueSupplierService = channelService.getLeagueSupplierService(); + SupplierBalanceResponse response = leagueSupplierService.getBalanceInfo(); + assertNotNull(response); + assertTrue(response.isSuccess()); + } + + @Test + public void testGetFlowDetail() throws WxErrorException { + WxLeagueSupplierService leagueSupplierService = channelService.getLeagueSupplierService(); + String flowId = ""; + SupplierFlowDetailResponse response = leagueSupplierService.getFlowDetail(flowId); + assertNotNull(response); + assertTrue(response.isSuccess()); + } + + @Test + public void testGetFlowList() throws WxErrorException { + WxLeagueSupplierService leagueSupplierService = channelService.getLeagueSupplierService(); + FlowListParam param = new FlowListParam(); + SupplierFlowListResponse response = leagueSupplierService.getFlowList(param); + assertNotNull(response); + assertTrue(response.isSuccess()); + } + + @Test + public void testGetProductDetail() throws WxErrorException { + WxLeagueSupplierService leagueSupplierService = channelService.getLeagueSupplierService(); + String productId = ""; + String appId = ""; + CoopProductResponse response = leagueSupplierService.getProductDetail(productId, appId); + assertNotNull(response); + assertTrue(response.isSuccess()); + } + + @Test + public void testGetProductList() throws WxErrorException { + WxLeagueSupplierService leagueSupplierService = channelService.getLeagueSupplierService(); + String appId = ""; + Integer pageSize = 10; + String nextKey = null; + CoopProductListResponse response = leagueSupplierService.getProductList(appId, pageSize, nextKey); + assertNotNull(response); + assertTrue(response.isSuccess()); + } + + @Test + public void testGetCommissionOrder() throws WxErrorException { + WxLeagueSupplierService leagueSupplierService = channelService.getLeagueSupplierService(); + String orderId = ""; + String skuId = ""; + CommissionOrderResponse response = leagueSupplierService.getCommissionOrder(orderId, skuId); + assertNotNull(response); + assertTrue(response.isSuccess()); + } + + @Test + public void testGetCommissionOrderList() throws WxErrorException { + WxLeagueSupplierService leagueSupplierService = channelService.getLeagueSupplierService(); + CommissionOrderListParam param = new CommissionOrderListParam(); + CommissionOrderListResponse response = leagueSupplierService.getCommissionOrderList(param); + assertNotNull(response); + assertTrue(response.isSuccess()); + } + + @Test + public void testGetShopDetail() throws WxErrorException { + WxLeagueSupplierService leagueSupplierService = channelService.getLeagueSupplierService(); + String appId = ""; + ShopDetailResponse response = leagueSupplierService.getShopDetail(appId); + assertNotNull(response); + assertTrue(response.isSuccess()); + } + + @Test + public void testGetShopList() throws WxErrorException { +WxLeagueSupplierService leagueSupplierService = channelService.getLeagueSupplierService(); + Integer pageSize = 10; + String nextKey = null; + ShopListResponse response = leagueSupplierService.getShopList(pageSize, nextKey); + assertNotNull(response); + assertTrue(response.isSuccess()); + } +} diff --git a/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/api/impl/WxLeagueWindowServiceImplTest.java b/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/api/impl/WxLeagueWindowServiceImplTest.java new file mode 100644 index 0000000000..3546b50b47 --- /dev/null +++ b/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/api/impl/WxLeagueWindowServiceImplTest.java @@ -0,0 +1,90 @@ +package me.chanjar.weixin.channel.api.impl; + +import static org.testng.Assert.*; + +import com.google.inject.Inject; +import me.chanjar.weixin.channel.api.WxChannelService; +import me.chanjar.weixin.channel.api.WxLeagueProductService; +import me.chanjar.weixin.channel.api.WxLeagueWindowService; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; +import me.chanjar.weixin.channel.bean.league.product.BatchAddParam; +import me.chanjar.weixin.channel.bean.league.product.BatchAddResponse; +import me.chanjar.weixin.channel.bean.league.window.AuthInfoResponse; +import me.chanjar.weixin.channel.bean.league.window.AuthStatusResponse; +import me.chanjar.weixin.channel.bean.league.window.ProductSearchParam; +import me.chanjar.weixin.channel.bean.league.window.WindowProductListResponse; +import me.chanjar.weixin.channel.bean.league.window.WindowProductResponse; +import me.chanjar.weixin.channel.test.ApiTestModule; +import me.chanjar.weixin.common.error.WxErrorException; +import org.testng.annotations.Guice; +import org.testng.annotations.Test; + +/** + * @author Zeyes + */ +@Guice(modules = ApiTestModule.class) +public class WxLeagueWindowServiceImplTest { + + @Inject + private WxChannelService channelService; + + @Test + public void testAddProduct() throws WxErrorException { + WxLeagueWindowService leagueWindowService = channelService.getLeagueWindowService(); + String appid = ""; + String openfinderid = ""; + String productId = ""; + WxChannelBaseResponse response = leagueWindowService.addProduct(appid, openfinderid, productId); + assertNotNull(response); + assertTrue(response.isSuccess()); + } + + @Test + public void testListProduct() throws WxErrorException { + WxLeagueWindowService leagueWindowService = channelService.getLeagueWindowService(); + ProductSearchParam param = new ProductSearchParam(); + WindowProductListResponse response = leagueWindowService.listProduct(param); + assertNotNull(response); + assertTrue(response.isSuccess()); + } + + @Test + public void testRemoveProduct() throws WxErrorException { + WxLeagueWindowService leagueWindowService = channelService.getLeagueWindowService(); + String appid = ""; + String openfinderid = ""; + String productId = ""; + WxChannelBaseResponse response = leagueWindowService.removeProduct(appid, openfinderid, productId); + assertNotNull(response); + assertTrue(response.isSuccess()); + } + + @Test + public void testGetProductDetail() throws WxErrorException { + WxLeagueWindowService leagueWindowService = channelService.getLeagueWindowService(); + String appid = ""; + String openfinderid = ""; + String productId = ""; + WindowProductResponse response = leagueWindowService.getProductDetail(appid, openfinderid, productId); + assertNotNull(response); + assertTrue(response.isSuccess()); + } + + @Test + public void testGetWindowAuthInfo() throws WxErrorException { + WxLeagueWindowService leagueWindowService = channelService.getLeagueWindowService(); + String finderId = ""; + AuthInfoResponse response = leagueWindowService.getWindowAuthInfo(finderId); + assertNotNull(response); + assertTrue(response.isSuccess()); + } + + @Test + public void testGetWindowAuthStatus() throws WxErrorException { + WxLeagueWindowService leagueWindowService = channelService.getLeagueWindowService(); + String finderId = ""; + AuthStatusResponse response = leagueWindowService.getWindowAuthStatus(finderId); + assertNotNull(response); + assertTrue(response.isSuccess()); + } +} diff --git a/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/api/impl/WxStoreCooperationServiceImplTest.java b/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/api/impl/WxStoreCooperationServiceImplTest.java new file mode 100644 index 0000000000..bf70ce3c78 --- /dev/null +++ b/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/api/impl/WxStoreCooperationServiceImplTest.java @@ -0,0 +1,66 @@ +package me.chanjar.weixin.channel.api.impl; + +import static org.testng.Assert.assertNotNull; +import static org.testng.Assert.assertTrue; + +import com.google.inject.Inject; +import me.chanjar.weixin.channel.api.WxChannelService; +import me.chanjar.weixin.channel.api.WxStoreCooperationService; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; +import me.chanjar.weixin.channel.test.ApiTestModule; +import me.chanjar.weixin.channel.util.JsonUtils; +import me.chanjar.weixin.common.error.WxErrorException; +import org.testng.annotations.Guice; +import org.testng.annotations.Test; + +/** + * @author Zeyes + */ +@Guice(modules = ApiTestModule.class) +public class WxStoreCooperationServiceImplTest { + + @Inject + private WxChannelService channelService; + + @Test + public void testListCooperation() throws WxErrorException { + WxStoreCooperationService service = channelService.getCooperationService(); + Integer sharerType = 3; + WxChannelBaseResponse response = service.listCooperation(sharerType); + assertNotNull(response); + assertTrue(response.isSuccess()); + System.out.println(JsonUtils.encode(response)); + } + + @Test + public void testGetCooperationStatus() throws WxErrorException { + WxStoreCooperationService service = channelService.getCooperationService(); + WxChannelBaseResponse response = service.getCooperationStatus("sph3FZbOEY64mWQ", 2); + assertNotNull(response); + assertTrue(response.isSuccess()); + } + + @Test + public void testGenerateQrCode() throws WxErrorException { + WxStoreCooperationService service = channelService.getCooperationService(); + WxChannelBaseResponse response = service.generateQrCode("sph3FZbOEY64mWQ", 2); + assertNotNull(response); + assertTrue(response.isSuccess()); + } + + @Test + public void testCancelInvitation() throws WxErrorException { + WxStoreCooperationService service = channelService.getCooperationService(); + WxChannelBaseResponse response = service.cancelInvitation("sph3FZbOEY64mWQ", 2); + assertNotNull(response); + assertTrue(response.isSuccess()); + } + + @Test + public void testUnbind() throws WxErrorException { + WxStoreCooperationService service = channelService.getCooperationService(); + WxChannelBaseResponse response = service.unbind("sph3FZbOEY64mWQ", 2); + assertNotNull(response); + assertTrue(response.isSuccess()); + } +} diff --git a/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/api/impl/WxStoreHomePageServiceImplTest.java b/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/api/impl/WxStoreHomePageServiceImplTest.java new file mode 100644 index 0000000000..0d6bb8c479 --- /dev/null +++ b/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/api/impl/WxStoreHomePageServiceImplTest.java @@ -0,0 +1,339 @@ +package me.chanjar.weixin.channel.api.impl; + +import static org.testng.Assert.assertNotNull; +import static org.testng.Assert.assertTrue; + +import com.google.inject.Inject; +import me.chanjar.weixin.channel.api.WxStoreHomePageService; +import me.chanjar.weixin.channel.api.WxChannelService; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; +import me.chanjar.weixin.channel.bean.home.background.BackgroundApplyResponse; +import me.chanjar.weixin.channel.bean.home.background.BackgroundGetResponse; +import me.chanjar.weixin.channel.bean.home.banner.BannerApplyParam; +import me.chanjar.weixin.channel.bean.home.banner.BannerApplyResponse; +import me.chanjar.weixin.channel.bean.home.banner.BannerGetResponse; +import me.chanjar.weixin.channel.bean.home.banner.BannerInfo; +import me.chanjar.weixin.channel.bean.home.tree.TreeProductEditInfo; +import me.chanjar.weixin.channel.bean.home.tree.TreeProductEditParam; +import me.chanjar.weixin.channel.bean.home.tree.TreeProductListInfo; +import me.chanjar.weixin.channel.bean.home.tree.TreeProductListResponse; +import me.chanjar.weixin.channel.bean.home.tree.TreeShowGetResponse; +import me.chanjar.weixin.channel.bean.home.tree.TreeShowInfo; +import me.chanjar.weixin.channel.bean.home.tree.TreeShowParam; +import me.chanjar.weixin.channel.bean.home.tree.TreeShowSetResponse; +import me.chanjar.weixin.channel.bean.home.window.WindowProductSettingResponse; +import me.chanjar.weixin.channel.test.ApiTestModule; +import me.chanjar.weixin.channel.util.JsonUtils; +import me.chanjar.weixin.common.error.WxErrorException; +import org.testng.annotations.Guice; +import org.testng.annotations.Test; + +/** + * @author Zeyes + */ +@Guice(modules = ApiTestModule.class) +public class WxStoreHomePageServiceImplTest { + + @Inject + private WxChannelService channelService; + + @Test + public void testAddTreeProduct() throws WxErrorException { + WxStoreHomePageService service = channelService.getHomePageService(); + // https://developers.weixin.qq.com/doc/store/API/homepage/classification/addclassificationproduct.html + String json = "{\n" + + " \"req\": {\n" + + " \"level_1_id\": 10000046,\n" + + " \"level_2_id\": 10000048,\n" + + " \"product_ids\": [\n" + + " 10000076089602\n" + + " ]\n" + + " }\n" + + "}"; + TreeProductEditParam param = JsonUtils.decode(json, TreeProductEditParam.class); + TreeProductEditInfo info = null; + if (param != null) { + info = param.getReq(); + } + WxChannelBaseResponse response = service.addTreeProduct(info); + assertNotNull(response); + assertTrue(response.isSuccess()); + assertNotNull(info); + System.out.println(JsonUtils.encode(response)); + } + + @Test + public void testDelTreeProduct() throws WxErrorException { + WxStoreHomePageService service = channelService.getHomePageService(); + String json = "{\n" + + " \"req\": {\n" + + " \"level_1_id\": 1,\n" + + " \"level_2_id\": 0,\n" + + " \"product_ids\": [\n" + + " 123\n" + + " ]\n" + + " }\n" + + "}"; + TreeProductEditParam param = JsonUtils.decode(json, TreeProductEditParam.class); + TreeProductEditInfo info = null; + if (param != null) { + info = param.getReq(); + } + WxChannelBaseResponse response = service.delTreeProduct(info); + assertNotNull(response); + assertTrue(response.isSuccess()); + assertNotNull(info); + System.out.println(JsonUtils.encode(response)); + } + + @Test + public void testGetTreeProductList() throws WxErrorException { + WxStoreHomePageService service = channelService.getHomePageService(); + String pageContext = ""; + TreeProductListInfo info = new TreeProductListInfo(); + info.setLevel1Id(1); + info.setLevel2Id(2); + info.setPageSize(10); + info.setPageContext(pageContext); + TreeProductListResponse response = service.getTreeProductList(info); + assertNotNull(response); + assertTrue(response.isSuccess()); + assertNotNull(info); + System.out.println(JsonUtils.encode(response)); + } + + @Test + public void testSetShowTree() throws WxErrorException { + WxStoreHomePageService service = channelService.getHomePageService(); + // https://developers.weixin.qq.com/doc/store/API/homepage/classification/setclassificationtree.html + String json = "{\n" + + " \"req\": {\n" + + " \"version\": 121,\n" + + " \"classification_id_deleted\": [\n" + + " \"1.2\"\n" + + " ],\n" + + " \"tree\": {\n" + + " \"level_1\": [\n" + + " {\n" + + " \"id\": 4,\n" + + " \"name\": \"测试7\",\n" + + " \"level_2\": [\n" + + " {\n" + + " \"id\": 5,\n" + + " \"name\": \"1\",\n" + + " \"is_displayed\": 1\n" + + " }\n" + + " ],\n" + + " \"is_displayed\": 1\n" + + " },\n" + + " {\n" + + " \"id\": 6,\n" + + " \"name\": \"测试8\",\n" + + " \"level_2\": [\n" + + " {\n" + + " \"id\": 7,\n" + + " \"name\": \"1\",\n" + + " \"is_displayed\": 1\n" + + " },\n" + + " {\n" + + " \"id\": 8,\n" + + " \"name\": \"2\",\n" + + " \"is_displayed\": 1\n" + + " }\n" + + " ],\n" + + " \"is_displayed\": 1\n" + + " }\n" + + " ]\n" + + " }\n" + + " }\n" + + "}"; + TreeShowParam param = JsonUtils.decode(json, TreeShowParam.class); + TreeShowInfo info = null; + if (param != null) { + info = param.getReq(); + } + TreeShowSetResponse response = service.setShowTree(info); + assertNotNull(response); + assertTrue(response.isSuccess()); + assertNotNull(info); + System.out.println(JsonUtils.encode(response)); + } + + @Test + public void testGetShowTree() throws WxErrorException { + WxStoreHomePageService service = channelService.getHomePageService(); + TreeShowGetResponse response = service.getShowTree(); + assertNotNull(response); + assertTrue(response.isSuccess()); + System.out.println(JsonUtils.encode(response)); + } + + @Test + public void testListWindowProduct() throws WxErrorException { + WxStoreHomePageService service = channelService.getHomePageService(); + Integer pageSize = 1; + String nextKey = ""; + WindowProductSettingResponse response = service.listWindowProduct(pageSize, nextKey); + assertNotNull(response); + assertTrue(response.isSuccess()); + System.out.println(JsonUtils.encode(response)); + } + + @Test + public void testReorderWindowProduct() throws WxErrorException { + WxStoreHomePageService service = channelService.getHomePageService(); + String productId = ""; + Integer indexNum = 100; + WxChannelBaseResponse response = service.reorderWindowProduct(productId, indexNum); + assertNotNull(response); + assertTrue(response.isSuccess()); + System.out.println(JsonUtils.encode(response)); + } + + @Test + public void testHideWindowProduct() throws WxErrorException { + WxStoreHomePageService service = channelService.getHomePageService(); + String productId = ""; + // 0:显示 1:隐藏 + Integer setHide = 0; + WxChannelBaseResponse response = service.hideWindowProduct(productId, setHide); + assertNotNull(response); + assertTrue(response.isSuccess()); + System.out.println(JsonUtils.encode(response)); + } + + @Test + public void testTopWindowProduct() throws WxErrorException { + WxStoreHomePageService service = channelService.getHomePageService(); + String productId = ""; + // 0:取消置顶 1:置顶 + Integer setTop = 0; + WxChannelBaseResponse response = service.topWindowProduct(productId, setTop); + assertNotNull(response); + assertTrue(response.isSuccess()); + System.out.println(JsonUtils.encode(response)); + } + + @Test + public void testApplyBackground() throws WxErrorException { + WxStoreHomePageService service = channelService.getHomePageService(); + String imgUrl = "https://github.githubassets.com/images/icons/emoji/octocat.png"; + BackgroundApplyResponse response = service.applyBackground(imgUrl); + assertNotNull(response); + assertTrue(response.isSuccess()); + System.out.println(JsonUtils.encode(response)); + } + + @Test + public void testGetBackground() throws WxErrorException { + WxStoreHomePageService service = channelService.getHomePageService(); + BackgroundGetResponse response = service.getBackground(); + assertNotNull(response); + assertTrue(response.isSuccess()); + System.out.println(JsonUtils.encode(response)); + } + + @Test + public void testCancelBackground() throws WxErrorException { + Integer applyId = 1; + WxStoreHomePageService service = channelService.getHomePageService(); + WxChannelBaseResponse response = service.cancelBackground(applyId); + assertNotNull(response); + assertTrue(response.isSuccess()); + System.out.println(JsonUtils.encode(response)); + } + + @Test + public void testRemoveBackground() throws WxErrorException { + WxStoreHomePageService service = channelService.getHomePageService(); + WxChannelBaseResponse response = service.removeBackground(); + assertNotNull(response); + assertTrue(response.isSuccess()); + System.out.println(JsonUtils.encode(response)); + } + + @Test + public void testApplyBanner() throws WxErrorException { + WxStoreHomePageService service = channelService.getHomePageService(); + // https://developers.weixin.qq.com/doc/store/API/homepage/banner/submit_banner_apply.html + String json = "{\n" + + " \"banner\": {\n" + + " \"scale\": 2,\n" + + " \"banner\": [\n" + + " {\n" + + " \"type\": 1,\n" + + " \"product\": {\n" + + " \"product_id\": 123\n" + + " },\n" + + " \"banner\": {\n" + + " \"description\": \"测试商品精品展示位描述\",\n" + + " \"img_url\": \"https://store.mp.video.tencent-cloud.com/abc\",\n" + + " \"title\": \"测试商品精品展示位标题\"\n" + + " }\n" + + " },\n" + + " {\n" + + " \"type\": 3,\n" + + " \"finder\": {\n" + + " \"feed_id\": \"export/abc\",\n" + + " \"finder_user_name\": \"sphabc\"\n" + + " },\n" + + " \"banner\": {\n" + + " \"description\": \"测试视频号视频精品展示位描述\",\n" + + " \"img_url\": \"https://store.mp.video.tencent-cloud.com/abc\",\n" + + " \"title\": \"测试视频号视频精品展示位标题\"\n" + + " }\n" + + " },\n" + + " {\n" + + " \"type\": 4,\n" + + " \"official_account\": {\n" + + " \"url\": \"https://mp.weixin.qq.com/abc\"\n" + + " },\n" + + " \"banner\": {\n" + + " \"description\": \"测试公众号文章精品展示位描述\",\n" + + " \"img_url\": \"https://store.mp.video.tencent-cloud.com/abc\",\n" + + " \"title\": \"测试公众号文章精品展示位标题\"\n" + + " }\n" + + " }\n" + + " ]\n" + + " }\n" + + "}"; + BannerApplyParam param = JsonUtils.decode(json, BannerApplyParam.class); + BannerInfo info = null; + if (param != null) { + info = param.getBanner(); + } + BannerApplyResponse response = service.applyBanner(info); + assertNotNull(response); + assertTrue(response.isSuccess()); + assertNotNull(info); + System.out.println(JsonUtils.encode(response)); + } + + @Test + public void testGetBanner() throws WxErrorException { + WxStoreHomePageService service = channelService.getHomePageService(); + BannerGetResponse response = service.getBanner(); + assertNotNull(response); + assertTrue(response.isSuccess()); + System.out.println(JsonUtils.encode(response)); + } + + @Test + public void testCancelBanner() throws WxErrorException { + Integer applyId = 1; + WxStoreHomePageService service = channelService.getHomePageService(); + WxChannelBaseResponse response = service.cancelBanner(applyId); + assertNotNull(response); + assertTrue(response.isSuccess()); + System.out.println(JsonUtils.encode(response)); + } + + @Test + public void testRemoveBanner() throws WxErrorException { + WxStoreHomePageService service = channelService.getHomePageService(); + WxChannelBaseResponse response = service.removeBanner(); + assertNotNull(response); + assertTrue(response.isSuccess()); + System.out.println(JsonUtils.encode(response)); + } +} diff --git a/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/message/WxChannelMessageRouterRuleTest.java b/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/message/WxChannelMessageRouterRuleTest.java new file mode 100644 index 0000000000..bff360f7cc --- /dev/null +++ b/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/message/WxChannelMessageRouterRuleTest.java @@ -0,0 +1,25 @@ +package me.chanjar.weixin.channel.message; + +import static org.testng.Assert.assertEquals; + +import me.chanjar.weixin.channel.bean.message.order.OrderPayMessage; +import org.testng.annotations.Test; + +/** + * @author Zeyes + */ +public class WxChannelMessageRouterRuleTest { + + @Test + public void testResolveMessageClass() { + WxChannelMessageRouterRule rule = new WxChannelMessageRouterRule<>(); + rule.setMessageClass(OrderPayMessage.class); + assertEquals(rule.getMessageClass(), OrderPayMessage.class); + + + } + + + + +} diff --git a/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/message/WxChannelMessageRouterTest.java b/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/message/WxChannelMessageRouterTest.java new file mode 100644 index 0000000000..9d683edce6 --- /dev/null +++ b/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/message/WxChannelMessageRouterTest.java @@ -0,0 +1,144 @@ +package me.chanjar.weixin.channel.message; + +import static me.chanjar.weixin.channel.constant.MessageEventConstants.BRAND; +import static me.chanjar.weixin.channel.constant.MessageEventConstants.CLOSE_STORE; +import static me.chanjar.weixin.channel.constant.MessageEventConstants.PRODUCT_SPU_AUDIT; +import static me.chanjar.weixin.channel.constant.MessageEventConstants.SET_SHOP_NICKNAME; +import static org.testng.Assert.*; + +import com.google.inject.Inject; +import java.util.Map; +import lombok.extern.slf4j.Slf4j; +import me.chanjar.weixin.channel.api.WxChannelService; +import me.chanjar.weixin.channel.bean.message.product.BrandMessage; +import me.chanjar.weixin.channel.bean.message.product.SpuAuditMessage; +import me.chanjar.weixin.channel.bean.message.store.CloseStoreMessage; +import me.chanjar.weixin.channel.bean.message.store.NicknameUpdateMessage; +import me.chanjar.weixin.channel.message.rule.HandlerConsumer; +import me.chanjar.weixin.channel.test.ApiTestModule; +import me.chanjar.weixin.channel.util.JsonUtils; +import me.chanjar.weixin.common.session.WxSessionManager; +import org.testng.annotations.Guice; +import org.testng.annotations.Test; + +/** + * @author Zeyes + */ +@Slf4j +@Guice(modules = ApiTestModule.class) +public class WxChannelMessageRouterTest { + + @Inject + private WxChannelService channelService; + + @Test + public void test1() { + WxChannelMessageRouter router = new WxChannelMessageRouter(); + /* 品牌资质事件回调 */ + this.addRule(router, BrandMessage.class, BRAND, this::brandUpdate); + /* 商品审核结果 */ + this.addRule(router, SpuAuditMessage.class, PRODUCT_SPU_AUDIT, this::spuAudit); + String spuAuditJson = "{\n" + + " \"ToUserName\":\"gh_*\",\n" + + " \"FromUserName\":\"OPENID\",\n" + + " \"CreateTime\":1662480000,\n" + + " \"MsgType\":\"event\",\n" + + " \"Event\":\"product_spu_audit\",\n" + + " \"ProductSpuAudit\": {\n" + + " \"product_id\":\"12345678\",\n" + + " \"status\":3,\n" + + " \"reason\":\"abc\"\n" + + " }\n" + + "}"; + WxChannelMessage message = JsonUtils.decode(spuAuditJson, WxChannelMessage.class); + Object result = router.route(message, spuAuditJson, "xxxWWQQxxx", channelService); + if (result != null) { + log.info("result:{}", result); + } else { + log.info("return null"); + } + } + + @Test + public void closeStore() { + WxChannelMessageRouter router = new WxChannelMessageRouter(); + /* 小店注销 */ + this.addRule(router, CloseStoreMessage.class, CLOSE_STORE, this::closeStore); + /* 小店修改名称 */ + this.addRule(router, NicknameUpdateMessage.class, SET_SHOP_NICKNAME, this::updateNickname); + String closeStoreJson = "{\n" + + " \"ToUserName\": \"gh_*\",\n" + + " \"FromUserName\": \"OPENID\",\n" + + " \"CreateTime\": 1662480000,\n" + + " \"MsgType\": \"event\",\n" + + " \"Event\": \"channels_ec_close_store\",\n" + + " \"appid\": \"APPID\",\n" + + " \"close_timestamp\": \"1662480000\"\n" + + "}"; + String updateNicknameJson = "{\n" + + " \"ToUserName\": \"gh_*\", \n" + + " \"FromUserName\": \"OPENID\", \n" + + " \"CreateTime\": 1662480000, \n" + + " \"MsgType\": \"event\", \n" + + " \"Event\": \"set_shop_nickname\", \n" + + " \"appid\": \"APPID\",\n" + + " \"old_nickname\": \"旧昵称\",\n" + + " \"new_nickname\": \"新昵称\"\n" + + "}"; + WxChannelMessage message1 = JsonUtils.decode(closeStoreJson, WxChannelMessage.class); + WxChannelMessage message2 = JsonUtils.decode(updateNicknameJson, WxChannelMessage.class); + Object result1 = router.route(message1, closeStoreJson, "123456", channelService); + if (result1 != null) { + log.info("result1:{}", result1); + } else { + log.info("result1 return null"); + } + Object result2 = router.route(message2, updateNicknameJson, "123456", channelService); + if (result2 != null) { + log.info("result2:{}", result2); + } else { + log.info("result2 return null"); + } + } + + public void brandUpdate(BrandMessage message, String content, String appId, + Map context, WxSessionManager sessionManager) { + log.info("品牌更新:{}", JsonUtils.encode(message)); + } + + + public void spuAudit(SpuAuditMessage message, String content, String appId, + Map context, WxSessionManager sessionManager) { + log.info("商品审核:{}", JsonUtils.encode(message)); + } + + public void closeStore(CloseStoreMessage message, String content, String appId, + Map context, WxSessionManager sessionManager) { + log.info("小店注销:{}", JsonUtils.encode(message)); + } + + public void updateNickname(NicknameUpdateMessage message, String content, String appId, + Map context, WxSessionManager sessionManager) { + log.info("昵称更新:{}", JsonUtils.encode(message)); + } + + /** + * 添加一条规则进入路由器 + * + * @param clazz 消息类型 + * @param event 事件类型 + * @param consumer 处理器 + * @param 消息类型 + */ + protected void addRule(WxChannelMessageRouter router, Class clazz, String event, + HandlerConsumer, WxSessionManager> consumer) { + WxChannelMessageRouterRule rule = new WxChannelMessageRouterRule<>(); + rule.setMessageClass(clazz).setEvent(event).setAsync(false); + rule.getHandlers().add((message, content, appId, context, sessionManager) -> { + consumer.accept(message, content, appId, context, sessionManager); + return "success"; + }); + router.getRules().add(rule); + } + +} diff --git a/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/test/ApiTestModule.java b/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/test/ApiTestModule.java new file mode 100644 index 0000000000..ddfb3a4b8a --- /dev/null +++ b/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/test/ApiTestModule.java @@ -0,0 +1,49 @@ +package me.chanjar.weixin.channel.test; + +import com.google.inject.Binder; +import com.google.inject.Module; +import java.io.IOException; +import java.io.InputStream; +import me.chanjar.weixin.channel.api.WxChannelService; +import me.chanjar.weixin.channel.api.impl.WxChannelServiceImpl; +import me.chanjar.weixin.channel.util.XmlUtils; +import me.chanjar.weixin.common.error.WxRuntimeException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * 测试模块 + * + * @author Zeyes + */ +public class ApiTestModule implements Module { + + private final Logger log = LoggerFactory.getLogger(this.getClass()); + private static final String TEST_CONFIG_XML = "test-config.xml"; + + @Override + public void configure(Binder binder) { + try (InputStream inputStream = ClassLoader.getSystemResourceAsStream(TEST_CONFIG_XML)) { + if (inputStream == null) { + throw new WxRuntimeException( + "测试配置文件【" + TEST_CONFIG_XML + "】未找到,请参照test-config-sample.xml文件生成"); + } + + // 提示xml相关依赖不存在时,引入一下就好 + TestConfig config = this.fromXml(TestConfig.class, inputStream); + WxChannelService service = new WxChannelServiceImpl(); + + service.setConfig(config); + + binder.bind(TestConfig.class).toInstance(config); + binder.bind(WxChannelService.class).toInstance(service); + } catch (IOException e) { + this.log.error(e.getMessage(), e); + } + } + + private T fromXml(Class clazz, InputStream is) { + return XmlUtils.decode(is, clazz); + } + +} diff --git a/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/test/TestConfig.java b/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/test/TestConfig.java new file mode 100644 index 0000000000..5f4dd76770 --- /dev/null +++ b/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/test/TestConfig.java @@ -0,0 +1,11 @@ +package me.chanjar.weixin.channel.test; + +import lombok.Getter; +import lombok.Setter; +import me.chanjar.weixin.channel.config.impl.WxChannelDefaultConfigImpl; + +@Getter +@Setter +public class TestConfig extends WxChannelDefaultConfigImpl { + +} diff --git a/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/util/JsonUtilsTest.java b/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/util/JsonUtilsTest.java new file mode 100644 index 0000000000..ca515a3b5c --- /dev/null +++ b/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/util/JsonUtilsTest.java @@ -0,0 +1,29 @@ +package me.chanjar.weixin.channel.util; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertNotNull; + +import me.chanjar.weixin.channel.bean.base.AttrInfo; +import org.testng.annotations.Test; + +/** + * @author Zeyes + */ +public class JsonUtilsTest { + + @Test + public void testEncode() { + AttrInfo info = new AttrInfo("这是Key", "这是Value"); + String json = JsonUtils.encode(info); + System.out.println(json); + assertNotNull(json); + } + + @Test + public void testDecode() { + String json = "{\"attr_key\": \"这是Key\",\"attr_value\": \"这是Value\"}"; + AttrInfo info = JsonUtils.decode(json, AttrInfo.class); + assertNotNull(info); + assertEquals(info.getKey(), "这是Key"); + } +} diff --git a/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/util/ResponseUtilsTest.java b/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/util/ResponseUtilsTest.java new file mode 100644 index 0000000000..036cac29e1 --- /dev/null +++ b/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/util/ResponseUtilsTest.java @@ -0,0 +1,33 @@ +package me.chanjar.weixin.channel.util; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertNotNull; + +import me.chanjar.weixin.channel.bean.shop.ShopInfoResponse; +import org.testng.annotations.Test; + +/** + * @author Zeyes + */ +public class ResponseUtilsTest { + + @Test + public void testDecode() { + String json = "{\"errcode\":0,\"errmsg\":\"ok\",\"info\":{\"nickname\":\"某某视频号\"," + + "\"headimg_url\":\"http://wx.qlogo.cn/xxx\",\"subject_type\":\"企业\"}}"; + ShopInfoResponse response = ResponseUtils.decode(json, ShopInfoResponse.class); + assertNotNull(response); + assertEquals(response.getErrCode(), 0); + assertEquals(response.getErrMsg(), "ok"); + assertEquals(response.getInfo().getNickname(), "某某视频号"); + assertEquals(response.getInfo().getHeadImgUrl(), "http://wx.qlogo.cn/xxx"); + assertEquals(response.getInfo().getSubjectType(), "企业"); + } + + @Test + public void testInternalError() { + ShopInfoResponse response = ResponseUtils.internalError(ShopInfoResponse.class); + assertNotNull(response); + assertEquals(response.getErrCode(), -99); + } +} diff --git a/weixin-java-channel/src/test/resources/logback-test.xml b/weixin-java-channel/src/test/resources/logback-test.xml new file mode 100644 index 0000000000..e4a33acd88 --- /dev/null +++ b/weixin-java-channel/src/test/resources/logback-test.xml @@ -0,0 +1,13 @@ + + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %replace(%caller{1}){'Caller', ''} - %msg%n + + + + + + + + diff --git a/weixin-java-channel/src/test/resources/test-config.sample.xml b/weixin-java-channel/src/test/resources/test-config.sample.xml new file mode 100644 index 0000000000..b84d8d9109 --- /dev/null +++ b/weixin-java-channel/src/test/resources/test-config.sample.xml @@ -0,0 +1,10 @@ + + JSON或者XML + appid + secret + Token + EncodingAESKey + false + 可以不填写 + 可以不填写 + diff --git a/weixin-java-channel/src/test/resources/testng.xml b/weixin-java-channel/src/test/resources/testng.xml new file mode 100644 index 0000000000..afa0aa32f2 --- /dev/null +++ b/weixin-java-channel/src/test/resources/testng.xml @@ -0,0 +1,11 @@ + + + + + + + + + diff --git a/weixin-java-channel/src/test/resources/tmp.png b/weixin-java-channel/src/test/resources/tmp.png new file mode 100644 index 0000000000..7c3a48838d Binary files /dev/null and b/weixin-java-channel/src/test/resources/tmp.png differ diff --git a/weixin-java-cloudbase/README.md b/weixin-java-cloudbase/README.md new file mode 100644 index 0000000000..495b8354e3 --- /dev/null +++ b/weixin-java-cloudbase/README.md @@ -0,0 +1,80 @@ +## 如何使用 `WxJava` 进行小程序云开发 + +[云开发(CloudBase)](https://www.cloudbase.net/)是基于Serverless架构构建的一站式后端云服务,涵盖函数、数据库、存储、CDN等服务,免后端运维,支持小程序、Web和APP开发。 +其中,小程序·云开发是微信和腾讯云联合推出的云端一体化解决方案,基于云开发可以免鉴权调用微信所有开放能力,在微信开发者工具中即可开通使用。 + +### 一、 引入`maven`依赖 + + +``` + + com.github.binarywang + weixin-java-miniapp + 3.7.1.B + +``` + + +### 二、 构造配置类,填入相关参数 + +``` + WxMaDefaultConfigImpl config = new WxMaDefaultConfigImpl(); + config.setAppid(...); // 微信小程序的appid + config.setSecret(...); // 微信小程序的Secret + config.setToken(...); // 微信小程序消息服务器配置的token,如果程序不涉及相关功能,可以忽略 + config.setAesKey(...); // 微信小程序消息服务器配置的EncodingAESKey,同上,如果不涉及,可以忽略 + config.setMsgDataFormat(...); // 消息数据格式,可以为XML或者JSON +``` + + +### 三、 构造service类,关联上述配置 + +``` + WxMaService wxMaService= new WxMaServiceImpl(); + + wxMaService.setWxMaConfig(config); +``` + +### 四、 根据小程序前端需要调用相应的方法: + +目前 `WxJava` 已支持当前所有接口(当然如果官方后续加入新接口,则还未在最新版本中实现,会考虑在之后版本中加入),所有已支持的接口列表可以参考在线`JavaDoc`:http://binary.ac.cn/weixin-java-miniapp-javadoc/cn/binarywang/wx/miniapp/api/WxMaCloudService.html + + +以触发云函数接口为例,可以采用如下方式调用 `invokeCloudFunction` 方法: + + +``` +String result = wxMaService.getCloudService().invokeCloudFunction("rcn", "login", "{}"); // 拿到result之后,可以在后续加入自己的处理逻辑代码 +``` + + +更多方法调用实例可以参考 `WxJava` 源码中的单元测试类:`cn.binarywang.wx.miniapp.api.impl.WxMaCloudServiceImplTest` + + +另外,顺便在此列出当前已支持云开发的方法如下: + +| 接口描述 | 方法名 | +| ---- | ---- | +| 删除文件 | batchDeleteFile(String env, String[] fileIds)| +| 获取文件下载链接 | batchDownloadFile(String env, String[] fileIds, long[] maxAges)| +| 数据库插入记录 | databaseAdd(String env, String query)| +| 数据库聚合记录 | databaseAggregate(String env, String query)| +| 新增集合 | databaseCollectionAdd(String env, String collectionName)| +| 删除集合 | databaseCollectionDelete(String env, String collectionName)| +| 获取特定云环境下集合信息 | databaseCollectionGet(String env, Long limit, Long offset)| +| 统计集合记录数或统计查询语句对应的结果记录数 | databaseCount(String env, String query)| +| 数据库删除记录 | databaseDelete(String env, String query)| +| 数据库导出 | databaseMigrateExport(String env, String filePath, int fileType, String query)| +| 数据库导入 | databaseMigrateImport(String env, String collectionName, String filePath, int fileType, boolean stopOnError, int conflictMode)| +| 数据库迁移状态查询 | databaseMigrateQueryInfo(String env, Long jobId)| +| 数据库查询记录 | databaseQuery(String env, String query)| +| 数据库更新记录 | databaseUpdate(String env, String query)| +| 获取腾讯云API调用凭证 | getQcloudToken(long lifeSpan)| +| 触发云函数 | invokeCloudFunction(String env, String name, String body)| +| 变更数据库索引 | updateIndex(String env, String collectionName, List createIndexes, List dropIndexNames)| +| 获取文件上传链接 | uploadFile(String env, String path)| + +### 五、 `Spring` 框架整合: + +可以参考https://github.com/binarywang/weixin-java-miniapp-demo 此项目整合 `Spring` 开发。 +此项目基于 `Spring Boot` ,如果需要支持`Spring MVC`项目,适当改造即可。 diff --git a/weixin-java-common/pom.xml b/weixin-java-common/pom.xml index 68c430f5c8..2053177b12 100644 --- a/weixin-java-common/pom.xml +++ b/weixin-java-common/pom.xml @@ -1,12 +1,12 @@ - + 4.0.0 com.github.binarywang wx-java - 3.6.0 + 4.8.0 weixin-java-common @@ -24,18 +24,17 @@ okhttp provided - + - org.slf4j - slf4j-api - - - com.thoughtworks.xstream - xstream + org.apache.httpcomponents.client5 + httpclient5 + + org.apache.httpcomponents httpclient + provided commons-logging @@ -46,11 +45,21 @@ org.apache.httpcomponents httpmime + provided + + + + org.slf4j + slf4j-api + + + com.thoughtworks.xstream + xstream org.slf4j jcl-over-slf4j - 1.7.24 + 1.7.36 @@ -77,6 +86,11 @@ org.projectlombok lombok + + org.mockito + mockito-core + test + ch.qos.logback @@ -88,11 +102,7 @@ testng test - - org.mockito - mockito-all - test - + com.google.inject guice @@ -116,7 +126,29 @@ org.dom4j dom4j - 2.1.1 + 2.1.3 + + + pull-parser + pull-parser + + + + + redis.clients + jedis + + + com.github.jedis-lock + jedis-lock + + + org.redisson + redisson + + + org.springframework.data + spring-data-redis @@ -134,4 +166,36 @@
+ + + native-image + + false + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.5.1 + + + com.github.binarywang.wx.graal.GraalProcessor,lombok.launch.AnnotationProcessorHider$AnnotationProcessor,lombok.launch.AnnotationProcessorHider$ClaimingProcessor + + + + com.github.binarywang + weixin-graal + ${project.version} + + + + + + + + + + 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 e63b4a8c6b..d7e8936e62 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/api/WxConsts.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/api/WxConsts.java @@ -1,17 +1,42 @@ package me.chanjar.weixin.common.api; +import lombok.experimental.UtilityClass; + +import java.util.Arrays; import java.util.HashMap; +import java.util.List; import java.util.Map; +import static me.chanjar.weixin.common.error.WxMpErrorMsgEnum.*; + /** * 微信开发所使用到的常量类. * - * @author Daniel Qian & binarywang + * @author Daniel Qian, binarywang, Wang_Wong */ +@UtilityClass public class WxConsts { + /** + * access_token 相关错误代码 + *
+   * 发生以下情况时尝试刷新access_token
+   * 40001 获取access_token时AppSecret错误,或者access_token无效
+   * 42001 access_token超时
+   * 40014 不合法的access_token,请开发者认真比对access_token的有效性(如是否过期),或查看是否正在为恰当的公众号调用接口
+   * 
+ */ + public static final List ACCESS_TOKEN_ERROR_CODES = Arrays.asList(CODE_40001.getCode(), + CODE_40014.getCode(), CODE_42001.getCode()); + + /** + * 微信接口返回的参数errcode. + */ + public static final String ERR_CODE = "errcode"; + /** * 微信推送过来的消息的类型,和发送给微信xml格式消息的消息类型. */ + @UtilityClass public static class XmlMsgType { public static final String TEXT = "text"; public static final String IMAGE = "image"; @@ -28,11 +53,14 @@ public static class XmlMsgType { public static final String DEVICE_STATUS = "device_status"; public static final String HARDWARE = "hardware"; public static final String TRANSFER_CUSTOMER_SERVICE = "transfer_customer_service"; + public static final String UPDATE_TASKCARD = "update_taskcard"; + public static final String UPDATE_BUTTON = "update_button"; } /** * 主动发送消息(即客服消息)的消息类型. */ + @UtilityClass public static class KefuMsgType { /** * 文本消息. @@ -103,11 +131,98 @@ public static class KefuMsgType { * 小程序通知消息. */ public static final String MINIPROGRAM_NOTICE = "miniprogram_notice"; + + /** + * 模板卡片消息. + */ + public static final String TEMPLATE_CARD = "template_card"; + + /** + * 发送图文消息(点击跳转到图文消息页面)使用通过 “发布” 系列接口得到的 article_id(草稿箱功能上线后不再支持客服接口中带 media_id 的 mpnews 类型的图文消息) + */ + public static final String MP_NEWS_ARTICLE = "mpnewsarticle"; + } + + /** + * 发送「学校通知」类型 + * https://developer.work.weixin.qq.com/document/path/92321 + */ + @UtilityClass + public static class SchoolContactMsgType { + + /** + * 文本消息. + */ + public static final String TEXT = "text"; + + /** + * 图片消息. + */ + public static final String IMAGE = "image"; + + /** + * 语音消息. + */ + public static final String VOICE = "voice"; + + /** + * 视频消息. + */ + public static final String VIDEO = "video"; + + /** + * 文件消息 + */ + public static final String FILE = "file"; + + /** + * 图文消息 + */ + public static final String NEWS = "news"; + + /** + * 图文消息(mpnews) + */ + public static final String MPNEWS = "mpnews"; + + /** + * 小程序消息 + */ + public static final String MINIPROGRAM = "miniprogram"; + + } + + /** + * 企业微信模板卡片消息的卡片类型 + */ + @UtilityClass + public static class TemplateCardType { + /** + * 文本通知型卡片 + */ + public static final String TEXT_NOTICE = "text_notice"; + /** + * 图文展示型卡片 + */ + public static final String NEWS_NOTICE = "news_notice"; + /** + * 按钮交互型卡片 + */ + public static final String BUTTON_INTERACTION = "button_interaction"; + /** + * 投票选择型卡片 + */ + public static final String VOTE_INTERACTION = "vote_interaction"; + /** + * 多项选择型卡片 + */ + public static final String MULTIPLE_INTERACTION = "multiple_interaction"; } /** * 表示是否是保密消息,0表示否,1表示是,默认0. */ + @UtilityClass public static class KefuMsgSafe { public static final String NO = "0"; public static final String YES = "1"; @@ -116,17 +231,20 @@ public static class KefuMsgSafe { /** * 群发消息的消息类型. */ + @UtilityClass public static class MassMsgType { public static final String MPNEWS = "mpnews"; public static final String TEXT = "text"; public static final String VOICE = "voice"; public static final String IMAGE = "image"; + public static final String IMAGES = "images"; public static final String MPVIDEO = "mpvideo"; } /** * 群发消息后微信端推送给服务器的反馈消息. */ + @UtilityClass public static class MassMsgStatus { public static final String SEND_SUCCESS = "send success"; public static final String SEND_FAIL = "send fail"; @@ -139,6 +257,12 @@ public static class MassMsgStatus { public static final String ERR_20013 = "err(20013)"; public static final String ERR_22000 = "err(22000)"; public static final String ERR_21000 = "err(21000)"; + public static final String ERR_30001 = "err(30001)"; + public static final String ERR_30002 = "err(30002)"; + public static final String ERR_30003 = "err(30003)"; + public static final String ERR_40001 = "err(40001)"; + public static final String ERR_40002 = "err(40002)"; + /** * 群发反馈消息代码所对应的文字描述. @@ -157,12 +281,18 @@ public static class MassMsgStatus { STATUS_DESC.put(ERR_20013, "涉嫌版权"); STATUS_DESC.put(ERR_22000, "涉嫌互推_互相宣传"); STATUS_DESC.put(ERR_21000, "涉嫌其他"); + STATUS_DESC.put(ERR_30001, "原创校验出现系统错误且用户选择了被判为转载就不群发"); + STATUS_DESC.put(ERR_30002, "原创校验被判定为不能群发"); + STATUS_DESC.put(ERR_30003, "原创校验被判定为转载文且用户选择了被判为转载就不群发"); + STATUS_DESC.put(ERR_40001, "管理员拒绝"); + STATUS_DESC.put(ERR_40002, "管理员30分钟内无响应,超时"); } } /** * 微信端推送过来的事件类型. */ + @UtilityClass public static class EventType { public static final String SUBSCRIBE = "subscribe"; public static final String UNSUBSCRIBE = "unsubscribe"; @@ -171,6 +301,8 @@ public static class EventType { public static final String CLICK = "CLICK"; 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"; /** * 扫码推事件的事件推送 */ @@ -196,7 +328,23 @@ public static class EventType { */ public static final String LOCATION_SELECT = "location_select"; + /** + * 授权用户资料变更事件 + * 1、 当部分用户的资料存在风险时,平台会对用户资料进行清理,并通过消息推送服务器通知最近30天授权过的公众号开发者,我们建议开发者留意响应该事件,及时主动更新或清理用户的头像及昵称,降低风险。 + * 2、 当用户撤回授权信息时,平台会通过消息推送服务器通知给公众号开发者,请开发者注意及时删除用户信息。 + */ + public static final String USER_INFO_MODIFIED = "user_info_modified"; + + /** + * 用户撤回授权事件 + */ + public static final String USER_AUTHORIZATION_REVOKE = "user_authorization_revoke"; + + /** + * 群发模板回调事件 + */ public static final String TEMPLATE_SEND_JOB_FINISH = "TEMPLATESENDJOBFINISH"; + /** * 微信小店 订单付款通知. */ @@ -222,6 +370,10 @@ public static class EventType { */ public static final String CARD_USER_GIFTING_CARD = "user_gifting_card"; + /** + * 异步安全校验事件 + */ + public static final String WXA_MEDIA_CHECK = "wxa_media_check"; /** * 卡券事件:用户核销卡券 @@ -280,6 +432,72 @@ public static class EventType { */ public static final String WEAPP_AUDIT_FAIL = "weapp_audit_fail"; + + /** + * 小程序审核事件:审核延后 + */ + public static final String WEAPP_AUDIT_DELAY = "weapp_audit_delay"; + + /** + * 小程序自定义交易组件支付通知 + */ + public static final String OPEN_PRODUCT_ORDER_PAY = "open_product_order_pay"; + /** + * 点击菜单跳转小程序的事件推送 + */ + public static final String VIEW_MINIPROGRAM = "view_miniprogram"; + + /** + * 订阅通知事件:用户操作订阅通知弹窗 + */ + public static final String SUBSCRIBE_MSG_POPUP_EVENT = "subscribe_msg_popup_event"; + + /** + * 订阅通知事件:用户管理订阅通知 + */ + public static final String SUBSCRIBE_MSG_CHANGE_EVENT = "subscribe_msg_change_event"; + + /** + * 订阅通知事件:发送订阅通知回调 + */ + public static final String SUBSCRIBE_MSG_SENT_EVENT = "subscribe_msg_sent_event"; + + /** + * 名称审核事件 + */ + 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_APPEAL_RECORD = "wxa_appeal_record"; + /** + * 隐私权限审核结果推送 + */ + public static final String WXA_PRIVACY_APPLY = "wxa_privacy_apply"; + /** + * 类目审核结果事件推送 + */ + 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_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"; } /** @@ -296,6 +514,7 @@ public static class MediaFileType { /** * 自定义菜单的按钮类型. */ + @UtilityClass public static class MenuButtonType { /** * 点击推事件. @@ -346,6 +565,7 @@ public static class MenuButtonType { /** * oauth2网页授权的scope. */ + @UtilityClass public static class OAuth2Scope { /** * 不弹出授权页面,直接跳转,只能获取用户openid. @@ -366,6 +586,7 @@ public static class OAuth2Scope { /** * 网页应用登录授权作用域. */ + @UtilityClass public static class QrConnectScope { public static final String SNSAPI_LOGIN = "snsapi_login"; } @@ -373,6 +594,7 @@ public static class QrConnectScope { /** * 永久素材类型. */ + @UtilityClass public static class MaterialType { public static final String NEWS = "news"; public static final String VOICE = "voice"; @@ -384,6 +606,7 @@ public static class MaterialType { /** * 网络检测入参. */ + @UtilityClass public static class NetCheckArgs { public static final String ACTIONDNS = "dns"; public static final String ACTIONPING = "ping"; @@ -394,5 +617,33 @@ public static class NetCheckArgs { public static final String OPERATORDEFAULT = "DEFAULT"; } + /** + * appId 类型 + */ + @UtilityClass + public static class AppIdType { + /** + * 公众号appId类型 + */ + public static final String MP_TYPE = "mp"; + /** + * 小程序appId类型 + */ + 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/api/WxMessageInMemoryDuplicateChecker.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/api/WxMessageInMemoryDuplicateChecker.java index d7ac36c7c6..88c3aeae69 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/api/WxMessageInMemoryDuplicateChecker.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/api/WxMessageInMemoryDuplicateChecker.java @@ -8,10 +8,12 @@ *
  * 默认消息重复检查器.
  * 将每个消息id保存在内存里,每隔5秒清理已经过期的消息id,每个消息id的过期时间是15秒
+ * 替换类WxMessageInMemoryDuplicateCheckerSingleton
  * 
* * @author Daniel Qian */ +@Deprecated public class WxMessageInMemoryDuplicateChecker implements WxMessageDuplicateChecker { /** @@ -61,23 +63,20 @@ protected void checkBackgroundProcessStarted() { if (this.backgroundProcessStarted.getAndSet(true)) { return; } - Thread t = new Thread(new Runnable() { - @Override - public void run() { - try { - while (true) { - Thread.sleep(WxMessageInMemoryDuplicateChecker.this.clearPeriod); - Long now = System.currentTimeMillis(); - for (Map.Entry entry : - WxMessageInMemoryDuplicateChecker.this.msgId2Timestamp.entrySet()) { - if (now - entry.getValue() > WxMessageInMemoryDuplicateChecker.this.timeToLive) { - WxMessageInMemoryDuplicateChecker.this.msgId2Timestamp.entrySet().remove(entry); - } + Thread t = new Thread(() -> { + try { + while (true) { + Thread.sleep(WxMessageInMemoryDuplicateChecker.this.clearPeriod); + Long now = System.currentTimeMillis(); + for (Map.Entry entry : + WxMessageInMemoryDuplicateChecker.this.msgId2Timestamp.entrySet()) { + if (now - entry.getValue() > WxMessageInMemoryDuplicateChecker.this.timeToLive) { + WxMessageInMemoryDuplicateChecker.this.msgId2Timestamp.entrySet().remove(entry); } } - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); } + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); } }); t.setDaemon(true); diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/api/WxMessageInMemoryDuplicateCheckerSingleton.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/api/WxMessageInMemoryDuplicateCheckerSingleton.java new file mode 100644 index 0000000000..befd367fe0 --- /dev/null +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/api/WxMessageInMemoryDuplicateCheckerSingleton.java @@ -0,0 +1,91 @@ +package me.chanjar.weixin.common.api; + +import com.google.common.util.concurrent.ThreadFactoryBuilder; +import lombok.extern.slf4j.Slf4j; + +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +/** + * @author jiangby + * @version 1.0 + *

+ * 消息去重,记录消息ID首次出现时的时间戳, + * 15S后定时任务触发时废除该记录消息ID + *

+ * created on 2022/5/26 1:32 + */ +@Slf4j +public class WxMessageInMemoryDuplicateCheckerSingleton implements WxMessageDuplicateChecker { + + /** + * 一个消息ID在内存的过期时间:15秒. + */ + private static final Long TIME_TO_LIVE = 15L; + + /** + * 每隔多少周期检查消息ID是否过期:5秒. + */ + private static final Long CLEAR_PERIOD = 5L; + + /** + * 线程池 + */ + private static final ScheduledThreadPoolExecutor SCHEDULED_THREAD_POOL_EXECUTOR = new ScheduledThreadPoolExecutor(1, + new ThreadFactoryBuilder().setNameFormat("wxMessage-memory-pool-%d").setDaemon(true).build(), new ThreadPoolExecutor.AbortPolicy()); + + /** + * 消息id->消息时间戳的map. + */ + private static final ConcurrentHashMap MSG_ID_2_TIMESTAMP = new ConcurrentHashMap<>(); + + static { + SCHEDULED_THREAD_POOL_EXECUTOR.scheduleAtFixedRate(() -> { + try { + Long now = System.currentTimeMillis(); + MSG_ID_2_TIMESTAMP.entrySet().removeIf(entry -> now - entry.getValue() > TIME_TO_LIVE * 1000); + } catch (Exception ex) { + log.error("重复消息去重任务出现异常", ex); + } + }, 1, CLEAR_PERIOD, TimeUnit.SECONDS); + } + + /** + * 私有化构造方法,避免外部调用 + */ + private WxMessageInMemoryDuplicateCheckerSingleton() { + } + + /** + * 获取单例 + * + * @return 单例对象 + */ + public static WxMessageInMemoryDuplicateCheckerSingleton getInstance() { + return WxMessageInnerClass.CHECKER_SINGLETON; + } + + /** + * 内部类实现单例 + */ + private static class WxMessageInnerClass { + static final WxMessageInMemoryDuplicateCheckerSingleton CHECKER_SINGLETON = new WxMessageInMemoryDuplicateCheckerSingleton(); + } + + /** + * messageId是否重复 + * + * @param messageId messageId + * @return 是否 + */ + @Override + public boolean isDuplicate(String messageId) { + if (messageId == null) { + return false; + } + Long timestamp = MSG_ID_2_TIMESTAMP.putIfAbsent(messageId, System.currentTimeMillis()); + return timestamp != null; + } +} diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/api/WxMessageInRedisDuplicateChecker.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/api/WxMessageInRedisDuplicateChecker.java new file mode 100644 index 0000000000..88c5e9a4e5 --- /dev/null +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/api/WxMessageInRedisDuplicateChecker.java @@ -0,0 +1,47 @@ +package me.chanjar.weixin.common.api; + +import lombok.RequiredArgsConstructor; +import org.redisson.api.RBucket; +import org.redisson.api.RedissonClient; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.concurrent.TimeUnit; + +/** + * 利用redis检查消息是否重复 + * + */ +@RequiredArgsConstructor +public class WxMessageInRedisDuplicateChecker implements WxMessageDuplicateChecker { + + /** + * 过期时间 + */ + private int expire = 10; + + private final Logger log = LoggerFactory.getLogger(getClass()); + + private final RedissonClient redissonClient; + + /** + * messageId是否重复 + * + * @param messageId messageId + * @return 是否 + */ + @Override + public boolean isDuplicate(String messageId) { + RBucket r = redissonClient.getBucket("wx:message:duplicate:check:" + messageId); + boolean setSuccess = r.trySet("1", expire, TimeUnit.SECONDS); + return !setSuccess; + } + + public int getExpire() { + return expire; + } + + public void setExpire(int expire) { + this.expire = expire; + } +} diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/CommonUploadData.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/CommonUploadData.java new file mode 100644 index 0000000000..ea76137f6b --- /dev/null +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/CommonUploadData.java @@ -0,0 +1,76 @@ +package me.chanjar.weixin.common.bean; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; +import org.jetbrains.annotations.NotNull; +import org.springframework.lang.Nullable; + +import java.io.*; +import java.nio.file.Files; + +/** + * 通用文件上传数据 + * + * @author 广州跨界 + * created on 2024/01/11 + */ +@Slf4j +@Data +@NoArgsConstructor +@AllArgsConstructor +public class CommonUploadData implements Serializable { + + /** + * 文件名,如:1.jpg + */ + @Nullable + private String fileName; + + /** + * 文件内容 + * + * @see FileInputStream 文件输入流 + * @see ByteArrayInputStream 字节输入流 + */ + @NotNull + private InputStream inputStream; + + /** + * 文件内容长度(字节数) + */ + private long length; + + /** + * 从文件构造 + * + * @param file 文件 + * @return 通用文件上传数据 + */ + @SneakyThrows + public static CommonUploadData fromFile(File file) { + return new CommonUploadData(file.getName(), Files.newInputStream(file.toPath()), file.length()); + } + + + /** + * 读取所有字节,此方法会关闭输入流 + * + * @return 字节数组 + */ + @SneakyThrows + public byte[] readAllBytes() { + byte[] bytes = new byte[(int) length]; + //noinspection ResultOfMethodCallIgnored + inputStream.read(bytes); + inputStream.close(); + return bytes; + } + + @Override + public String toString() { + return String.format("{fileName:%s, length:%s}", fileName, length); + } +} diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/CommonUploadParam.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/CommonUploadParam.java new file mode 100644 index 0000000000..3a9872fc92 --- /dev/null +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/CommonUploadParam.java @@ -0,0 +1,65 @@ +package me.chanjar.weixin.common.bean; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.SneakyThrows; +import org.jetbrains.annotations.NotNull; +import org.springframework.lang.Nullable; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.Serializable; + +/** + * 通用文件上传参数 + * + * @author 广州跨界 + * created on 2024/01/11 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class CommonUploadParam implements Serializable { + + /** + * 文件对应的接口参数名称(非文件名),如:media + */ + @NotNull + private String name; + + /** + * 上传数据 + */ + @NotNull + private CommonUploadData data; + + /** + * 从文件构造 + * + * @param name 参数名,如:media + * @param file 文件 + * @return 文件上传参数对象 + */ + @SneakyThrows + public static CommonUploadParam fromFile(String name, File file) { + return new CommonUploadParam(name, CommonUploadData.fromFile(file)); + } + + /** + * 从字节数组构造 + * + * @param name 参数名,如:media + * @param bytes 字节数组 + * @return 文件上传参数对象 + */ + @SneakyThrows + public static CommonUploadParam fromBytes(String name, @Nullable String fileName, byte[] bytes) { + return new CommonUploadParam(name, new CommonUploadData(fileName, new ByteArrayInputStream(bytes), bytes.length)); + } + + @Override + public String toString() { + return String.format("{name:%s, data:%s}", name, data); + } +} diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/ToJson.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/ToJson.java new file mode 100644 index 0000000000..6f10e60b71 --- /dev/null +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/ToJson.java @@ -0,0 +1,15 @@ +package me.chanjar.weixin.common.bean; + +/** + * 包含toJson()方法的接口. + * + * @author Binary Wang + * created on 2020-10-05 + */ +public interface ToJson { + /** + * 转换为json字符串 + * @return json字符串 + */ + String toJson(); +} diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/WxAccessTokenEntity.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/WxAccessTokenEntity.java new file mode 100644 index 0000000000..fe19817b2b --- /dev/null +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/WxAccessTokenEntity.java @@ -0,0 +1,19 @@ +package me.chanjar.weixin.common.bean; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; + +/** + * token + * + * @author cn + */ +@Getter +@Setter +@ToString(callSuper = true) +@EqualsAndHashCode(callSuper = true) +public class WxAccessTokenEntity extends WxAccessToken { + private String appid; +} diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/WxNetCheckResult.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/WxNetCheckResult.java index 2df2417f57..b5f5762cb3 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/WxNetCheckResult.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/WxNetCheckResult.java @@ -13,7 +13,6 @@ */ @Data public class WxNetCheckResult implements Serializable { - private static final long serialVersionUID = 6918924418847404172L; private List dnsInfos = new ArrayList<>(); diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/WxOAuth2UserInfo.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/WxOAuth2UserInfo.java new file mode 100644 index 0000000000..906e9de2b1 --- /dev/null +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/WxOAuth2UserInfo.java @@ -0,0 +1,66 @@ +package me.chanjar.weixin.common.bean; + + +import com.google.gson.annotations.SerializedName; +import lombok.Data; +import me.chanjar.weixin.common.util.json.WxGsonBuilder; + +import java.io.Serializable; + +/** + * oauth2用户个人信息. + * + * @author Binary Wang + * created on 2020-10-11 + */ +@Data +public class WxOAuth2UserInfo implements Serializable { + private static final long serialVersionUID = 3181943506448954725L; + + /** + * openid 普通用户的标识,对当前开发者帐号唯一 + */ + private String openid; + /** + * nickname 普通用户昵称 + */ + private String nickname; + /** + * sex 普通用户性别,1为男性,2为女性 + */ + private Integer sex; + /** + * city 普通用户个人资料填写的城市 + */ + private String city; + + /** + * province 普通用户个人资料填写的省份 + */ + private String province; + /** + * country 国家,如中国为CN + */ + private String country; + /** + * headimgurl 用户头像,最后一个数值代表正方形头像大小(有0、46、64、96、132数值可选,0代表640*640正方形头像), + * 用户没有头像时该项为空 + */ + @SerializedName("headimgurl") + private String headImgUrl; + /** + * unionid 用户统一标识。针对一个微信开放平台帐号下的应用,同一用户的unionid是唯一的。 + */ + @SerializedName("unionid") + private String unionId; + /** + * privilege 用户特权信息,json数组,如微信沃卡用户为(chinaunicom) + */ + @SerializedName("privilege") + private String[] privileges; + + + public static WxOAuth2UserInfo fromJson(String json) { + return WxGsonBuilder.create().fromJson(json, WxOAuth2UserInfo.class); + } +} diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/imgproc/WxImgProcAiCropResult.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/imgproc/WxImgProcAiCropResult.java new file mode 100644 index 0000000000..4cfc514d70 --- /dev/null +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/imgproc/WxImgProcAiCropResult.java @@ -0,0 +1,69 @@ +package me.chanjar.weixin.common.bean.imgproc; + +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; + +/** + * @author Theo Nie + */ +@Data +public class WxImgProcAiCropResult implements Serializable { + private static final long serialVersionUID = -6470673963772979463L; + + @SerializedName("img_size") + private ImgSize imgSize; + + @SerializedName("results") + private List results; + + @Override + public String toString() { + return WxGsonBuilder.create().toJson(this); + } + + public static WxImgProcAiCropResult fromJson(String json) { + return WxGsonBuilder.create().fromJson(json, WxImgProcAiCropResult.class); + } + + @Data + public static class ImgSize implements Serializable { + private static final long serialVersionUID = -6470673963772979463L; + + @SerializedName("w") + private int w; + + @SerializedName("h") + private int h; + + @Override + public String toString() { + return WxGsonBuilder.create().toJson(this); + } + } + + @Data + public static class Results implements Serializable { + private static final long serialVersionUID = -6470673963772979463L; + + @SerializedName("crop_left") + private int cropLeft; + + @SerializedName("crop_top") + private int cropTop; + + @SerializedName("crop_right") + private int cropRight; + + @SerializedName("crop_bottom") + private int cropBottom; + + @Override + public String toString() { + return WxGsonBuilder.create().toJson(this); + } + } +} diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/imgproc/WxImgProcQrCodeResult.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/imgproc/WxImgProcQrCodeResult.java new file mode 100644 index 0000000000..c257146092 --- /dev/null +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/imgproc/WxImgProcQrCodeResult.java @@ -0,0 +1,103 @@ +package me.chanjar.weixin.common.bean.imgproc; + +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; + +/** + * 二维码/条码识别返回结果 + * + * @author Theo Nie + */ +@Data +public class WxImgProcQrCodeResult implements Serializable { + private static final long serialVersionUID = -1194154790100866123L; + + @SerializedName("img_size") + private ImgSize imgSize; + + @SerializedName("code_results") + private List codeResults; + + @Data + public static class ImgSize implements Serializable { + private static final long serialVersionUID = -8847603245514017839L; + + @SerializedName("w") + private int w; + @SerializedName("h") + private int h; + + @Override + public String toString() { + return WxGsonBuilder.create().toJson(this); + } + } + + @Data + public static class CodeResults implements Serializable { + private static final long serialVersionUID = -6138135951229076759L; + + @SerializedName("type_name") + private String typeName; + + @SerializedName("data") + private String data; + + @SerializedName("pos") + private Pos pos; + + @Override + public String toString() { + return WxGsonBuilder.create().toJson(this); + } + + @Data + public static class Pos implements Serializable { + private static final long serialVersionUID = 7754894061212819602L; + @SerializedName("left_top") + private Coordinate leftTop; + + @SerializedName("right_top") + private Coordinate rightTop; + + @SerializedName("right_bottom") + private Coordinate rightBottom; + + @SerializedName("left_bottom") + private Coordinate leftBottom; + + @Override + public String toString() { + return WxGsonBuilder.create().toJson(this); + } + + @Data + public static class Coordinate implements Serializable { + private static final long serialVersionUID = 8930443668927359677L; + @SerializedName("x") + private int x; + + @SerializedName("y") + private int y; + + @Override + public String toString() { + return WxGsonBuilder.create().toJson(this); + } + } + } + } + + public static WxImgProcQrCodeResult fromJson(String json) { + return WxGsonBuilder.create().fromJson(json, WxImgProcQrCodeResult.class); + } + + @Override + public String toString() { + return WxGsonBuilder.create().toJson(this); + } +} diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/imgproc/WxImgProcSuperResolutionResult.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/imgproc/WxImgProcSuperResolutionResult.java new file mode 100644 index 0000000000..2ce5d3829f --- /dev/null +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/imgproc/WxImgProcSuperResolutionResult.java @@ -0,0 +1,28 @@ +package me.chanjar.weixin.common.bean.imgproc; + +import com.google.gson.annotations.SerializedName; +import lombok.Data; +import me.chanjar.weixin.common.util.json.WxGsonBuilder; + +import java.io.Serializable; + +/** + * 图片高清化返回结果 + * @author Theo Nie + */ +@Data +public class WxImgProcSuperResolutionResult implements Serializable { + private static final long serialVersionUID = 8007440280170407021L; + + @SerializedName("media_id") + private String mediaId; + + @Override + public String toString() { + return WxGsonBuilder.create().toJson(this); + } + + public static WxImgProcSuperResolutionResult fromJson(String json) { + return WxGsonBuilder.create().fromJson(json, WxImgProcSuperResolutionResult.class); + } +} diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/menu/WxMenuButton.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/menu/WxMenuButton.java index 114e267d41..344f544bad 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/menu/WxMenuButton.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/menu/WxMenuButton.java @@ -58,6 +58,15 @@ public class WxMenuButton implements Serializable { @SerializedName("media_id") private String mediaId; + /** + *
+   * 调用发布图文接口获得的article_id.
+   * article_id类型和article_view_limited类型必须
+   * 
+ */ + @SerializedName("article_id") + private String articleId; + /** *
    * 小程序的appid.
diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/menu/WxMenuRule.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/menu/WxMenuRule.java
index 49d4e891c4..437b4ba9fe 100644
--- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/menu/WxMenuRule.java
+++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/menu/WxMenuRule.java
@@ -24,6 +24,7 @@ public class WxMenuRule implements Serializable {
   private String country;
   private String province;
   private String city;
+  @SerializedName("client_platform_type")
   private String clientPlatformType;
   private String language;
 
diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/oauth2/WxOAuth2AccessToken.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/oauth2/WxOAuth2AccessToken.java
new file mode 100644
index 0000000000..b339844ad6
--- /dev/null
+++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/oauth2/WxOAuth2AccessToken.java
@@ -0,0 +1,58 @@
+package me.chanjar.weixin.common.bean.oauth2;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+import me.chanjar.weixin.common.util.json.WxGsonBuilder;
+
+import java.io.Serializable;
+
+/**
+ * OAuth2 AccessToken
+ * 

+ * 参考:{@code https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140842} + *

+ * + * @author Daniel Qian + */ +@Data +public class WxOAuth2AccessToken implements Serializable { + private static final long serialVersionUID = -1345910558078620805L; + + @SerializedName("access_token") + private String accessToken; + + @SerializedName("expires_in") + private int expiresIn = -1; + + @SerializedName("refresh_token") + private String refreshToken; + + @SerializedName("openid") + private String openId; + + @SerializedName("scope") + private String scope; + /** + * 是否为快照页模式虚拟账号,只有当用户是快照页模式虚拟账号时返回,值为1 + */ + @SerializedName("is_snapshotuser") + private Integer snapshotUser; + + /** + * 本接口在scope参数为snsapi_base时不再提供unionID字段。 + *

+ * 参考:{@code https://mp.weixin.qq.com/cgi-bin/announce?action=getannouncement&announce_id=11513156443eZYea&version=&lang=zh_CN} + *

+ */ + @SerializedName("unionid") + private String unionId; + + public static WxOAuth2AccessToken fromJson(String json) { + return WxGsonBuilder.create().fromJson(json, WxOAuth2AccessToken.class); + } + + @Override + public String toString() { + return WxGsonBuilder.create().toJson(this); + } +} diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/ocr/WxOcrBankCardResult.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/ocr/WxOcrBankCardResult.java new file mode 100644 index 0000000000..d5ff0b901d --- /dev/null +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/ocr/WxOcrBankCardResult.java @@ -0,0 +1,29 @@ +package me.chanjar.weixin.common.bean.ocr; + +import com.google.gson.annotations.SerializedName; +import lombok.Data; +import me.chanjar.weixin.common.util.json.WxGsonBuilder; + +import java.io.Serializable; + +/** + * 银行卡OCR识别结果 + * + * @author Theo Nie + */ +@Data +public class WxOcrBankCardResult implements Serializable { + + private static final long serialVersionUID = 554136620394204143L; + @SerializedName("number") + private String number; + + @Override + public String toString() { + return WxGsonBuilder.create().toJson(this); + } + + public static WxOcrBankCardResult fromJson(String json) { + return WxGsonBuilder.create().fromJson(json, WxOcrBankCardResult.class); + } +} diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/ocr/WxOcrBizLicenseResult.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/ocr/WxOcrBizLicenseResult.java new file mode 100644 index 0000000000..2e83443e95 --- /dev/null +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/ocr/WxOcrBizLicenseResult.java @@ -0,0 +1,108 @@ +package me.chanjar.weixin.common.bean.ocr; + +import com.google.gson.annotations.SerializedName; +import lombok.Data; +import me.chanjar.weixin.common.util.json.WxGsonBuilder; + +import java.io.Serializable; + +/** + * @author Theo Nie + */ +@Data +public class WxOcrBizLicenseResult implements Serializable { + private static final long serialVersionUID = -5007671093920178291L; + + /** + * 注册号 + */ + @SerializedName("reg_num") + private String regNum; + /** + * 编号 + */ + @SerializedName("serial") + private String serial; + /** + * 法定代表人姓名 + */ + @SerializedName("legal_representative") + private String legalRepresentative; + /** + * 企业名称 + */ + @SerializedName("enterprise_name") + private String enterpriseName; + /** + * 组成形式 + */ + @SerializedName("type_of_organization") + private String typeOfOrganization; + /** + * 经营场所/企业住所 + */ + @SerializedName("address") + private String address; + /** + * 公司类型 + */ + @SerializedName("type_of_enterprise") + private String typeOfEnterprise; + /** + * 经营范围 + */ + @SerializedName("business_scope") + private String businessScope; + /** + * 注册资本 + */ + @SerializedName("registered_capital") + private String registeredCapital; + /** + * 实收资本 + */ + @SerializedName("paid_in_capital") + private String paidInCapital; + /** + * 营业期限 + */ + @SerializedName("valid_period") + private String validPeriod; + /** + * 注册日期/成立日期 + */ + @SerializedName("registered_date") + private String registeredDate; + /** + * 营业执照位置 + */ + @SerializedName("cert_position") + private CertPosition certPosition; + /** + * 图片大小 + */ + @SerializedName("img_size") + private WxOcrImgSize imgSize; + + public static WxOcrBizLicenseResult fromJson(String json) { + return WxGsonBuilder.create().fromJson(json, WxOcrBizLicenseResult.class); + } + + @Override + public String toString() { + return WxGsonBuilder.create().toJson(this); + } + + @Data + public static class CertPosition implements Serializable { + private static final long serialVersionUID = 290286813344131863L; + + @SerializedName("pos") + private WxOcrPos pos; + + @Override + public String toString() { + return WxGsonBuilder.create().toJson(this); + } + } +} diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/ocr/WxOcrCommResult.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/ocr/WxOcrCommResult.java new file mode 100644 index 0000000000..5f56d16e3c --- /dev/null +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/ocr/WxOcrCommResult.java @@ -0,0 +1,45 @@ +package me.chanjar.weixin.common.bean.ocr; + +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; + +/** + * @author Theo Nie + */ +@Data +public class WxOcrCommResult implements Serializable { + private static final long serialVersionUID = 455833771627756440L; + + @SerializedName("img_size") + private WxOcrImgSize imgSize; + @SerializedName("items") + private List items; + + public static WxOcrCommResult fromJson(String json) { + return WxGsonBuilder.create().fromJson(json, WxOcrCommResult.class); + } + + @Override + public String toString() { + return WxGsonBuilder.create().toJson(this); + } + + @Data + public static class Items implements Serializable { + private static final long serialVersionUID = 3066181677009102791L; + + @SerializedName("text") + private String text; + @SerializedName("pos") + private WxOcrPos pos; + + @Override + public String toString() { + return WxGsonBuilder.create().toJson(this); + } + } +} diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/ocr/WxOcrDrivingLicenseResult.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/ocr/WxOcrDrivingLicenseResult.java new file mode 100644 index 0000000000..c9306200de --- /dev/null +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/ocr/WxOcrDrivingLicenseResult.java @@ -0,0 +1,80 @@ +package me.chanjar.weixin.common.bean.ocr; + +import com.google.gson.annotations.SerializedName; +import lombok.Data; +import me.chanjar.weixin.common.util.json.WxGsonBuilder; + +import java.io.Serializable; + +/** + * @author Theo Nie + */ +@Data +public class WxOcrDrivingLicenseResult implements Serializable { + private static final long serialVersionUID = -6984670645802585738L; + + /** + * 证号 + */ + @SerializedName("id_num") + private String idNum; + /** + * 姓名 + */ + @SerializedName("name") + private String name; + /** + * 性别 + */ + @SerializedName("sex") + private String sex; + /** + * 国籍 + */ + @SerializedName("nationality") + private String nationality; + /** + * 住址 + */ + @SerializedName("address") + private String address; + /** + * 出生日期 + */ + @SerializedName("birth_date") + private String birthDate; + /** + * 初次领证日期 + */ + @SerializedName("issue_date") + private String issueDate; + /** + * 准驾车型 + */ + @SerializedName("car_class") + private String carClass; + /** + * 有效期限起始日 + */ + @SerializedName("valid_from") + private String validFrom; + /** + * 有效期限终止日 + */ + @SerializedName("valid_to") + private String validTo; + /** + * 印章文字 + */ + @SerializedName("official_seal") + private String officialSeal; + + @Override + public String toString() { + return WxGsonBuilder.create().toJson(this); + } + + public static WxOcrDrivingLicenseResult fromJson(String json) { + return WxGsonBuilder.create().fromJson(json, WxOcrDrivingLicenseResult.class); + } +} diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/ocr/WxOcrDrivingResult.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/ocr/WxOcrDrivingResult.java new file mode 100644 index 0000000000..b486baf1c4 --- /dev/null +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/ocr/WxOcrDrivingResult.java @@ -0,0 +1,133 @@ +package me.chanjar.weixin.common.bean.ocr; + +import com.google.gson.annotations.SerializedName; +import lombok.Data; +import me.chanjar.weixin.common.util.json.WxGsonBuilder; + +import java.io.Serializable; + +/** + * @author Theo Nie + */ +@Data +public class WxOcrDrivingResult implements Serializable { + private static final long serialVersionUID = -7477484374200211303L; + + /** + * 车牌号码 + */ + @SerializedName("plate_num") + private String plateNum; + /** + * 车辆类型 + */ + @SerializedName("vehicle_type") + private String vehicleType; + /** + * 所有人 + */ + @SerializedName("owner") + private String owner; + /** + * 住址 + */ + @SerializedName("addr") + private String addr; + /** + * 使用性质 + */ + @SerializedName("use_character") + private String useCharacter; + /** + * 品牌型号 + */ + @SerializedName("model") + private String model; + /** + * 车辆识别代码 + */ + @SerializedName("vin") + private String vin; + /** + * 发动机号码 + */ + @SerializedName("engine_num") + private String engineNum; + /** + * 注册日期 + */ + @SerializedName("register_date") + private String registerDate; + /** + * 发证日期 + */ + @SerializedName("issue_date") + private String issueDate; + /** + * 车牌号码 + */ + @SerializedName("plate_num_b") + private String plateNumB; + /** + * 号牌 + */ + @SerializedName("record") + private String record; + /** + * 核定载人数 + */ + @SerializedName("passengers_num") + private String passengersNum; + /** + * 总质量 + */ + @SerializedName("total_quality") + private String totalQuality; + /** + * 整备质量 + */ + @SerializedName("prepare_quality") + private String prepareQuality; + /** + * 外廓尺寸 + */ + @SerializedName("overall_size") + private String overallSize; + /** + * 卡片正面位置(检测到卡片正面才会返回) + */ + @SerializedName("card_position_front") + private CardPosition cardPositionFront; + /** + * 卡片反面位置(检测到卡片反面才会返回) + */ + @SerializedName("card_position_back") + private CardPosition cardPositionBack; + /** + * 图片大小 + */ + @SerializedName("img_size") + private WxOcrImgSize imgSize; + + @Data + public static class CardPosition implements Serializable { + private static final long serialVersionUID = 2884515165228160517L; + + @SerializedName("pos") + private WxOcrPos pos; + + @Override + public String toString() { + return WxGsonBuilder.create().toJson(this); + } + } + + public static WxOcrDrivingResult fromJson(String json) { + return WxGsonBuilder.create().fromJson(json, WxOcrDrivingResult.class); + } + + @Override + public String toString() { + return WxGsonBuilder.create().toJson(this); + } +} diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/ocr/WxOcrIdCardResult.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/ocr/WxOcrIdCardResult.java new file mode 100644 index 0000000000..a50bd96e55 --- /dev/null +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/ocr/WxOcrIdCardResult.java @@ -0,0 +1,38 @@ +package me.chanjar.weixin.common.bean.ocr; + +import com.google.gson.annotations.SerializedName; +import lombok.Data; +import me.chanjar.weixin.common.util.json.WxGsonBuilder; + +import java.io.Serializable; + +/** + * OCR身份证识别结果. + * + * @author Binary Wang + * created on 2019-06-23 + */ +@Data +public class WxOcrIdCardResult implements Serializable { + private static final long serialVersionUID = 8184352486986729980L; + + @SerializedName("type") + private String type; + @SerializedName("name") + private String name; + @SerializedName("id") + private String id; + @SerializedName("addr") + private String addr; + @SerializedName("gender") + private String gender; + @SerializedName("nationality") + private String nationality; + @SerializedName("valid_date") + private String validDate; + + public static WxOcrIdCardResult fromJson(String json) { + return WxGsonBuilder.create().fromJson(json, WxOcrIdCardResult.class); + } + +} diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/ocr/WxOcrImgSize.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/ocr/WxOcrImgSize.java new file mode 100644 index 0000000000..f5446ab405 --- /dev/null +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/ocr/WxOcrImgSize.java @@ -0,0 +1,25 @@ +package me.chanjar.weixin.common.bean.ocr; + +import com.google.gson.annotations.SerializedName; +import lombok.Data; +import me.chanjar.weixin.common.util.json.WxGsonBuilder; + +import java.io.Serializable; + +/** + * @author Theo Nie + */ +@Data +public class WxOcrImgSize implements Serializable { + private static final long serialVersionUID = 5234409123551074168L; + + @SerializedName("w") + private int w; + @SerializedName("h") + private int h; + + @Override + public String toString() { + return WxGsonBuilder.create().toJson(this); + } +} diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/ocr/WxOcrPos.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/ocr/WxOcrPos.java new file mode 100644 index 0000000000..54089f3235 --- /dev/null +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/ocr/WxOcrPos.java @@ -0,0 +1,43 @@ +package me.chanjar.weixin.common.bean.ocr; + +import com.google.gson.annotations.SerializedName; +import lombok.Data; +import me.chanjar.weixin.common.util.json.WxGsonBuilder; + +import java.io.Serializable; + +/** + * @author Theo Nie + */ +@Data +public class WxOcrPos implements Serializable { + private static final long serialVersionUID = 4204160206873907920L; + + @SerializedName("left_top") + private Coordinate leftTop; + @SerializedName("right_top") + private Coordinate rightTop; + @SerializedName("right_bottom") + private Coordinate rightBottom; + @SerializedName("left_bottom") + private Coordinate leftBottom; + + @Override + public String toString() { + return WxGsonBuilder.create().toJson(this); + } + + @Data + public static class Coordinate implements Serializable { + private static final long serialVersionUID = 8675059935386304399L; + @SerializedName("x") + private int x; + @SerializedName("y") + private int y; + + @Override + public String toString() { + return WxGsonBuilder.create().toJson(this); + } + } +} 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 new file mode 100644 index 0000000000..5427d5cada --- /dev/null +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/result/WxMinishopImageUploadCustomizeResult.java @@ -0,0 +1,40 @@ +package me.chanjar.weixin.common.bean.result; + +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import lombok.Data; +import me.chanjar.weixin.common.api.WxConsts; +import me.chanjar.weixin.common.util.json.WxGsonBuilder; + +import java.io.Serializable; + +@Data +public class WxMinishopImageUploadCustomizeResult implements Serializable { + private String errcode; + private String errmsg; + + private WxMinishopPicFileCustomizeResult imgInfo; + + public static WxMinishopImageUploadCustomizeResult fromJson(String json) { + JsonObject jsonObject = JsonParser.parseString(json).getAsJsonObject(); + WxMinishopImageUploadCustomizeResult result = new WxMinishopImageUploadCustomizeResult(); + result.setErrcode(jsonObject.get(WxConsts.ERR_CODE).getAsNumber().toString()); + if (result.getErrcode().equals("0")) { + WxMinishopPicFileCustomizeResult picFileResult = new WxMinishopPicFileCustomizeResult(); + JsonObject picObject = jsonObject.get("img_info").getAsJsonObject(); + if (picObject.has("media_id")) { + picFileResult.setMediaId(picObject.get("media_id").getAsString()); + } + if (picObject.has("temp_img_url")) { + picFileResult.setTempImgUrl(picObject.get("temp_img_url").getAsString()); + } + result.setImgInfo(picFileResult); + } + return result; + } + + @Override + public String toString() { + return WxGsonBuilder.create().toJson(this); + } +} 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 new file mode 100644 index 0000000000..9c2cbaf3ba --- /dev/null +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/result/WxMinishopImageUploadResult.java @@ -0,0 +1,46 @@ +package me.chanjar.weixin.common.bean.result; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import lombok.Data; +import me.chanjar.weixin.common.api.WxConsts; +import me.chanjar.weixin.common.util.json.WxGsonBuilder; + +import java.io.Serializable; + +@Data +public class WxMinishopImageUploadResult implements Serializable { + private static final long serialVersionUID = 330834334738622332L; + + private String errcode; + private String errmsg; + + + private WxMinishopPicFileResult picFile; + + + public static WxMinishopImageUploadResult fromJson(String json) { + JsonObject jsonObject = JsonParser.parseString(json).getAsJsonObject(); + WxMinishopImageUploadResult result = new WxMinishopImageUploadResult(); + result.setErrcode(jsonObject.get(WxConsts.ERR_CODE).getAsNumber().toString()); + if (result.getErrcode().equals("0")) { + WxMinishopPicFileResult picFileResult = new WxMinishopPicFileResult(); + JsonObject picObject = jsonObject.get("pic_file").getAsJsonObject(); + JsonElement mediaId = picObject.get("media_id"); + picFileResult.setMediaId(mediaId==null ? "" : mediaId.getAsString()); + JsonElement payMediaId = picObject.get("pay_media_id"); + picFileResult.setPayMediaId(payMediaId==null ? "" : payMediaId.getAsString()); + JsonElement tempImgUrl = picObject.get("temp_img_url"); + picFileResult.setTempImgUrl(tempImgUrl==null ? "" : tempImgUrl.getAsString()); + result.setPicFile(picFileResult); + + } + return result; + } + + @Override + public String toString() { + return WxGsonBuilder.create().toJson(this); + } +} diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/result/WxMinishopPicFileCustomizeResult.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/result/WxMinishopPicFileCustomizeResult.java new file mode 100644 index 0000000000..8f2f36f8dd --- /dev/null +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/result/WxMinishopPicFileCustomizeResult.java @@ -0,0 +1,11 @@ +package me.chanjar.weixin.common.bean.result; + +import lombok.Data; + +import java.io.Serializable; + +@Data +public class WxMinishopPicFileCustomizeResult implements Serializable { + private String mediaId; + private String tempImgUrl; +} diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/result/WxMinishopPicFileResult.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/result/WxMinishopPicFileResult.java new file mode 100644 index 0000000000..2ae2e2320b --- /dev/null +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/result/WxMinishopPicFileResult.java @@ -0,0 +1,12 @@ +package me.chanjar.weixin.common.bean.result; + +import lombok.Data; + +import java.io.Serializable; + +@Data +public class WxMinishopPicFileResult implements Serializable { + private String mediaId; + private String payMediaId; + private String tempImgUrl; +} diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/subscribemsg/CategoryData.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/subscribemsg/CategoryData.java new file mode 100644 index 0000000000..997beb91ac --- /dev/null +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/subscribemsg/CategoryData.java @@ -0,0 +1,19 @@ +package me.chanjar.weixin.common.bean.subscribemsg; + +import lombok.Data; + +import java.io.Serializable; + +/** + * . + * + * @author Binary Wang + * created on 2021-01-27 + */ +@Data +public class CategoryData implements Serializable { + private static final long serialVersionUID = -5935548352317679892L; + + private int id; + private String name; +} diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/subscribemsg/PubTemplateKeyword.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/subscribemsg/PubTemplateKeyword.java new file mode 100644 index 0000000000..3f4681047b --- /dev/null +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/subscribemsg/PubTemplateKeyword.java @@ -0,0 +1,21 @@ +package me.chanjar.weixin.common.bean.subscribemsg; + +import lombok.Data; + +import java.io.Serializable; + +/** + * . + * + * @author Binary Wang + * created on 2021-01-27 + */ +@Data +public class PubTemplateKeyword implements Serializable { + private static final long serialVersionUID = -1100641668859815647L; + + private int kid; + private String name; + private String example; + private String rule; +} diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/subscribemsg/PubTemplateTitleListResult.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/subscribemsg/PubTemplateTitleListResult.java new file mode 100644 index 0000000000..32154a14f0 --- /dev/null +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/subscribemsg/PubTemplateTitleListResult.java @@ -0,0 +1,36 @@ +package me.chanjar.weixin.common.bean.subscribemsg; + +import lombok.Data; +import me.chanjar.weixin.common.util.json.WxGsonBuilder; + +import java.io.Serializable; +import java.util.List; + +/** + * @author ArBing + */ +@Data +public class PubTemplateTitleListResult implements Serializable { + private static final long serialVersionUID = -7718911668757837527L; + + private int count; + + private List data; + + public static PubTemplateTitleListResult fromJson(String json) { + return WxGsonBuilder.create().fromJson(json, PubTemplateTitleListResult.class); + } + + @Data + public static class TemplateItem implements Serializable { + private static final long serialVersionUID = 6888726696879905332L; + + private Integer type; + + private Integer tid; + + private String categoryId; + + private String title; + } +} diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/subscribemsg/TemplateInfo.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/subscribemsg/TemplateInfo.java new file mode 100644 index 0000000000..64222480ad --- /dev/null +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/subscribemsg/TemplateInfo.java @@ -0,0 +1,22 @@ +package me.chanjar.weixin.common.bean.subscribemsg; + +import lombok.Data; + +import java.io.Serializable; + +/** + * . + * + * @author Binary Wang + * created on 2021-01-27 + */ +@Data +public class TemplateInfo implements Serializable { + private static final long serialVersionUID = 6971785763573992264L; + + private String priTmplId; + private String title; + private String content; + private String example; + private int type; +} diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/enums/TicketType.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/enums/TicketType.java new file mode 100644 index 0000000000..afbd1ec382 --- /dev/null +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/enums/TicketType.java @@ -0,0 +1,35 @@ +package me.chanjar.weixin.common.enums; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +/** + *
+ * ticket类型枚举
+ * Created by Binary Wang on 2018/11/18.
+ * 
+ * + * @author Binary Wang + */ +@Getter +@RequiredArgsConstructor +public enum TicketType { + /** + * jsapi + */ + JSAPI("jsapi"), + /** + * sdk + */ + SDK("2"), + /** + * 微信卡券 + */ + WX_CARD("wx_card"); + + /** + * type代码 + */ + private final String code; + +} diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/WxType.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/enums/WxType.java similarity index 80% rename from weixin-java-common/src/main/java/me/chanjar/weixin/common/WxType.java rename to weixin-java-common/src/main/java/me/chanjar/weixin/common/enums/WxType.java index 4230506763..9d7d601a0a 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/WxType.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/enums/WxType.java @@ -1,4 +1,4 @@ -package me.chanjar.weixin.common; +package me.chanjar.weixin.common.enums; /** *
@@ -28,5 +28,10 @@ public enum WxType {
   /**
    * 微信支付.
    */
-  Pay;
+  Pay,
+  /**
+   * 微信视频号
+   */
+  Channel,
+  ;
 }
diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/error/WxChannelErrorMsgEnum.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/error/WxChannelErrorMsgEnum.java
new file mode 100644
index 0000000000..3491e74dc8
--- /dev/null
+++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/error/WxChannelErrorMsgEnum.java
@@ -0,0 +1,172 @@
+package me.chanjar.weixin.common.error;
+
+import com.google.common.collect.Maps;
+import java.util.Map;
+
+/**
+ *
+ * 
+ *     微信小店公共错误码.
+ *     参考文档:微信小店公共错误码
+ * 
+ * + * @author Zeyes + */ +public enum WxChannelErrorMsgEnum { + /** + * 系统繁忙,此时请开发者稍候再试 system error + */ + CODE_1(-1, "系统繁忙,此时请开发者稍候再试"), + /** + * 请求成功 ok + */ + CODE_0(0, "请求成功"), + /** + * 获取 access_token 时 AppSecret 错误,或者 access_token 无效。请开发者认真检查 AppSecret 的正确性 + * invalid credential, access_token is invalid or not latest, could get access_token by getStableAccessToken, more details at https://mmbizurl.cn/s/JtxxFh33r + */ + CODE_40001(40001, "获取 access_token 时 AppSecret 错误,或者 access_token 无效。请开发者认真检查 AppSecret 的正确性"), + /** + * 请检查 openid 的正确性 + * invalid openid + */ + CODE_40003(40003, "请检查 openid 的正确性"), + /** + * 请检查 appid 的正确性,避免异常字符,注意大小写 + * invalid appid + */ + CODE_40013(40013, "请检查 appid 的正确性,避免异常字符,注意大小写"), + /** + * 请检查API的URL是否与文档一致 + * invalid url + */ + CODE_40066(40066, "请检查API的URL是否与文档一致"), + /** + * 缺少 access_token 参数 + * access_token missing + */ + CODE_41001(41001, "缺少 access_token 参数"), + /** + * 请检查URL参数中是否有 ?appid= + * appid missing + */ + CODE_41002(41002, "请检查URL参数中是否有 ?appid="), + /** + * 请检查POST json中是否包含component_ appid宇段 + * missing component_appid + */ + CODE_41018(41018, "请检查POST json中是否包含component_ appid宇段"), + /** + * access_token失效,需要重新获取新的access_token + * access_token expired + */ + CODE_42001(42001, "access_token失效,需要重新获取新的access_token"), + /** + * 请检查发起API请求的Method是否为POST + * require POST method + */ + CODE_43002(43002, "请检查发起API请求的Method是否为POST"), + /** + * 请使用HTTPS方式清求,不要使用HTTP方式 + * require https + */ + CODE_43003(43003, "请使用HTTPS方式清求,不要使用HTTP方式"), + /** + * POST 的数据包为空 + * empty post data + */ + CODE_44002(44002, "POST 的数据包为空"), + /** + * 请对数据进行压缩 + * content size out of limit + */ + CODE_45002(45002, "请对数据进行压缩"), + /** + * 查看调用次数是否符合预期,可通过get_api_quota接口获取每天的调用quota;用完后可通过clear_quota进行清空 + * reach max api daily quota limit + */ + CODE_45009(45009, "查看调用次数是否符合预期,可通过get_api_quota接口获取每天的调用quota;用完后可通过clear_quota进行清空"), + /** + * 命中每分钟的频率限制 + * api minute-quota reach limit must slower retry next minute + */ + CODE_45011(45011, "命中每分钟的频率限制"), + /** + * 需要登录 channels.weixin.qq.com/shop 配置IP白名单 + * access clientip is not registered, not in ip-white-list + */ + CODE_45035(45035, "需要登录 channels.weixin.qq.com/shop 配置IP白名单"), + /** + * 解析 JSON/XML 内容错误 + * data format error + */ + CODE_47001(47001, "解析 JSON/XML 内容错误"), + /** + * 没有该接口权限 + * api unauthorized + */ + CODE_48001(48001, "没有该接口权限"), + /** + * 接口被禁用 + * api forbidden for irregularities + */ + CODE_48004(48004, "接口被禁用"), + /** + * 请找用户获取该api授权 + * user unauthorized + */ + CODE_50001(50001, "请找用户获取该api授权"), + /** + * 请检查封禁原因 + * user limited + */ + CODE_50002(50002, "请检查封禁原因"), + /** + * 需要登录 channels.weixin.qq.com/shop 配置IP白名单 + * access clientip is not registered, not in ip-white-list + */ + CODE_61004(61004, "需要登录 channels.weixin.qq.com/shop 配置IP白名单"), + /** + * 请检查第三方平台服务商检查已获取的授权集 + * api is unauthorized to component + */ + CODE_61007(61007, "请检查第三方平台服务商检查已获取的授权集"), + /** + * 需要登录 channels.weixin.qq.com/shop 继续完成注销 + * 账号发起注销,进入注销公示期 + */ + CODE_10080000(10080000, "需要登录 channels.weixin.qq.com/shop 继续完成注销"), + /** + * 账号已注销 + */ + CODE_10080001(10080001, "账号已注销"), + /** + * 小店的视频号带货身份为达人号,不允许使用该功能,如需使用,请将带货身份修改为商家 + */ + CODE_10080002(10080002, "小店的视频号带货身份为达人号,不允许使用该功能,如需使用,请将带货身份修改为商家"), + + ; + + private final int code; + private final String msg; + + WxChannelErrorMsgEnum(int code, String msg) { + this.code = code; + this.msg = msg; + } + + static final Map valueMap = Maps.newHashMap(); + + static { + for (WxChannelErrorMsgEnum value : WxChannelErrorMsgEnum.values()) { + valueMap.put(value.code, value.msg); + } + } + + /** + * 通过错误代码查找其中文含义. + */ + public static String findMsgByCode(int code) { + return valueMap.getOrDefault(code, null); + } +} diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/error/WxCpErrorMsgEnum.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/error/WxCpErrorMsgEnum.java index 5529d69759..356d1dbbf9 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/error/WxCpErrorMsgEnum.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/error/WxCpErrorMsgEnum.java @@ -1,11 +1,14 @@ package me.chanjar.weixin.common.error; +import com.google.common.collect.Maps; import lombok.Getter; +import java.util.Map; + /** *
  * 企业微信全局错误码.
- * 参考文档:企业微信全局错误码
+ * 参考文档:企业微信全局错误码
  * Created by Binary Wang on 2018/5/13.
  * 
* @@ -245,6 +248,10 @@ public enum WxCpErrorMsgEnum { * 不合法的URL;缺少主页URL参数,或者URL不合法(链接需要带上协议头,以 http:// 或者 https:// 开头). */ CODE_40094(40094, "不合法的URL;缺少主页URL参数,或者URL不合法(链接需要带上协议头,以 http:// 或者 https:// 开头)"), + /** + * 不合法的外部联系人userid + */ + CODE_40096(40096,"不合法的外部联系人userid"), /** * 缺少access_token参数. */ @@ -446,7 +453,7 @@ public enum WxCpErrorMsgEnum { */ CODE_60008(60008, "部门已存在;部门ID或者部门名称已存在"), /** - * 部门名称含有非法字符;不能含有 \\:?*“< >| 等字符. + * {@code 部门名称含有非法字符;不能含有 \\:?*"< >| 等字符.} */ CODE_60009(60009, "部门名称含有非法字符;不能含有 \\ :?*“< >| 等字符"), /** @@ -514,7 +521,7 @@ public enum WxCpErrorMsgEnum { */ CODE_60124(60124, "无效的父部门id;父部门不存在通讯录中"), /** - * 非法部门名字;不能为空,且不能超过64字节,且不能含有\\:*?”< >|等字符. + * {@code 非法部门名字;不能为空,且不能超过64字节,且不能含有\\:*?"< >|等字符.} */ CODE_60125(60125, "非法部门名字;不能为空,且不能超过64字节,且不能含有\\:*?”< >|等字符"), /** @@ -609,6 +616,322 @@ public enum WxCpErrorMsgEnum { * 不符合的state参数;必须是[a-zA-Z0-9]的参数值,长度不可超过128个字节. */ CODE_84025(84025, "不符合的state参数;必须是[a-zA-Z0-9]的参数值,长度不可超过128个字节"), + /** + * 缺少caller参数. + */ + CODE_84052(84052, "缺少caller参数"), + /** + * 缺少callee参数. + */ + CODE_84053(84053, "缺少callee参数"), + /** + * 缺少auth_corpid参数. + */ + CODE_84054(84054, "缺少auth_corpid参数"), + /** + * 超过拨打公费电话频率。排查方法:同一个客服5秒内只能调用api拨打一次公费电话 + */ + CODE_84055(84055, "超过拨打公费电话频率。排查方法:同一个客服5秒内只能调用api拨打一次公费电话"), + /** + * 被拨打用户安装应用时未授权拨打公费电话权限. + */ + CODE_84056(84056, "被拨打用户安装应用时未授权拨打公费电话权限"), + /** + * 公费电话余额不足. + */ + CODE_84057(84057, "公费电话余额不足"), + /** + * caller + */ + CODE_84058(84058, "caller 呼叫号码不支持"), + /** + * 号码非法. + */ + CODE_84059(84059, "号码非法"), + /** + * callee + */ + CODE_84060(84060, "callee 呼叫号码不支持"), + /** + * 不存在外部联系人的关系. + */ + CODE_84061(84061, "不存在外部联系人的关系"), + /** + * 未开启公费电话应用. + */ + CODE_84062(84062, "未开启公费电话应用"), + /** + * caller不存在. + */ + CODE_84063(84063, "caller不存在"), + /** + * callee不存在. + */ + CODE_84064(84064, "callee不存在"), + /** + * caller跟callee电话号码一致。排查方法:不允许自己拨打给自己 + */ + CODE_84065(84065, "caller跟callee电话号码一致。排查方法:不允许自己拨打给自己"), + /** + * 服务商拨打次数超过限制。排查方法:单个企业管理员,在一天(以上午10 + */ + CODE_84066(84066, "服务商拨打次数超过限制。排查方法:单个企业管理员,在一天(以上午10:00为起始时间)内,对应单个服务商,只能被呼叫【4】次。"), + /** + * 管理员收到的服务商公费电话个数超过限制。排查方法:单个企业管理员,在一天(以上午10 + */ + CODE_84067(84067, "管理员收到的服务商公费电话个数超过限制。排查方法:单个企业管理员,在一天(以上午10:00为起始时间)内,一共只能被【3】个服务商成功呼叫。"), + /** + * 拨打方被限制拨打公费电话. + */ + CODE_84069(84069, "拨打方被限制拨打公费电话"), + /** + * 不支持的电话号码。排查方法:拨打方或者被拨打方电话号码不支持 + */ + CODE_84070(84070, "不支持的电话号码。排查方法:拨打方或者被拨打方电话号码不支持"), + /** + * 不合法的外部联系人授权码。排查方法:非法或者已经消费过 + */ + CODE_84071(84071, "不合法的外部联系人授权码。排查方法:非法或者已经消费过"), + /** + * 应用未配置客服. + */ + CODE_84072(84072, "应用未配置客服"), + /** + * 客服userid不在应用配置的客服列表中. + */ + CODE_84073(84073, "客服userid不在应用配置的客服列表中"), + /** + * 没有外部联系人权限. + */ + CODE_84074(84074, "没有外部联系人权限"), + /** + * 不合法或过期的authcode. + */ + CODE_84075(84075, "不合法或过期的authcode"), + /** + * 缺失authcode. + */ + CODE_84076(84076, "缺失authcode"), + /** + * 订单价格过高,无法受理. + */ + CODE_84077(84077, "订单价格过高,无法受理"), + /** + * 购买人数不正确. + */ + CODE_84078(84078, "购买人数不正确"), + /** + * 价格策略不存在. + */ + CODE_84079(84079, "价格策略不存在"), + /** + * 订单不存在. + */ + CODE_84080(84080, "订单不存在"), + /** + * 存在未支付订单. + */ + CODE_84081(84081, "存在未支付订单"), + /** + * 存在申请退款中的订单. + */ + CODE_84082(84082, "存在申请退款中的订单"), + /** + * 非服务人员. + */ + CODE_84083(84083, "非服务人员"), + /** + * 非跟进用户. + */ + CODE_84084(84084, "非跟进用户"), + /** + * 应用已下架. + */ + CODE_84085(84085, "应用已下架"), + /** + * 订单人数超过可购买最大人数. + */ + CODE_84086(84086, "订单人数超过可购买最大人数"), + /** + * 打开订单支付前禁止关闭订单. + */ + CODE_84087(84087, "打开订单支付前禁止关闭订单"), + /** + * 禁止关闭已支付的订单. + */ + CODE_84088(84088, "禁止关闭已支付的订单"), + /** + * 订单已支付. + */ + CODE_84089(84089, "订单已支付"), + /** + * 缺失user_ticket. + */ + CODE_84090(84090, "缺失user_ticket"), + /** + * 订单价格不可低于下限. + */ + CODE_84091(84091, "订单价格不可低于下限"), + /** + * 无法发起代下单操作. + */ + CODE_84092(84092, "无法发起代下单操作"), + /** + * 代理关系已占用,无法代下单. + */ + CODE_84093(84093, "代理关系已占用,无法代下单"), + /** + * 该应用未配置代理分润规则,请先联系应用服务商处理. + */ + CODE_84094(84094, "该应用未配置代理分润规则,请先联系应用服务商处理"), + /** + * 免费试用版,无法扩容. + */ + CODE_84095(84095, "免费试用版,无法扩容"), + /** + * 免费试用版,无法续期. + */ + CODE_84096(84096, "免费试用版,无法续期"), + /** + * 当前企业有未处理订单. + */ + CODE_84097(84097, "当前企业有未处理订单"), + /** + * 固定总量,无法扩容. + */ + CODE_84098(84098, "固定总量,无法扩容"), + /** + * 非购买状态,无法扩容. + */ + CODE_84099(84099, "非购买状态,无法扩容"), + /** + * 未购买过此应用,无法续期. + */ + CODE_84100(84100, "未购买过此应用,无法续期"), + /** + * 企业已试用付费版本,无法全新购买. + */ + CODE_84101(84101, "企业已试用付费版本,无法全新购买"), + /** + * 企业当前应用状态已过期,无法扩容. + */ + CODE_84102(84102, "企业当前应用状态已过期,无法扩容"), + /** + * 仅可修改未支付订单. + */ + CODE_84103(84103, "仅可修改未支付订单"), + /** + * 订单已支付,无法修改. + */ + CODE_84104(84104, "订单已支付,无法修改"), + /** + * 订单已被取消,无法修改. + */ + CODE_84105(84105, "订单已被取消,无法修改"), + /** + * 企业含有该应用的待支付订单,无法代下单. + */ + CODE_84106(84106, "企业含有该应用的待支付订单,无法代下单"), + /** + * 企业含有该应用的退款中订单,无法代下单. + */ + CODE_84107(84107, "企业含有该应用的退款中订单,无法代下单"), + /** + * 企业含有该应用的待生效订单,无法代下单. + */ + CODE_84108(84108, "企业含有该应用的待生效订单,无法代下单"), + /** + * 订单定价不能未0. + */ + CODE_84109(84109, "订单定价不能未0"), + /** + * 新安装应用不在试用状态,无法升级为付费版. + */ + CODE_84110(84110, "新安装应用不在试用状态,无法升级为付费版"), + /** + * 无足够可用优惠券. + */ + CODE_84111(84111, "无足够可用优惠券"), + /** + * 无法关闭未支付订单. + */ + CODE_84112(84112, "无法关闭未支付订单"), + /** + * 无付费信息. + */ + CODE_84113(84113, "无付费信息"), + /** + * 虚拟版本不支持下单. + */ + CODE_84114(84114, "虚拟版本不支持下单"), + /** + * 虚拟版本不支持扩容. + */ + CODE_84115(84115, "虚拟版本不支持扩容"), + /** + * 虚拟版本不支持续期. + */ + CODE_84116(84116, "虚拟版本不支持续期"), + /** + * 在虚拟正式版期内不能扩容. + */ + CODE_84117(84117, "在虚拟正式版期内不能扩容"), + /** + * 虚拟正式版期内不能变更版本. + */ + CODE_84118(84118, "虚拟正式版期内不能变更版本"), + /** + * 当前企业未报备,无法进行代下单. + */ + CODE_84119(84119, "当前企业未报备,无法进行代下单"), + /** + * 当前应用版本已删除. + */ + CODE_84120(84120, "当前应用版本已删除"), + /** + * 应用版本已删除,无法扩容. + */ + CODE_84121(84121, "应用版本已删除,无法扩容"), + /** + * 应用版本已删除,无法续期. + */ + CODE_84122(84122, "应用版本已删除,无法续期"), + /** + * 非虚拟版本,无法升级. + */ + CODE_84123(84123, "非虚拟版本,无法升级"), + /** + * 非行业方案订单,不能添加部分应用版本的订单. + */ + CODE_84124(84124, "非行业方案订单,不能添加部分应用版本的订单"), + /** + * 购买人数不能少于最少购买人数. + */ + CODE_84125(84125, "购买人数不能少于最少购买人数"), + /** + * 购买人数不能多于最大购买人数. + */ + CODE_84126(84126, "购买人数不能多于最大购买人数"), + /** + * 无应用管理权限. + */ + CODE_84127(84127, "无应用管理权限"), + /** + * 无该行业方案下全部应用的管理权限. + */ + CODE_84128(84128, "无该行业方案下全部应用的管理权限"), + /** + * 付费策略已被删除,无法下单. + */ + CODE_84129(84129, "付费策略已被删除,无法下单"), + /** + * 订单生效时间不合法. + */ + CODE_84130(84130, "订单生效时间不合法"), + /** + * 文件转译解析错误。排查方法:只支持utf8文件转译,可能是不支持的文件类型或者格式 + */ + CODE_84200(84200, "文件转译解析错误。排查方法:只支持utf8文件转译,可能是不支持的文件类型或者格式"), /** * 包含不合法的词语. */ @@ -753,10 +1076,46 @@ public enum WxCpErrorMsgEnum { * 获取打卡记录时间间隔超限;保证开始时间大于0 且结束时间大于 0 且结束时间大于开始时间,且间隔少于93天. */ CODE_301024(301024, "获取打卡记录时间间隔超限;保证开始时间大于0 且结束时间大于 0 且结束时间大于开始时间,且间隔少于93天"), + /** + * 提交审批单请求参数错误 + */ + CODE_301025(301025, "提交审批单请求参数错误"), /** * 不允许更新该用户的userid. */ CODE_301036(301036, "不允许更新该用户的userid"), + /** + * 无审批应用权限,或者提单者不在审批应用/自建应用的可见范围 + */ + CODE_301055(301055, "无审批应用权限,或者提单者不在审批应用/自建应用的可见范围"), + /** + * 审批应用已停用 + */ + CODE_301056(301056, "审批应用已停用"), + /** + * 通用错误码,提交审批单内部接口失败 + */ + CODE_301057(301057, "通用错误码,提交审批单内部接口失败"), + /** + * 输入userid无对应成员 + */ + CODE_301069(301069,"输入userid无对应成员"), + /** + * 系统错误,请稍后再试 + */ + CODE_301070(301070,"系统错误,请稍后再试"), + /** + * 企业内有其他人员有相似人脸,此情况下人脸仍然会录入成功 + */ + CODE_301071(301071,"企业内有其他人员有相似人脸,此情况下人脸仍然会录入成功"), + /** + * 人脸图像数据错误请更换图片 + */ + CODE_301072(301072,"企业内有其他人员有相似人脸,此情况下人脸仍然会录入成功"), + /** + * 输入参数错误 + */ + CODE_301075(301075,"输入参数错误"), /** * 批量导入任务的文件中userid有重复. */ @@ -782,24 +1141,26 @@ public enum WxCpErrorMsgEnum { */ CODE_2000002(2000002, "CorpId参数无效;指定的CorpId不存在"); - private int code; - private String msg; + private final int code; + private final String msg; WxCpErrorMsgEnum(int code, String msg) { this.code = code; this.msg = msg; } + static final Map valueMap = Maps.newHashMap(); + + static { + for (WxCpErrorMsgEnum value : WxCpErrorMsgEnum.values()) { + valueMap.put(value.code, value.msg); + } + } + /** * 通过错误代码查找其中文含义.. */ public static String findMsgByCode(int code) { - for (WxCpErrorMsgEnum value : WxCpErrorMsgEnum.values()) { - if (value.code == code) { - return value.msg; - } - } - - return null; + return valueMap.getOrDefault(code, null); } } diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/error/WxError.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/error/WxError.java index d53e7fb25c..1aab7f1f20 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/error/WxError.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/error/WxError.java @@ -1,8 +1,10 @@ package me.chanjar.weixin.common.error; +import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; -import me.chanjar.weixin.common.WxType; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.common.enums.WxType; import me.chanjar.weixin.common.util.json.WxGsonBuilder; import org.apache.commons.lang3.StringUtils; @@ -10,13 +12,17 @@ /** * 微信错误码. + *

* 请阅读: - * 公众平台:全局返回码说明 + * 公众平台:全局返回码说明 * 企业微信:全局错误码 + *

* - * @author Daniel Qian & Binary Wang + * @author Daniel Qian, Binary Wang */ @Data +@NoArgsConstructor +@AllArgsConstructor @Builder public class WxError implements Serializable { private static final long serialVersionUID = 7869786563361406291L; @@ -39,6 +45,11 @@ public class WxError implements Serializable { private String json; + public WxError(int errorCode, String errorMsg) { + this.errorCode = errorCode; + this.errorMsg = errorMsg; + } + public static WxError fromJson(String json) { return fromJson(json, null); } @@ -75,6 +86,20 @@ public static WxError fromJson(String json, WxType type) { } break; } + case Open: { + final String msg = WxOpenErrorMsgEnum.findMsgByCode(wxError.getErrorCode()); + if (msg != null) { + wxError.setErrorMsg(msg); + } + break; + } + case Channel: { + final String msg = WxChannelErrorMsgEnum.findMsgByCode(wxError.getErrorCode()); + if (msg != null) { + wxError.setErrorMsg(msg); + } + break; + } default: return wxError; } diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/error/WxErrorException.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/error/WxErrorException.java index 6e9a2c538d..992081da07 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/error/WxErrorException.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/error/WxErrorException.java @@ -6,7 +6,13 @@ public class WxErrorException extends Exception { private static final long serialVersionUID = -6357149550353160810L; - private WxError error; + private final WxError error; + + private static final int DEFAULT_ERROR_CODE = -99; + + public WxErrorException(String message) { + this(WxError.builder().errorCode(DEFAULT_ERROR_CODE).errorMsg(message).build()); + } public WxErrorException(WxError error) { super(error.toString()); @@ -18,9 +24,12 @@ public WxErrorException(WxError error, Throwable cause) { this.error = error; } + public WxErrorException(Throwable cause) { + super(cause.getMessage(), cause); + this.error = WxError.builder().errorCode(DEFAULT_ERROR_CODE).errorMsg(cause.getMessage()).build(); + } + public WxError getError() { return this.error; } - - } diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/error/WxMaErrorMsgEnum.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/error/WxMaErrorMsgEnum.java index f1ced3fd09..ffe9b5e3ea 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/error/WxMaErrorMsgEnum.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/error/WxMaErrorMsgEnum.java @@ -1,7 +1,10 @@ package me.chanjar.weixin.common.error; +import com.google.common.collect.Maps; import lombok.Getter; +import java.util.Map; + /** * 微信小程序错误码 * @@ -43,23 +46,23 @@ public enum WxMaErrorMsgEnum { */ CODE_40003(40003, "openid 不正确"), /** - *
    * 无效媒体文件类型
-   * 对应操作:uploadTempMedia
+   * 

+ * 对应操作:{@code uploadTempMedia} * 对应地址: - * POST https://api.weixin.qq.com/cgi-bin/media/upload?access_token=ACCESS_TOKEN&type=TYPE + * {@code POST https://api.weixin.qq.com/cgi-bin/media/upload?access_token=ACCESS_TOKEN&type=TYPE} * 参考文档地址: https://developers.weixin.qq.com/miniprogram/dev/api/open-api/customer-message/uploadTempMedia.html - *

+ *

*/ CODE_40004(40004, "无效媒体文件类型"), /** - *
    * 无效媒体文件 ID.
-   * 对应操作:getTempMedia
+   * 

+ * 对应操作:{@code getTempMedia} * 对应地址: - * GET https://api.weixin.qq.com/cgi-bin/media/get?access_token=ACCESS_TOKEN&media_id=MEDIA_ID + * {@code GET https://api.weixin.qq.com/cgi-bin/media/get?access_token=ACCESS_TOKEN&media_id=MEDIA_ID} * 参考文档地址: https://developers.weixin.qq.com/miniprogram/dev/api/open-api/customer-message/getTempMedia.html - *

+ *

*/ CODE_40007(40007, "无效媒体文件 ID"), /** @@ -67,11 +70,10 @@ public enum WxMaErrorMsgEnum { * appid不正确,或者不符合绑定关系要求. * 对应操作:sendUniformMessage * 对应地址: - * POST https://api.weixin.qq.com/cgi-bin/message/wxopen/template/uniform_send?access_token=ACCESS_TOKEN - * 参考文档地址: https://developers.weixin.qq.com/miniprogram/dev/api/open-api/uniform-message/sendUniformMessage.html + * 参考文档地址: https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/openApi-mgnt/clearQuota.html *
*/ - CODE_40013(40013, "appid不正确,或者不符合绑定关系要求"), + CODE_40013(40013, "appid不正确/不合法(避免异常字符,注意大小写),或者不符合绑定关系要求"), /** *
    * template_id 不正确.
@@ -97,29 +99,29 @@ public enum WxMaErrorMsgEnum {
    */
   CODE_41028(41028, "form_id 不正确,或者过期"),
   /**
-   * 
    * code 或 template_id 不正确.
-   * 对应操作:code2Session, sendUniformMessage, sendTemplateMessage
+   * 

+ * 对应操作:{@code code2Session}, {@code sendUniformMessage}, {@code sendTemplateMessage} * 对应地址: - * GET https://api.weixin.qq.com/sns/jscode2session?appid=APPID&secret=SECRET&js_code=JSCODE&grant_type=authorization_code + * {@code GET https://api.weixin.qq.com/sns/jscode2session?appid=APPID&secret=SECRET&js_code=JSCODE&grant_type=authorization_code} * POST https://api.weixin.qq.com/cgi-bin/message/wxopen/template/uniform_send?access_token=ACCESS_TOKEN * POST https://api.weixin.qq.com/cgi-bin/message/wxopen/template/send?access_token=ACCESS_TOKEN * 参考文档地址: https://developers.weixin.qq.com/miniprogram/dev/api/open-api/login/code2Session.html * https://developers.weixin.qq.com/miniprogram/dev/api/open-api/uniform-message/sendUniformMessage.html * https://developers.weixin.qq.com/miniprogram/dev/api/open-api/template-message/sendTemplateMessage.html - *

+ *

*/ CODE_41029(41029, "请求的参数不正确"), /** - *
    * form_id 已被使用,或者所传page页面不存在,或者小程序没有发布
-   * 对应操作:sendUniformMessage, getWXACodeUnlimit
+   * 

+ * 对应操作:{@code sendUniformMessage}, {@code getWXACodeUnlimit} * 对应地址: * POST https://api.weixin.qq.com/cgi-bin/message/wxopen/template/uniform_send?access_token=ACCESS_TOKEN * POST https://api.weixin.qq.com/wxa/getwxacodeunlimit?access_token=ACCESS_TOKEN * 参考文档地址: https://developers.weixin.qq.com/miniprogram/dev/api/open-api/uniform-message/sendUniformMessage.html - * https://developers.weixin.qq.com/miniprogram/dev/api/open-api/qr-code/getWXACodeUnlimit.html - *

+ * https://developers.weixin.qq.com/miniprogram/dev/api/open-api/qr-code/getWXACodeUnlimit.html + *

*/ CODE_41030(41030, "请求的参数不正确"), /** @@ -136,13 +138,13 @@ public enum WxMaErrorMsgEnum { */ CODE_45009(45009, "调用分钟频率受限"), /** - *
    * 频率限制,每个用户每分钟100次.
-   * 对应操作:code2Session
+   * 

+ * 对应操作:{@code code2Session} * 对应地址: - * GET https://api.weixin.qq.com/sns/jscode2session?appid=APPID&secret=SECRET&js_code=JSCODE&grant_type=authorization_code + * {@code GET https://api.weixin.qq.com/sns/jscode2session?appid=APPID&secret=SECRET&js_code=JSCODE&grant_type=authorization_code} * 参考文档地址: https://developers.weixin.qq.com/miniprogram/dev/api/open-api/login/code2Session.html - *

+ *

*/ CODE_45011(45011, "频率限制,每个用户每分钟100次"), /** @@ -188,12 +190,13 @@ public enum WxMaErrorMsgEnum { */ CODE_45072(45072, "command字段取值不对"), /** - *
    * 下发输入状态,需要之前30秒内跟用户有过消息交互.
-   * 对应操作:customerTyping
+   * 

+ * 对应操作:{@code customerTyping} * 对应地址: * POST https://api.weixin.qq.com/cgi-bin/message/custom/typing?access_token=ACCESS_TOKEN * 参考文档地址: https://developers.weixin.qq.com/miniprogram/dev/api/open-api/customer-message/customerTyping.html + *

*/ CODE_45080(45080, "下发输入状态,需要之前30秒内跟用户有过消息交互"), /** @@ -264,6 +267,50 @@ public enum WxMaErrorMsgEnum { * activity_id 过期. */ CODE_47504(47504, "activity_id 过期"), + /** + * api 禁止清零调用次数,因为清零次数达到上限 + * + * @see 参考文档 + */ + CODE_48006(48006, "api 禁止清零调用次数,因为清零次数达到上限"), + + /** + * rid不存在 + * + * @see 参考文档 + */ + CODE_76001(76001, "rid不存在"), + /** + * rid为空或者格式错误 + * + * @see 参考文档 + */ + CODE_76002(76002, "rid为空或者格式错误"), + /** + * 当前账号无权查询该rid,该rid属于其他账号调用所产生 + * + * @see 参考文档 + */ + CODE_76003(76003, "当前账号无权查询该rid,该rid属于其他账号调用所产生"), + /** + * rid过期 + * + * @see 参考文档 + */ + CODE_76004(76004, "rid过期,仅支持持续7天内的rid"), + /** + * cgi_path填错了 + * + * @see 参考文档 + */ + CODE_76021(76021, "cgi_path填错了"), + /** + * 当前调用接口使用的token与api所属账号不符 + * + * @see 参考文档 + */ + CODE_76022(76022, "当前调用接口使用的token与api所属账号不符,详情可看注意事项的说明"), + /** * 没有绑定开放平台帐号. */ @@ -340,6 +387,17 @@ public enum WxMaErrorMsgEnum { CODE_91017(91017, "+号规则 不同类型关联名主体不一致"), CODE_40097(40097, "参数错误"), + /** + * 缺少 appid 参数 + * 参考文档 + */ + CODE_41002(41002, "缺少 appid 参数"), + /** + * 缺少 secret 参数 + * 参考文档 + */ + CODE_41004(41004, "缺少 secret 参数"), + CODE_41006(41006, "media_id 不能为空"), @@ -409,7 +467,7 @@ public enum WxMaErrorMsgEnum { CODE_85012(85012, "无效的审核 id"), - CODE_87013(87013, "撤回次数达到上限(每天一次,每个月 10 次)"), + CODE_87013(87013, "撤回次数达到上限(每天5次,每个月 10 次)"), CODE_85019(85019, "没有审核版本"), @@ -452,26 +510,356 @@ public enum WxMaErrorMsgEnum { CODE_43101(43101, "用户拒绝接受消息,如果用户之前曾经订阅过,则表示用户取消了订阅关系"), CODE_47003(47003, "模板参数不准确,可能为空或者不满足规则,errmsg会提示具体是哪个字段出错"), + + /** + * 小程序绑定体验者 + */ + CODE_85001(85001, "微信号不存在或微信号设置为不可搜索"), + + CODE_85002(85002, "小程序绑定的体验者数量达到上限"), + + CODE_85003(85003, "微信号绑定的小程序体验者达到上限"), + + CODE_85004(85004, "微信号已经绑定"), + + /** + * 53010 + * 名称格式不合法 + */ + CODE_53010(53010, "名称格式不合法"), + + /** + * 53011 + * 名称检测命中频率限制 + */ + CODE_53011(53011, "名称检测命中频率限制"), + + /** + * 53012 + * 禁止使用该名称 + */ + CODE_53012(53012, "禁止使用该名称"), + + /** + * 53013 + * 公众号:名称与已有公众号名称重复;小程序:该名称与已有小程序名称重复 + */ + CODE_53013(53013, "公众号:名称与已有公众号名称重复;小程序:该名称与已有小程序名称重复"), + + /** + * 53014 + * 公众号:公众号已有{名称 A+}时,需与该帐号相同主体才可申请{名称 A};小程序:小程序已有{名称 A+}时,需与该帐号相同主体才可申请{名称 A} + */ + CODE_53014(53014, "公众号:公众号已有{名称 A+}时,需与该帐号相同主体才可申请{名称 A};小程序:小程序已有{名称 A+}时,需与该帐号相同主体才可申请{名称 A}"), + + /** + * 53015 + * 公众号:该名称与已有小程序名称重复,需与该小程序帐号相同主体才可申请;小程序:该名称与已有公众号名称重复,需与该公众号帐号相同主体才可申请 + */ + CODE_53015(53015, "公众号:该名称与已有小程序名称重复,需与该小程序帐号相同主体才可申请;小程序:该名称与已有公众号名称重复,需与该公众号帐号相同主体才可申请"), + + /** + * 53016 + * 公众号:该名称与已有多个小程序名称重复,暂不支持申请;小程序:该名称与已有多个公众号名称重复,暂不支持申请 + */ + CODE_53016(53016, "公众号:该名称与已有多个小程序名称重复,暂不支持申请;小程序:该名称与已有多个公众号名称重复,暂不支持申请"), + + /** + * 53017 + * 公众号:小程序已有{名称 A+}时,需与该帐号相同主体才可申请{名称 A};小程序:公众号已有{名称 A+}时,需与该帐号相同主体才可申请{名称 A} + */ + CODE_53017(53017, "公众号:小程序已有{名称 A+}时,需与该帐号相同主体才可申请{名称 A};小程序:公众号已有{名称 A+}时,需与该帐号相同主体才可申请{名称 A}"), + + /** + * 53018 + * 名称命中微信号 + */ + CODE_53018(53018, "名称命中微信号"), + + /** + * 53019 + * 名称在保护期内 + */ + CODE_53019(53019, "名称在保护期内"), + + /** + * 61070 + * 法人姓名与微信号不一致 name, wechat name not in accordance + */ + CODE_61070(61070, "法人姓名与微信号不一致"), + + /** + * 85015 + * 该账号不是小程序账号 + */ + CODE_85015(85015, "该账号不是小程序账号"), + + /** + * 85066 + * 链接错误 + */ + CODE_85066(85066, "链接错误"), + + /** + * 85068 + * 测试链接不是子链接 + */ + CODE_85068(85068, "测试链接不是子链接"), + + /** + * 85069 + * 校验文件失败 + */ + CODE_85069(85069, "校验文件失败"), + + /** + * 85070 + * 个人类型小程序无法设置二维码规则 + */ + CODE_85070(85070, "个人类型小程序无法设置二维码规则"), + + /** + * 85071 + * 已添加该链接,请勿重复添加 + */ + CODE_85071(85071, "已添加该链接,请勿重复添加"), + + /** + * 85072 + * 该链接已被占用 + */ + CODE_85072(85072, "该链接已被占用"), + + /** + * 85073 + * 二维码规则已满 + */ + CODE_85073(85073, "二维码规则已满"), + + /** + * 85074 + * 小程序未发布, 小程序必须先发布代码才可以发布二维码跳转规则 + */ + CODE_85074(85074, "小程序未发布, 小程序必须先发布代码才可以发布二维码跳转规则"), + + /** + * 85075 + * 个人类型小程序无法设置二维码规则 + */ + CODE_85075(85075, "个人类型小程序无法设置二维码规则"), + + /** + * 86004 + * 无效微信号 invalid wechat + */ + CODE_86004(86004, "无效微信号"), + + /** + * 89247 + * 内部错误 inner error + */ + CODE_89247(89247, "内部错误"), + + /** + * 89248 + * 企业代码类型无效,请选择正确类型填写 invalid code_type type + */ + CODE_89248(89248, "企业代码类型无效,请选择正确类型填写"), + + /** + * 89249 + * 该主体已有任务执行中,距上次任务 24h 后再试 task running + */ + CODE_89249(89249, "该主体已有任务执行中,距上次任务 24h 后再试"), + + /** + * 89250 + * 未找到该任务 task not found + */ + CODE_89250(89250, "未找到该任务"), + + + /** + * 89251 + * 待法人人脸核身校验 legal person checking + */ + CODE_89251(89251, "待法人人脸核身校验"), + + /** + * 89252 + * {@code 法人&企业信息一致性校验中 front checking} + */ + CODE_89252(89252, "法人&企业信息一致性校验中"), + + /** + * 89253 + * 缺少参数 lack of some params + */ + CODE_89253(89253, "缺少参数s"), + + + /** + * 89254 + * 第三方权限集不全,补全权限集全网发布后生效 lack of some component rights + */ + CODE_89254(89254, "第三方权限集不全,补全权限集全网发布后生效"), + + /** + * 89255 + * code参数无效,请检查code长度以及内容是否正确 code参数无效,请检查code长度以及内容是否正确_; + * 注意code_type的值不同需要传的code长度不一样 ;注意code_type的值不同需要传的code长度不一样 enterprise code_invalid invalid + */ + CODE_89255(89255, "code参数无效,请检查code长度以及内容是否正确_;注意code_type的值不同需要传的code长度不一样 ;注意code_type的值不同需要传的code长度不一样"), + +// CODE_504002(-504002, "云函数未找到 Function not found"), + + /** + * 半屏小程序系统错误 + */ + CODE_89408(89408, "半屏小程序系统错误"), + + /** + * 获取半屏小程序列表参数错误 + */ + CODE_89409(89409, "获取半屏小程序列表参数错误"), + + /** + * 添加半屏小程序appid参数错误 + */ + CODE_89410(89410, "添加半屏小程序appid参数错误"), + + /** + * 添加半屏小程序appid参数为空 + */ + CODE_89411(89411, "添加半屏小程序appid参数为空"), + + /** + * 添加半屏小程序申请理由不得超过30个字 + */ + CODE_89412(89412, "添加半屏小程序申请理由不得超过30个字"), + + /** + * 该小程序被申请次数已达24h限制 + */ + CODE_89413(89413, "该小程序被申请次数已达24h限制"), + + /** + * 每天仅允许申请50次半屏小程序 + */ + CODE_89414(89414, "每天仅允许申请50次半屏小程序"), + + /** + * 删除半屏小程序appid参数为空 + */ + CODE_89415(89415, "删除半屏小程序appid参数为空"), + + /** + * 取消半屏小程序授权appid参数为空 + */ + CODE_89416(89416, "取消半屏小程序授权appid参数为空"), + + /** + * 修改半屏小程序方式flag参数错误 + */ + CODE_89417(89417, "修改半屏小程序方式flag参数错误"), + + /** + * 获取半屏小程序每日申请次数失败 + */ + CODE_89418(89418, "获取半屏小程序每日申请次数失败"), + + /** + * 获取半屏小程序每日授权次数失败 + */ + CODE_89419(89419, "获取半屏小程序每日授权次数失败"), + + /** + * 不支持添加个人主体小程序 + */ + CODE_89420(89420, "不支持添加个人主体小程序"), + + /** + * 删除数据未找到 + */ + CODE_89421(89421, "删除数据未找到"), + + /** + * 删除状态异常 + */ + CODE_89422(89422, "删除状态异常"), + + /** + * 申请次数添加到达上限 + */ + CODE_89423(89423, "申请次数添加到达上限"), + + /** + * 申请添加已超时 + */ + CODE_89425(89425, "申请添加已超时"), + + /** + * 申请添加状态异常 + */ + CODE_89426(89426, "申请添加状态异常"), + + /** + * 申请号和授权号相同 + */ + CODE_89427(89427, "申请号和授权号相同"), + + /** + * 该小程序已申请,不允许重复添加 + */ + CODE_89428(89428, "该小程序已申请,不允许重复添加"), + + /** + * 已到达同一小程序每日最多申请次数 + */ + CODE_89429(89429, "已到达同一小程序每日最多申请次数"), + + /** + * 该小程序已设置自动拒绝申请 + */ + CODE_89430(89430, "该小程序已设置自动拒绝申请"), + + /** + * 不支持此类型小程序 + */ + CODE_89431(89431, "不支持此类型小程序"), + + /** + * 不是小程序 + */ + CODE_89432(89432, "不是小程序"), + + /** + * 授权次数到达上限 + */ + CODE_89424(89424, "授权次数到达上限"), + ; - private int code; - private String msg; + private final int code; + private final String msg; WxMaErrorMsgEnum(int code, String msg) { this.code = code; this.msg = msg; } + static final Map valueMap = Maps.newHashMap(); + + static { + for (WxMaErrorMsgEnum value : WxMaErrorMsgEnum.values()) { + valueMap.put(value.code, value.msg); + } + } + /** * 通过错误代码查找其中文含义. */ public static String findMsgByCode(int code) { - for (WxMaErrorMsgEnum value : WxMaErrorMsgEnum.values()) { - if (value.code == code) { - return value.msg; - } - } - - return null; + return valueMap.getOrDefault(code, null); } } diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/error/WxMpErrorMsgEnum.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/error/WxMpErrorMsgEnum.java index c75f759660..fdfb397cb3 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/error/WxMpErrorMsgEnum.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/error/WxMpErrorMsgEnum.java @@ -1,11 +1,14 @@ package me.chanjar.weixin.common.error; +import com.google.common.collect.Maps; import lombok.Getter; +import java.util.Map; + /** *
  * 微信公众平台全局返回码.
- * 参考文档:公众平台全局返回码
+ * 参考文档:公众平台全局返回码
  * Created by Binary Wang on 2018/5/13.
  * 
* @@ -86,9 +89,9 @@ public enum WxMpErrorMsgEnum { */ CODE_40016(40016, "不合法的按钮个数"), /** - * 不合法的按钮个数. + * 不合法的按钮类型. */ - CODE_40017(40017, "不合法的按钮个数"), + CODE_40017(40017, "不合法的按钮类型"), /** * 不合法的按钮名字长度. */ @@ -209,6 +212,10 @@ public enum WxMpErrorMsgEnum { * 请勿添加其他公众号的主页链接. */ CODE_40155(40155, "请勿添加其他公众号的主页链接"), + /** + * oauth_code已使用 + */ + CODE_40163(40163, "oauth_code已使用"), /** * 缺少 access_token 参数. */ @@ -369,6 +376,18 @@ public enum WxMpErrorMsgEnum { * 非法的tag_id. */ CODE_45159(45159, "非法的tag_id"), + /** + * 相同 clientmsgid 已存在群发记录,返回数据中带有已存在的群发任务的 msgid + */ + CODE_45065(45065, "相同 clientmsgid 已存在群发记录,返回数据中带有已存在的群发任务的 msgid"), + /** + * 相同 clientmsgid 重试速度过快,请间隔1分钟重试 + */ + CODE_45066(45066, "相同 clientmsgid 重试速度过快,请间隔1分钟重试"), + /** + * clientmsgid 长度超过限制 + */ + CODE_45067(45067, "clientmsgid 长度超过限制"), /** * 不存在媒体数据. */ @@ -644,24 +663,26 @@ public enum WxMpErrorMsgEnum { */ CODE_45084(45084, "没有设置 speed 参数"); - private int code; - private String msg; + private final int code; + private final String msg; WxMpErrorMsgEnum(int code, String msg) { this.code = code; this.msg = msg; } + static final Map valueMap = Maps.newHashMap(); + + static { + for (WxMpErrorMsgEnum value : WxMpErrorMsgEnum.values()) { + valueMap.put(value.code, value.msg); + } + } + /** * 通过错误代码查找其中文含义.. */ public static String findMsgByCode(int code) { - for (WxMpErrorMsgEnum value : WxMpErrorMsgEnum.values()) { - if (value.code == code) { - return value.msg; - } - } - - return null; + return valueMap.getOrDefault(code, null); } } diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/error/WxOpenErrorMsgEnum.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/error/WxOpenErrorMsgEnum.java new file mode 100644 index 0000000000..ba910e988b --- /dev/null +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/error/WxOpenErrorMsgEnum.java @@ -0,0 +1,9207 @@ +package me.chanjar.weixin.common.error; + +import com.google.common.collect.Maps; +import lombok.Getter; + +import java.util.Map; + +/** + *
+ *     微信开放平台全局返回码.
+ *     参考文档:开放平台全局返回码
+ * 
+ * + * @author Lam Jerry + */ + +@Getter +public enum WxOpenErrorMsgEnum { + /** + * 系统繁忙,此时请开发者稍候再试 system error + */ + CODE_1(-1, "系统繁忙,此时请开发者稍候再试"), + + /** + * 请求成功 ok + */ + CODE_0(0, "请求成功"), + + /** + * POST参数非法 + */ + CODE_1003(1003, "POST参数非法"), + + /** + * 商品id不存在 + */ + CODE_20002(20002, "商品id不存在"), + + /** + * 获取 access_token 时 AppSecret 错误,或者 access_token 无效。请开发者认真比对 AppSecret 的正确性,或查看是否正在为恰当的公众号调用接口 invalid credential, access_token is invalid or not latest + */ + CODE_40001(40001, "获取 access_token 时 AppSecret 错误,或者 access_token 无效。请开发者认真比对 AppSecret 的正确性,或查看是否正在为恰当的公众号调用接口"), + + /** + * 不合法的凭证类型 invalid grant_type + */ + CODE_40002(40002, "不合法的凭证类型"), + + /** + * 不合法的 OpenID ,请开发者确认 OpenID (该用户)是否已关注公众号,或是否是其他公众号的 OpenID invalid openid + */ + CODE_40003(40003, "不合法的 OpenID ,请开发者确认 OpenID (该用户)是否已关注公众号,或是否是其他公众号的 OpenID"), + + /** + * 不合法的媒体文件类型 invalid media type + */ + CODE_40004(40004, "不合法的媒体文件类型"), + + /** + * 上传素材文件格式不对 invalid file type + */ + CODE_40005(40005, "上传素材文件格式不对"), + + /** + * 上传素材文件大小超出限制 invalid meida size + */ + CODE_40006(40006, "上传素材文件大小超出限制"), + + /** + * 不合法的媒体文件 id invalid media_id + */ + CODE_40007(40007, "不合法的媒体文件 id"), + + /** + * 不合法的消息类型 invalid message type + */ + CODE_40008(40008, "不合法的消息类型"), + + /** + * 图片尺寸太大 invalid image size + */ + CODE_40009(40009, "图片尺寸太大"), + + /** + * 不合法的语音文件大小 invalid voice size + */ + CODE_40010(40010, "不合法的语音文件大小"), + + /** + * 不合法的视频文件大小 invalid video size + */ + CODE_40011(40011, "不合法的视频文件大小"), + + /** + * 不合法的缩略图文件大小 invalid thumb size + */ + CODE_40012(40012, "不合法的缩略图文件大小"), + + /** + * 不合法的appid invalid appid + */ + CODE_40013(40013, "不合法的appid"), + + /** + * 不合法的 access_token ,请开发者认真比对 access_token 的有效性(如是否过期),或查看是否正在为恰当的公众号调用接口 invalid access_token + */ + CODE_40014(40014, "不合法的 access_token ,请开发者认真比对 access_token 的有效性(如是否过期),或查看是否正在为恰当的公众号调用接口"), + + /** + * 不合法的菜单类型 invalid menu type + */ + CODE_40015(40015, "不合法的菜单类型"), + + /** + * 不合法的按钮个数 invalid button size + */ + CODE_40016(40016, "不合法的按钮个数"), + + /** + * 不合法的按钮类型 invalid button type + */ + CODE_40017(40017, "不合法的按钮类型"), + + /** + * 不合法的按钮名字长度 invalid button name size + */ + CODE_40018(40018, "不合法的按钮名字长度"), + + /** + * 不合法的按钮 KEY 长度 invalid button key size + */ + CODE_40019(40019, "不合法的按钮 KEY 长度"), + + /** + * 不合法的按钮 URL 长度 invalid button url size + */ + CODE_40020(40020, "不合法的按钮 URL 长度"), + + /** + * 不合法的菜单版本号 invalid menu version + */ + CODE_40021(40021, "不合法的菜单版本号"), + + /** + * 不合法的子菜单级数 invalid sub_menu level + */ + CODE_40022(40022, "不合法的子菜单级数"), + + /** + * 不合法的子菜单按钮个数 invalid sub button size + */ + CODE_40023(40023, "不合法的子菜单按钮个数"), + + /** + * 不合法的子菜单按钮类型 invalid sub button type + */ + CODE_40024(40024, "不合法的子菜单按钮类型"), + + /** + * 不合法的子菜单按钮名字长度 invalid sub button name size + */ + CODE_40025(40025, "不合法的子菜单按钮名字长度"), + + /** + * 不合法的子菜单按钮 KEY 长度 invalid sub button key size + */ + CODE_40026(40026, "不合法的子菜单按钮 KEY 长度"), + + /** + * 不合法的子菜单按钮 URL 长度 invalid sub button url size + */ + CODE_40027(40027, "不合法的子菜单按钮 URL 长度"), + + /** + * 不合法的自定义菜单使用用户 invalid menu api user + */ + CODE_40028(40028, "不合法的自定义菜单使用用户"), + + /** + * 无效的 oauth_code invalid code + */ + CODE_40029(40029, "无效的 oauth_code"), + + /** + * 不合法的 refresh_token invalid refresh_token + */ + CODE_40030(40030, "不合法的 refresh_token"), + + /** + * 不合法的 openid 列表 invalid openid list + */ + CODE_40031(40031, "不合法的 openid 列表"), + + /** + * 不合法的 openid 列表长度 invalid openid list size + */ + CODE_40032(40032, "不合法的 openid 列表长度"), + + /** + * 不合法的请求字符,不能包含 \\uxxxx 格式的字符 invalid charset. please check your request, if include \\uxxxx will create fail! + */ + CODE_40033(40033, "不合法的请求字符,不能包含 \\uxxxx 格式的字符"), + + /** + * invalid template size + */ + CODE_40034(40034, "invalid template size"), + + /** + * 不合法的参数 invalid args size + */ + CODE_40035(40035, "不合法的参数"), + + /** + * 不合法的 template_id 长度 invalid template_id size + */ + CODE_40036(40036, "不合法的 template_id 长度"), + + /** + * 不合法的 template_id invalid template_id + */ + CODE_40037(40037, "不合法的 template_id"), + + /** + * 不合法的请求格式 invalid packaging type + */ + CODE_40038(40038, "不合法的请求格式"), + + /** + * 不合法的 URL 长度 invalid url size + */ + CODE_40039(40039, "不合法的 URL 长度"), + + /** + * invalid plugin token + */ + CODE_40040(40040, "invalid plugin token"), + + /** + * invalid plugin id + */ + CODE_40041(40041, "invalid plugin id"), + + /** + * invalid plugin session + */ + CODE_40042(40042, "invalid plugin session"), + + /** + * invalid fav type + */ + CODE_40043(40043, "invalid fav type"), + + /** + * invalid size in link.title + */ + CODE_40044(40044, "invalid size in link.title"), + + /** + * invalid size in link.description + */ + CODE_40045(40045, "invalid size in link.description"), + + /** + * invalid size in link.iconurl + */ + CODE_40046(40046, "invalid size in link.iconurl"), + + /** + * invalid size in link.url + */ + CODE_40047(40047, "invalid size in link.url"), + + /** + * 无效的url invalid url domain + */ + CODE_40048(40048, "无效的url"), + + /** + * invalid score report type + */ + CODE_40049(40049, "invalid score report type"), + + /** + * 不合法的分组 id invalid timeline type + */ + CODE_40050(40050, "不合法的分组 id"), + + /** + * 分组名字不合法 invalid group name + */ + CODE_40051(40051, "分组名字不合法"), + + /** + * invalid action name + */ + CODE_40052(40052, "invalid action name"), + + /** + * invalid action info, please check document + */ + CODE_40053(40053, "invalid action info, please check document"), + + /** + * 不合法的子菜单按钮 url 域名 invalid sub button url domain + */ + CODE_40054(40054, "不合法的子菜单按钮 url 域名"), + + /** + * 不合法的菜单按钮 url 域名 invalid button url domain + */ + CODE_40055(40055, "不合法的菜单按钮 url 域名"), + + /** + * invalid serial code + */ + CODE_40056(40056, "invalid serial code"), + + /** + * invalid tabbar size + */ + CODE_40057(40057, "invalid tabbar size"), + + /** + * invalid tabbar name size + */ + CODE_40058(40058, "invalid tabbar name size"), + + /** + * invalid msg id + */ + CODE_40059(40059, "invalid msg id"), + + /** + * 删除单篇图文时,指定的 article_idx 不合法 invalid article idx + */ + CODE_40060(40060, "删除单篇图文时,指定的 article_idx 不合法"), + + /** + * invalid title size + */ + CODE_40062(40062, "invalid title size"), + + /** + * invalid message_ext size + */ + CODE_40063(40063, "invalid message_ext size"), + + /** + * invalid app type + */ + CODE_40064(40064, "invalid app type"), + + /** + * invalid msg status + */ + CODE_40065(40065, "invalid msg status"), + + /** + * 不合法的 url ,递交的页面被sitemap标记为拦截 invalid url + */ + CODE_40066(40066, "不合法的 url ,递交的页面被sitemap标记为拦截"), + + /** + * invalid tvid + */ + CODE_40067(40067, "invalid tvid"), + + /** + * contain mailcious url + */ + CODE_40068(40068, "contain mailcious url"), + + /** + * invalid hardware type + */ + CODE_40069(40069, "invalid hardware type"), + + /** + * invalid sku info + */ + CODE_40070(40070, "invalid sku info"), + + /** + * invalid card type + */ + CODE_40071(40071, "invalid card type"), + + /** + * invalid location id + */ + CODE_40072(40072, "invalid location id"), + + /** + * invalid card id + */ + CODE_40073(40073, "invalid card id"), + + /** + * invalid pay template id + */ + CODE_40074(40074, "invalid pay template id"), + + /** + * invalid encrypt code + */ + CODE_40075(40075, "invalid encrypt code"), + + /** + * invalid color id + */ + CODE_40076(40076, "invalid color id"), + + /** + * invalid score type + */ + CODE_40077(40077, "invalid score type"), + + /** + * invalid card status + */ + CODE_40078(40078, "invalid card status"), + + /** + * invalid time + */ + CODE_40079(40079, "invalid time"), + + /** + * invalid card ext + */ + CODE_40080(40080, "invalid card ext"), + + /** + * invalid template_id + */ + CODE_40081(40081, "invalid template_id"), + + /** + * invalid banner picture size + */ + CODE_40082(40082, "invalid banner picture size"), + + /** + * invalid banner url size + */ + CODE_40083(40083, "invalid banner url size"), + + /** + * invalid button desc size + */ + CODE_40084(40084, "invalid button desc size"), + + /** + * invalid button url size + */ + CODE_40085(40085, "invalid button url size"), + + /** + * invalid sharelink logo size + */ + CODE_40086(40086, "invalid sharelink logo size"), + + /** + * invalid sharelink desc size + */ + CODE_40087(40087, "invalid sharelink desc size"), + + /** + * invalid sharelink title size + */ + CODE_40088(40088, "invalid sharelink title size"), + + /** + * invalid platform id + */ + CODE_40089(40089, "invalid platform id"), + + /** + * invalid request source (bad client ip) + */ + CODE_40090(40090, "invalid request source (bad client ip)"), + + /** + * invalid component ticket + */ + CODE_40091(40091, "invalid component ticket"), + + /** + * invalid remark name + */ + CODE_40092(40092, "invalid remark name"), + + /** + * not completely ok, err_item will return location_id=-1,check your required_fields in json. + */ + CODE_40093(40093, "not completely ok, err_item will return location_id=-1,check your required_fields in json."), + + /** + * invalid component credential + */ + CODE_40094(40094, "invalid component credential"), + + /** + * bad source of caller + */ + CODE_40095(40095, "bad source of caller"), + + /** + * invalid biztype + */ + CODE_40096(40096, "invalid biztype"), + + /** + * 参数错误 invalid args + */ + CODE_40097(40097, "参数错误"), + + /** + * invalid poiid + */ + CODE_40098(40098, "invalid poiid"), + + /** + * invalid code, this code has consumed. + */ + CODE_40099(40099, "invalid code, this code has consumed."), + + /** + * {@code invalid DateInfo, Make Sure OldDateInfoType==NewDateInfoType && NewBeginTime<=OldBeginTime && OldEndTime<= NewEndTime} + */ + CODE_40100(40100, "invalid DateInfo, Make Sure OldDateInfoType==NewDateInfoType && NewBeginTime<=OldBeginTime && OldEndTime<= NewEndTime"), + + /** + * missing parameter + */ + CODE_40101(40101, "missing parameter"), + + /** + * invalid industry id + */ + CODE_40102(40102, "invalid industry id"), + + /** + * invalid industry index + */ + CODE_40103(40103, "invalid industry index"), + + /** + * invalid category id + */ + CODE_40104(40104, "invalid category id"), + + /** + * invalid view type + */ + CODE_40105(40105, "invalid view type"), + + /** + * invalid user name + */ + CODE_40106(40106, "invalid user name"), + + /** + * invalid card id! 1,card status must verify ok; 2,this card must have location_id + */ + CODE_40107(40107, "invalid card id! 1,card status must verify ok; 2,this card must have location_id"), + + /** + * invalid client version + */ + CODE_40108(40108, "invalid client version"), + + /** + * {@code too many code size, must <= 100} + */ + CODE_40109(40109, "too many code size, must <= 100"), + + /** + * have empty code + */ + CODE_40110(40110, "have empty code"), + + /** + * have same code + */ + CODE_40111(40111, "have same code"), + + /** + * can not set bind openid + */ + CODE_40112(40112, "can not set bind openid"), + + /** + * unsupported file type + */ + CODE_40113(40113, "unsupported file type"), + + /** + * invalid index value + */ + CODE_40114(40114, "invalid index value"), + + /** + * invalid session from + */ + CODE_40115(40115, "invalid session from"), + + /** + * invalid code + */ + CODE_40116(40116, "invalid code"), + + /** + * 分组名字不合法 invalid button media_id size + */ + CODE_40117(40117, "分组名字不合法"), + + /** + * media_id 大小不合法 invalid sub button media_id size + */ + CODE_40118(40118, "media_id 大小不合法"), + + /** + * button 类型错误 invalid use button type + */ + CODE_40119(40119, "button 类型错误"), + + /** + * 子 button 类型错误 invalid use sub button type + */ + CODE_40120(40120, "子 button 类型错误"), + + /** + * 不合法的 media_id 类型 invalid media type in view_limited + */ + CODE_40121(40121, "不合法的 media_id 类型"), + + /** + * invalid card quantity + */ + CODE_40122(40122, "invalid card quantity"), + + /** + * invalid task_id + */ + CODE_40123(40123, "invalid task_id"), + + /** + * too many custom field! + */ + CODE_40124(40124, "too many custom field!"), + + /** + * 不合法的 AppID ,请开发者检查 AppID 的正确性,避免异常字符,注意大小写 invalid appsecret + */ + CODE_40125(40125, "不合法的 AppID ,请开发者检查 AppID 的正确性,避免异常字符,注意大小写"), + + /** + * invalid text size + */ + CODE_40126(40126, "invalid text size"), + + /** + * invalid user-card status! Hint: the card was given to user, but may be deleted or expired or set unavailable ! + */ + CODE_40127(40127, "invalid user-card status! Hint: the card was given to user, but may be deleted or expired or set unavailable !"), + + /** + * invalid media id! must be uploaded by api(cgi-bin/material/add_material) + */ + CODE_40128(40128, "invalid media id! must be uploaded by api(cgi-bin/material/add_material)"), + + /** + * invalid scene + */ + CODE_40129(40129, "invalid scene"), + + /** + * invalid openid list size, at least two openid + */ + CODE_40130(40130, "invalid openid list size, at least two openid"), + + /** + * out of limit of ticket + */ + CODE_40131(40131, "out of limit of ticket"), + + /** + * 微信号不合法 invalid username + */ + CODE_40132(40132, "微信号不合法"), + + /** + * invalid encryt data + */ + CODE_40133(40133, "invalid encryt data"), + + /** + * invalid not supply bonus, can not change card_id which supply bonus to be not supply + */ + CODE_40135(40135, "invalid not supply bonus, can not change card_id which supply bonus to be not supply"), + + /** + * {@code invalid use DepositCodeMode, make sure sku.quantity>DepositCode.quantity} + */ + CODE_40136(40136, "invalid use DepositCodeMode, make sure sku.quantity>DepositCode.quantity"), + + /** + * 不支持的图片格式 invalid image format + */ + CODE_40137(40137, "不支持的图片格式"), + + /** + * emphasis word can not be first neither remark + */ + CODE_40138(40138, "emphasis word can not be first neither remark"), + + /** + * invalid sub merchant id + */ + CODE_40139(40139, "invalid sub merchant id"), + + /** + * invalid sub merchant status + */ + CODE_40140(40140, "invalid sub merchant status"), + + /** + * invalid image url + */ + CODE_40141(40141, "invalid image url"), + + /** + * invalid sharecard parameters + */ + CODE_40142(40142, "invalid sharecard parameters"), + + /** + * invalid least cost info, should be 0 + */ + CODE_40143(40143, "invalid least cost info, should be 0"), + + /** + * 1)maybe share_card_list.num or consume_share_self_num too big; 2)maybe card_id_list also has self-card_id;3)maybe card_id_list has many different card_id;4)maybe both consume_share_self_num and share_card_list.num bigger than 0 + */ + CODE_40144(40144, "1)maybe share_card_list.num or consume_share_self_num too big; 2)maybe card_id_list also has self-card_id;3)maybe card_id_list has many different card_id;4)maybe both consume_share_self_num and share_card_list.num bigger than 0"), + + /** + * invalid update! Can not both set PayCell and CenterCellInfo(include: center_title, center_sub_title, center_url). + */ + CODE_40145(40145, "invalid update! Can not both set PayCell and CenterCellInfo(include: center_title, center_sub_title, center_url)."), + + /** + * invalid openid! card may be marked by other user! + */ + CODE_40146(40146, "invalid openid! card may be marked by other user!"), + + /** + * invalid consume! Consume time overranging restricts. + */ + CODE_40147(40147, "invalid consume! Consume time overranging restricts."), + + /** + * invalid friends card type + */ + CODE_40148(40148, "invalid friends card type"), + + /** + * invalid use time limit + */ + CODE_40149(40149, "invalid use time limit"), + + /** + * invalid card parameters + */ + CODE_40150(40150, "invalid card parameters"), + + /** + * invalid card info, text/pic hit antispam + */ + CODE_40151(40151, "invalid card info, text/pic hit antispam"), + + /** + * invalid group id + */ + CODE_40152(40152, "invalid group id"), + + /** + * self consume cell for friends card must need verify code + */ + CODE_40153(40153, "self consume cell for friends card must need verify code"), + + /** + * invalid voip parameters + */ + CODE_40154(40154, "invalid voip parameters"), + + /** + * 请勿添加其他公众号的主页链接 please don't contain other home page url + */ + CODE_40155(40155, "请勿添加其他公众号的主页链接"), + + /** + * invalid face recognize parameters + */ + CODE_40156(40156, "invalid face recognize parameters"), + + /** + * invalid picture, has no face + */ + CODE_40157(40157, "invalid picture, has no face"), + + /** + * invalid use_custom_code, need be false + */ + CODE_40158(40158, "invalid use_custom_code, need be false"), + + /** + * invalid length for path, or the data is not json string + */ + CODE_40159(40159, "invalid length for path, or the data is not json string"), + + /** + * invalid image file + */ + CODE_40160(40160, "invalid image file"), + + /** + * image file not match + */ + CODE_40161(40161, "image file not match"), + + /** + * invalid lifespan + */ + CODE_40162(40162, "invalid lifespan"), + + /** + * oauth_code已使用 code been used + */ + CODE_40163(40163, "oauth_code已使用"), + + /** + * invalid ip, not in whitelist + */ + CODE_40164(40164, "invalid ip, not in whitelist"), + + /** + * invalid weapp pagepath + */ + CODE_40165(40165, "invalid weapp pagepath"), + + /** + * invalid weapp appid + */ + CODE_40166(40166, "invalid weapp appid"), + + /** + * there is no relation with plugin appid + */ + CODE_40167(40167, "there is no relation with plugin appid"), + + /** + * unlinked weapp card + */ + CODE_40168(40168, "unlinked weapp card"), + + /** + * invalid length for scene, or the data is not json string + */ + CODE_40169(40169, "invalid length for scene, or the data is not json string"), + + /** + * args count exceed count limit + */ + CODE_40170(40170, "args count exceed count limit"), + + /** + * product id can not empty and the length cannot exceed 32 + */ + CODE_40171(40171, "product id can not empty and the length cannot exceed 32"), + + /** + * can not have same product id + */ + CODE_40172(40172, "can not have same product id"), + + /** + * there is no bind relation + */ + CODE_40173(40173, "there is no bind relation"), + + /** + * not card user + */ + CODE_40174(40174, "not card user"), + + /** + * invalid material id + */ + CODE_40175(40175, "invalid material id"), + + /** + * invalid template id + */ + CODE_40176(40176, "invalid template id"), + + /** + * invalid product id + */ + CODE_40177(40177, "invalid product id"), + + /** + * invalid sign + */ + CODE_40178(40178, "invalid sign"), + + /** + * Function is adjusted, rules are not allowed to add or update + */ + CODE_40179(40179, "Function is adjusted, rules are not allowed to add or update"), + + /** + * invalid client tmp token + */ + CODE_40180(40180, "invalid client tmp token"), + + /** + * invalid opengid + */ + CODE_40181(40181, "invalid opengid"), + + /** + * invalid pack_id + */ + CODE_40182(40182, "invalid pack_id"), + + /** + * invalid product_appid, product_appid should bind with wxa_appid + */ + CODE_40183(40183, "invalid product_appid, product_appid should bind with wxa_appid"), + + /** + * invalid url path + */ + CODE_40184(40184, "invalid url path"), + + /** + * invalid auth_token, or auth_token is expired + */ + CODE_40185(40185, "invalid auth_token, or auth_token is expired"), + + /** + * invalid delegate + */ + CODE_40186(40186, "invalid delegate"), + + /** + * invalid ip + */ + CODE_40187(40187, "invalid ip"), + + /** + * invalid scope + */ + CODE_40188(40188, "invalid scope"), + + /** + * invalid width + */ + CODE_40189(40189, "invalid width"), + + /** + * invalid delegate time + */ + CODE_40190(40190, "invalid delegate time"), + + /** + * invalid pic_url + */ + CODE_40191(40191, "invalid pic_url"), + + /** + * invalid author in news + */ + CODE_40192(40192, "invalid author in news"), + + /** + * invalid recommend length + */ + CODE_40193(40193, "invalid recommend length"), + + /** + * illegal recommend + */ + CODE_40194(40194, "illegal recommend"), + + /** + * invalid show_num + */ + CODE_40195(40195, "invalid show_num"), + + /** + * invalid smartmsg media_id + */ + CODE_40196(40196, "invalid smartmsg media_id"), + + /** + * invalid smartmsg media num + */ + CODE_40197(40197, "invalid smartmsg media num"), + + /** + * invalid default msg article size, must be same as show_num + */ + CODE_40198(40198, "invalid default msg article size, must be same as show_num"), + + /** + * 运单 ID 不存在,未查到运单 waybill_id not found + */ + CODE_40199(40199, "运单 ID 不存在,未查到运单"), + + /** + * invalid account type + */ + CODE_40200(40200, "invalid account type"), + + /** + * invalid check url + */ + CODE_40201(40201, "invalid check url"), + + /** + * invalid check action + */ + CODE_40202(40202, "invalid check action"), + + /** + * invalid check operator + */ + CODE_40203(40203, "invalid check operator"), + + /** + * can not delete wash or rumor article + */ + CODE_40204(40204, "can not delete wash or rumor article"), + + /** + * invalid check keywords string + */ + CODE_40205(40205, "invalid check keywords string"), + + /** + * invalid check begin stamp + */ + CODE_40206(40206, "invalid check begin stamp"), + + /** + * invalid check alive seconds + */ + CODE_40207(40207, "invalid check alive seconds"), + + /** + * invalid check notify id + */ + CODE_40208(40208, "invalid check notify id"), + + /** + * invalid check notify msg + */ + CODE_40209(40209, "invalid check notify msg"), + + /** + * pages 中的path参数不存在或为空 invalid check wxa path + */ + CODE_40210(40210, "pages 中的path参数不存在或为空"), + + /** + * invalid scope_data + */ + CODE_40211(40211, "invalid scope_data"), + + /** + * {@code paegs 当中存在不合法的query,query格式遵循URL标准,即k1=v1&k2=v2 invalid query} + */ + CODE_40212(40212, "paegs 当中存在不合法的query,query格式遵循URL标准,即k1=v1&k2=v2"), + + /** + * invalid href tag + */ + CODE_40213(40213, "invalid href tag"), + + /** + * invalid href text + */ + CODE_40214(40214, "invalid href text"), + + /** + * invalid image count + */ + CODE_40215(40215, "invalid image count"), + + /** + * invalid desc + */ + CODE_40216(40216, "invalid desc"), + + /** + * invalid video count + */ + CODE_40217(40217, "invalid video count"), + + /** + * invalid video id + */ + CODE_40218(40218, "invalid video id"), + + /** + * pages不存在或者参数为空 pages is empty + */ + CODE_40219(40219, "pages不存在或者参数为空"), + + /** + * data_list is empty + */ + CODE_40220(40220, "data_list is empty"), + + /** + * invalid Content-Encoding + */ + CODE_40221(40221, "invalid Content-Encoding"), + + /** + * invalid request idc domain + */ + CODE_40222(40222, "invalid request idc domain"), + + /** + * empty media cover, please check the media + */ + CODE_40229(40229, "媒体封面为空,请添加媒体封面"), + + /** + * 缺少 access_token 参数 access_token missing + */ + CODE_41001(41001, "缺少 access_token 参数"), + + /** + * 缺少 appid 参数 appid missing + */ + CODE_41002(41002, "缺少 appid 参数"), + + /** + * 缺少 refresh_token 参数 refresh_token missing + */ + CODE_41003(41003, "缺少 refresh_token 参数"), + + /** + * 缺少 secret 参数 appsecret missing + */ + CODE_41004(41004, "缺少 secret 参数"), + + /** + * 缺少多媒体文件数据,传输素材无视频或图片内容 media data missing + */ + CODE_41005(41005, "缺少多媒体文件数据,传输素材无视频或图片内容"), + + /** + * 缺少 media_id 参数 media_id missing + */ + CODE_41006(41006, "缺少 media_id 参数"), + + /** + * 缺少子菜单数据 sub_menu data missing + */ + CODE_41007(41007, "缺少子菜单数据"), + + /** + * 缺少 oauth code missing code + */ + CODE_41008(41008, "缺少 oauth code"), + + /** + * 缺少 openid missing openid + */ + CODE_41009(41009, "缺少 openid"), + + /** + * 缺失 url 参数 missing url + */ + CODE_41010(41010, "缺失 url 参数"), + + /** + * missing required fields! please check document and request json! + */ + CODE_41011(41011, "missing required fields! please check document and request json!"), + + /** + * missing card id + */ + CODE_41012(41012, "missing card id"), + + /** + * missing code + */ + CODE_41013(41013, "missing code"), + + /** + * missing ticket_class + */ + CODE_41014(41014, "missing ticket_class"), + + /** + * missing show_time + */ + CODE_41015(41015, "missing show_time"), + + /** + * missing screening_room + */ + CODE_41016(41016, "missing screening_room"), + + /** + * missing seat_number + */ + CODE_41017(41017, "missing seat_number"), + + /** + * missing component_appid + */ + CODE_41018(41018, "missing component_appid"), + + /** + * missing platform_secret + */ + CODE_41019(41019, "missing platform_secret"), + + /** + * missing platform_ticket + */ + CODE_41020(41020, "missing platform_ticket"), + + /** + * missing component_access_token + */ + CODE_41021(41021, "missing component_access_token"), + + /** + * missing "display" field + */ + CODE_41024(41024, "missing \"display\" field"), + + /** + * poi_list empty + */ + CODE_41025(41025, "poi_list empty"), + + /** + * missing image list info, text maybe empty + */ + CODE_41026(41026, "missing image list info, text maybe empty"), + + /** + * missing voip call key + */ + CODE_41027(41027, "missing voip call key"), + + /** + * invalid form id + */ + CODE_41028(41028, "invalid form id"), + + /** + * form id used count reach limit + */ + CODE_41029(41029, "form id used count reach limit"), + + /** + * page路径不正确,需要保证在现网版本小程序中存在,与app.json保持一致 invalid page + */ + CODE_41030(41030, "page路径不正确,需要保证在现网版本小程序中存在,与app.json保持一致"), + + /** + * the form id have been blocked! + */ + CODE_41031(41031, "the form id have been blocked!"), + + /** + * not allow to send message with submitted form id, for punishment + */ + CODE_41032(41032, "not allow to send message with submitted form id, for punishment"), + + /** + * 只允许通过api创建的小程序使用 invaid register type + */ + CODE_41033(41033, "只允许通过api创建的小程序使用"), + + /** + * not allow to send message with submitted form id, for punishment + */ + CODE_41034(41034, "not allow to send message with submitted form id, for punishment"), + + /** + * not allow to send message with prepay id, for punishment + */ + CODE_41035(41035, "not allow to send message with prepay id, for punishment"), + + /** + * appid ad cid + */ + CODE_41036(41036, "appid ad cid"), + + /** + * appid ad_mch_appid + */ + CODE_41037(41037, "appid ad_mch_appid"), + + /** + * appid pos_type + */ + CODE_41038(41038, "appid pos_type"), + + /** + * access_token 超时,请检查 access_token 的有效期,请参考基础支持 - 获取 access_token 中,对 access_token 的详细机制说明 access_token expired + */ + CODE_42001(42001, "access_token 超时,请检查 access_token 的有效期,请参考基础支持 - 获取 access_token 中,对 access_token 的详细机制说明"), + + /** + * refresh_token 超时 refresh_token expired + */ + CODE_42002(42002, "refresh_token 超时"), + + /** + * oauth_code 超时 code expired + */ + CODE_42003(42003, "oauth_code 超时"), + + /** + * plugin token expired + */ + CODE_42004(42004, "plugin token expired"), + + /** + * api usage expired + */ + CODE_42005(42005, "api usage expired"), + + /** + * component_access_token expired + */ + CODE_42006(42006, "component_access_token expired"), + + /** + * 用户修改微信密码, accesstoken 和 refreshtoken 失效,需要重新授权 access_token and refresh_token exception + */ + CODE_42007(42007, "用户修改微信密码, accesstoken 和 refreshtoken 失效,需要重新授权"), + + /** + * voip call key expired + */ + CODE_42008(42008, "voip call key expired"), + + /** + * client tmp token expired + */ + CODE_42009(42009, "client tmp token expired"), + + /** + * 需要 GET 请求 require GET method + */ + CODE_43001(43001, "需要 GET 请求"), + + /** + * 需要 POST 请求 require POST method + */ + CODE_43002(43002, "需要 POST 请求"), + + /** + * 需要 HTTPS 请求 require https + */ + CODE_43003(43003, "需要 HTTPS 请求"), + + /** + * 需要接收者关注 require subscribe + */ + CODE_43004(43004, "需要接收者关注"), + + /** + * 需要好友关系 require friend relations + */ + CODE_43005(43005, "需要好友关系"), + + /** + * require not block + */ + CODE_43006(43006, "require not block"), + + /** + * require bizuser authorize + */ + CODE_43007(43007, "require bizuser authorize"), + + /** + * require biz pay auth + */ + CODE_43008(43008, "require biz pay auth"), + + /** + * can not use custom code, need authorize + */ + CODE_43009(43009, "can not use custom code, need authorize"), + + /** + * can not use balance, need authorize + */ + CODE_43010(43010, "can not use balance, need authorize"), + + /** + * can not use bonus, need authorize + */ + CODE_43011(43011, "can not use bonus, need authorize"), + + /** + * can not use custom url, need authorize + */ + CODE_43012(43012, "can not use custom url, need authorize"), + + /** + * can not use shake card, need authorize + */ + CODE_43013(43013, "can not use shake card, need authorize"), + + /** + * require check agent + */ + CODE_43014(43014, "require check agent"), + + /** + * require authorize by wechat team to use this function! + */ + CODE_43015(43015, "require authorize by wechat team to use this function!"), + + /** + * 小程序未认证 require verify + */ + CODE_43016(43016, "小程序未认证"), + + /** + * require location id! + */ + CODE_43017(43017, "require location id!"), + + /** + * code has no been mark! + */ + CODE_43018(43018, "code has no been mark!"), + + /** + * 需要将接收者从黑名单中移除 require remove blacklist + */ + CODE_43019(43019, "需要将接收者从黑名单中移除"), + + /** + * change template too frequently + */ + CODE_43100(43100, "change template too frequently"), + + /** + * 用户拒绝接受消息,如果用户之前曾经订阅过,则表示用户取消了订阅关系 user refuse to accept the msg + */ + CODE_43101(43101, "用户拒绝接受消息,如果用户之前曾经订阅过,则表示用户取消了订阅关系"), + + /** + * the tempalte is not subscriptiontype + */ + CODE_43102(43102, "the tempalte is not subscriptiontype"), + + /** + * the api only can cancel the subscription + */ + CODE_43103(43103, "the api only can cancel the subscription"), + + /** + * this appid does not have permission + */ + CODE_43104(43104, "this appid does not have permission"), + + /** + * news has no binding relation with template_id + */ + CODE_43105(43105, "news has no binding relation with template_id"), + + /** + * not allow to add template, for punishment + */ + CODE_43106(43106, "not allow to add template, for punishment"), + + /** + * 多媒体文件为空 empty media data + */ + CODE_44001(44001, "多媒体文件为空"), + + /** + * POST 的数据包为空 empty post data + */ + CODE_44002(44002, "POST 的数据包为空"), + + /** + * 图文消息内容为空 empty news data + */ + CODE_44003(44003, "图文消息内容为空"), + + /** + * 文本消息内容为空 empty content + */ + CODE_44004(44004, "文本消息内容为空"), + + /** + * 空白的列表 empty list size + */ + CODE_44005(44005, "空白的列表"), + + /** + * empty file data + */ + CODE_44006(44006, "empty file data"), + + /** + * repeated msg id + */ + CODE_44007(44007, "repeated msg id"), + + /** + * image url size out of limit + */ + CODE_44997(44997, "image url size out of limit"), + + /** + * keyword string media size out of limit + */ + CODE_44998(44998, "keyword string media size out of limit"), + + /** + * keywords list size out of limit + */ + CODE_44999(44999, "keywords list size out of limit"), + + /** + * msg_id size out of limit + */ + CODE_45000(45000, "msg_id size out of limit"), + + /** + * 多媒体文件大小超过限制 media size out of limit + */ + CODE_45001(45001, "多媒体文件大小超过限制"), + + /** + * 消息内容超过限制 content size out of limit + */ + CODE_45002(45002, "消息内容超过限制"), + + /** + * 标题字段超过限制 title size out of limit + */ + CODE_45003(45003, "标题字段超过限制"), + + /** + * 描述字段超过限制 description size out of limit + */ + CODE_45004(45004, "描述字段超过限制"), + + /** + * 链接字段超过限制 url size out of limit + */ + CODE_45005(45005, "链接字段超过限制"), + + /** + * 图片链接字段超过限制 picurl size out of limit + */ + CODE_45006(45006, "图片链接字段超过限制"), + + /** + * 语音播放时间超过限制 playtime out of limit + */ + CODE_45007(45007, "语音播放时间超过限制"), + + /** + * 图文消息超过限制 article size out of limit + */ + CODE_45008(45008, "图文消息超过限制"), + + /** + * 接口调用超过限制 reach max api daily quota limit + */ + CODE_45009(45009, "接口调用超过限制"), + + /** + * 创建菜单个数超过限制 create menu limit + */ + CODE_45010(45010, "创建菜单个数超过限制"), + + /** + * API 调用太频繁,请稍候再试 api minute-quota reach limit, must slower, retry next minute + */ + CODE_45011(45011, "API 调用太频繁,请稍候再试"), + + /** + * 模板大小超过限制 template size out of limit + */ + CODE_45012(45012, "模板大小超过限制"), + + /** + * too many template args + */ + CODE_45013(45013, "too many template args"), + + /** + * template message size out of limit + */ + CODE_45014(45014, "template message size out of limit"), + + /** + * 回复时间超过限制 response out of time limit or subscription is canceled + */ + CODE_45015(45015, "回复时间超过限制"), + + /** + * 系统分组,不允许修改 can't modify sys group + */ + CODE_45016(45016, "系统分组,不允许修改"), + + /** + * 分组名字过长 can't set group name too long sys group + */ + CODE_45017(45017, "分组名字过长"), + + /** + * 分组数量超过上限 too many group now, no need to add new + */ + CODE_45018(45018, "分组数量超过上限"), + + /** + * too many openid, please input less + */ + CODE_45019(45019, "too many openid, please input less"), + + /** + * too many image, please input less + */ + CODE_45020(45020, "too many image, please input less"), + + /** + * some argument may be out of length limit! please check document and request json! + */ + CODE_45021(45021, "some argument may be out of length limit! please check document and request json!"), + + /** + * bonus is out of limit + */ + CODE_45022(45022, "bonus is out of limit"), + + /** + * balance is out of limit + */ + CODE_45023(45023, "balance is out of limit"), + + /** + * rank template number is out of limit + */ + CODE_45024(45024, "rank template number is out of limit"), + + /** + * poiid count is out of limit + */ + CODE_45025(45025, "poiid count is out of limit"), + + /** + * template num exceeds limit + */ + CODE_45026(45026, "template num exceeds limit"), + + /** + * template conflict with industry + */ + CODE_45027(45027, "template conflict with industry"), + + /** + * has no masssend quota + */ + CODE_45028(45028, "has no masssend quota"), + + /** + * qrcode count out of limit + */ + CODE_45029(45029, "qrcode count out of limit"), + + /** + * limit cardid, not support this function + */ + CODE_45030(45030, "limit cardid, not support this function"), + + /** + * stock is out of limit + */ + CODE_45031(45031, "stock is out of limit"), + + /** + * not inner ip for special acct in white-list + */ + CODE_45032(45032, "not inner ip for special acct in white-list"), + + /** + * user get card num is out of get_limit + */ + CODE_45033(45033, "user get card num is out of get_limit"), + + /** + * media file count is out of limit + */ + CODE_45034(45034, "media file count is out of limit"), + + /** + * access clientip is not registered, not in ip-white-list + */ + CODE_45035(45035, "access clientip is not registered, not in ip-white-list"), + + /** + * User receive announcement limit + */ + CODE_45036(45036, "User receive announcement limit"), + + /** + * user out of time limit or never talked in tempsession + */ + CODE_45037(45037, "user out of time limit or never talked in tempsession"), + + /** + * user subscribed, cannot use tempsession api + */ + CODE_45038(45038, "user subscribed, cannot use tempsession api"), + + /** + * card_list_size out of limit + */ + CODE_45039(45039, "card_list_size out of limit"), + + /** + * reach max monthly quota limit + */ + CODE_45040(45040, "reach max monthly quota limit"), + + /** + * this card reach total sku quantity limit! + */ + CODE_45041(45041, "this card reach total sku quantity limit!"), + + /** + * limit card type, this card type can NOT create by sub merchant + */ + CODE_45042(45042, "limit card type, this card type can NOT create by sub merchant"), + + /** + * can not set share_friends=true because has no Abstract Or Text_Img_List has no img Or image url not valid + */ + CODE_45043(45043, "can not set share_friends=true because has no Abstract Or Text_Img_List has no img Or image url not valid"), + + /** + * icon url size in abstract is out of limit + */ + CODE_45044(45044, "icon url size in abstract is out of limit"), + + /** + * unauthorized friends card, please contact administrator + */ + CODE_45045(45045, "unauthorized friends card, please contact administrator"), + + /** + * operate field conflict, CenterCell, PayCell, SelfConsumeCell conflict + */ + CODE_45046(45046, "operate field conflict, CenterCell, PayCell, SelfConsumeCell conflict"), + + /** + * 客服接口下行条数超过上限 out of response count limit + */ + CODE_45047(45047, "客服接口下行条数超过上限"), + + /** + * menu use invalid type + */ + CODE_45048(45048, "menu use invalid type"), + + /** + * ivr use invalid type + */ + CODE_45049(45049, "ivr use invalid type"), + + /** + * custom msg use invalid type + */ + CODE_45050(45050, "custom msg use invalid type"), + + /** + * template msg use invalid link + */ + CODE_45051(45051, "template msg use invalid link"), + + /** + * masssend msg use invalid type + */ + CODE_45052(45052, "masssend msg use invalid type"), + + /** + * exceed consume verify code size + */ + CODE_45053(45053, "exceed consume verify code size"), + + /** + * below consume verify code size + */ + CODE_45054(45054, "below consume verify code size"), + + /** + * the code is not in consume verify code charset + */ + CODE_45055(45055, "the code is not in consume verify code charset"), + + /** + * too many tag now, no need to add new + */ + CODE_45056(45056, "too many tag now, no need to add new"), + + /** + * can't delete the tag that has too many fans + */ + CODE_45057(45057, "can't delete the tag that has too many fans"), + + /** + * can't modify sys tag + */ + CODE_45058(45058, "can't modify sys tag"), + + /** + * can not tagging one user too much + */ + CODE_45059(45059, "can not tagging one user too much"), + + /** + * media is applied in ivr or menu, can not be deleted + */ + CODE_45060(45060, "media is applied in ivr or menu, can not be deleted"), + + /** + * maybe the update frequency is too often, please try again + */ + CODE_45061(45061, "maybe the update frequency is too often, please try again"), + + /** + * has agreement ad. please use mp.weixin.qq.com + */ + CODE_45062(45062, "has agreement ad. please use mp.weixin.qq.com"), + + /** + * accesstoken is not xiaochengxu + */ + CODE_45063(45063, "accesstoken is not xiaochengxu"), + + /** + * 创建菜单包含未关联的小程序 no permission to use weapp in menu + */ + CODE_45064(45064, "创建菜单包含未关联的小程序"), + + /** + * 相同 clientmsgid 已存在群发记录,返回数据中带有已存在的群发任务的 msgid clientmsgid exist + */ + CODE_45065(45065, "相同 clientmsgid 已存在群发记录,返回数据中带有已存在的群发任务的 msgid"), + + /** + * 相同 clientmsgid 重试速度过快,请间隔1分钟重试 same clientmsgid retry too fast + */ + CODE_45066(45066, "相同 clientmsgid 重试速度过快,请间隔1分钟重试"), + + /** + * clientmsgid 长度超过限制 clientmsgid size out of limit + */ + CODE_45067(45067, "clientmsgid 长度超过限制"), + + /** + * file size out of limit + */ + CODE_45068(45068, "file size out of limit"), + + /** + * product list size out of limit + */ + CODE_45069(45069, "product list size out of limit"), + + /** + * the business account have been created + */ + CODE_45070(45070, "the business account have been created"), + + /** + * business account not found + */ + CODE_45071(45071, "business account not found"), + + /** + * command字段取值不对 invalid command + */ + CODE_45072(45072, "command字段取值不对"), + + /** + * not inner vip for sns in white list + */ + CODE_45073(45073, "not inner vip for sns in white list"), + + /** + * material list size out of limit, you should delete the useless material + */ + CODE_45074(45074, "material list size out of limit, you should delete the useless material"), + + /** + * invalid keyword id + */ + CODE_45075(45075, "invalid keyword id"), + + /** + * invalid count + */ + CODE_45076(45076, "invalid count"), + + /** + * number of business account reach limit + */ + CODE_45077(45077, "number of business account reach limit"), + + /** + * nickname is illegal! + */ + CODE_45078(45078, "nickname is illegal!"), + + /** + * nickname is forbidden!(matched forbidden keyword) + */ + CODE_45079(45079, "nickname is forbidden!(matched forbidden keyword)"), + + /** + * 下发输入状态,需要之前30秒内跟用户有过消息交互 need sending message to user, or recving message from user in the last 30 seconds before typing + */ + CODE_45080(45080, "下发输入状态,需要之前30秒内跟用户有过消息交互"), + + /** + * 已经在输入状态,不可重复下发 you are already typing + */ + CODE_45081(45081, "已经在输入状态,不可重复下发"), + + /** + * need icp license for the url domain + */ + CODE_45082(45082, "need icp license for the url domain"), + + /** + * the speed out of range + */ + CODE_45083(45083, "the speed out of range"), + + /** + * No speed message + */ + CODE_45084(45084, "No speed message"), + + /** + * speed server err + */ + CODE_45085(45085, "speed server err"), + + /** + * invalid attrbute 'data-miniprogram-appid' + */ + CODE_45086(45086, "invalid attrbute 'data-miniprogram-appid'"), + + /** + * customer service message from this account have been blocked! + */ + CODE_45087(45087, "customer service message from this account have been blocked!"), + + /** + * action size out of limit + */ + CODE_45088(45088, "action size out of limit"), + + /** + * expired + */ + CODE_45089(45089, "expired"), + + /** + * invalid group msg ticket + */ + CODE_45090(45090, "invalid group msg ticket"), + + /** + * account_name is illegal! + */ + CODE_45091(45091, "account_name is illegal!"), + + /** + * no voice data + */ + CODE_45092(45092, "no voice data"), + + /** + * no quota to send msg + */ + CODE_45093(45093, "no quota to send msg"), + + /** + * not allow to send custom message when user enter session, for punishment + */ + CODE_45094(45094, "not allow to send custom message when user enter session, for punishment"), + + /** + * not allow to modify stock for the advertisement batch + */ + CODE_45095(45095, "not allow to modify stock for the advertisement batch"), + + /** + * invalid qrcode + */ + CODE_45096(45096, "invalid qrcode"), + + /** + * invalid qrcode prefix + */ + CODE_45097(45097, "invalid qrcode prefix"), + + /** + * msgmenu list size is out of limit + */ + CODE_45098(45098, "msgmenu list size is out of limit"), + + /** + * msgmenu item content size is out of limit + */ + CODE_45099(45099, "msgmenu item content size is out of limit"), + + /** + * invalid size of keyword_id_list + */ + CODE_45100(45100, "invalid size of keyword_id_list"), + + /** + * hit upload limit + */ + CODE_45101(45101, "hit upload limit"), + + /** + * this api have been blocked temporarily. + */ + CODE_45102(45102, "this api have been blocked temporarily."), + + /** + * This API has been unsupported + */ + CODE_45103(45103, "This API has been unsupported"), + + /** + * reach max domain quota limit + */ + CODE_45104(45104, "reach max domain quota limit"), + + /** + * the consume verify code not found + */ + CODE_45154(45154, "the consume verify code not found"), + + /** + * the consume verify code is existed + */ + CODE_45155(45155, "the consume verify code is existed"), + + /** + * the consume verify code's length not invalid + */ + CODE_45156(45156, "the consume verify code's length not invalid"), + + /** + * invalid tag name + */ + CODE_45157(45157, "invalid tag name"), + + /** + * tag name too long + */ + CODE_45158(45158, "tag name too long"), + + /** + * invalid tag id + */ + CODE_45159(45159, "invalid tag id"), + + /** + * invalid category to create card + */ + CODE_45160(45160, "invalid category to create card"), + + /** + * this video id must be generated by calling upload api + */ + CODE_45161(45161, "this video id must be generated by calling upload api"), + + /** + * invalid type + */ + CODE_45162(45162, "invalid type"), + + /** + * invalid sort_method + */ + CODE_45163(45163, "invalid sort_method"), + + /** + * invalid offset + */ + CODE_45164(45164, "invalid offset"), + + /** + * invalid limit + */ + CODE_45165(45165, "invalid limit"), + + /** + * invalid content + */ + CODE_45166(45166, "invalid content"), + + /** + * invalid voip call key + */ + CODE_45167(45167, "invalid voip call key"), + + /** + * keyword in blacklist + */ + CODE_45168(45168, "keyword in blacklist"), + + /** + * part or whole of the requests from the very app is temporary blocked by supervisor + */ + CODE_45501(45501, "part or whole of the requests from the very app is temporary blocked by supervisor"), + + /** + * 不存在媒体数据,media_id 不存在 media data no exist + */ + CODE_46001(46001, "不存在媒体数据,media_id 不存在"), + + /** + * 不存在的菜单版本 menu version no exist + */ + CODE_46002(46002, "不存在的菜单版本"), + + /** + * 不存在的菜单数据 menu no exist + */ + CODE_46003(46003, "不存在的菜单数据"), + + /** + * 不存在的用户 user no exist + */ + CODE_46004(46004, "不存在的用户"), + + /** + * poi no exist + */ + CODE_46005(46005, "poi no exist"), + + /** + * voip file not exist + */ + CODE_46006(46006, "voip file not exist"), + + /** + * file being transcoded, please try later + */ + CODE_46007(46007, "file being transcoded, please try later"), + + /** + * result id not exist + */ + CODE_46008(46008, "result id not exist"), + + /** + * there is no user data + */ + CODE_46009(46009, "there is no user data"), + + /** + * this api have been not supported since 2020-01-11 00:00:00, please use new api(subscribeMessage)! + */ + CODE_46101(46101, "this api have been not supported since 2020-01-11 00:00:00, please use new api(subscribeMessage)!"), + + /** + * 解析 JSON/XML 内容错误 data format error + */ + CODE_47001(47001, "解析 JSON/XML 内容错误"), + + /** + * data format error, do NOT use json unicode encode (\\uxxxx\\uxxxx), please use utf8 encoded text! + */ + CODE_47002(47002, "data format error, do NOT use json unicode encode (\\uxxxx\\uxxxx), please use utf8 encoded text!"), + + /** + * 模板参数不准确,可能为空或者不满足规则,errmsg会提示具体是哪个字段出错 argument invalid! + */ + CODE_47003(47003, "模板参数不准确,可能为空或者不满足规则,errmsg会提示具体是哪个字段出错"), + + /** + * 每次提交的页面数超过1000(备注:每次提交页面数应小于或等于1000) submit pages count more than each quota + */ + CODE_47004(47004, "每次提交的页面数超过1000(备注:每次提交页面数应小于或等于1000)"), + + /** + * tabbar no exist + */ + CODE_47005(47005, "tabbar no exist"), + + /** + * 当天提交页面数达到了配额上限,请明天再试 submit pages count reach daily limit, please try tomorrow + */ + CODE_47006(47006, "当天提交页面数达到了配额上限,请明天再试"), + + /** + * 搜索结果总数超过了1000条 search results count more than limit + */ + CODE_47101(47101, "搜索结果总数超过了1000条"), + + /** + * next_page_info参数错误 next_page_info error + */ + CODE_47102(47102, "next_page_info参数错误"), + + /** + * 参数 activity_id 错误 activity_id error + */ + CODE_47501(47501, "参数 activity_id 错误"), + + /** + * 参数 target_state 错误 target_state error + */ + CODE_47502(47502, "参数 target_state 错误"), + + /** + * 参数 version_type 错误 version_type error + */ + CODE_47503(47503, "参数 version_type 错误"), + + /** + * activity_id activity_id expired time + */ + CODE_47504(47504, "activity_id"), + + /** + * api 功能未授权,请确认公众号已获得该接口,可以在公众平台官网 - 开发者中心页中查看接口权限 api unauthorized + */ + CODE_48001(48001, "api 功能未授权,请确认公众号已获得该接口,可以在公众平台官网 - 开发者中心页中查看接口权限"), + + /** + * 粉丝拒收消息(粉丝在公众号选项中,关闭了 “ 接收消息 ” ) user block receive message + */ + CODE_48002(48002, "粉丝拒收消息(粉丝在公众号选项中,关闭了 “ 接收消息 ” )"), + + /** + * user not agree mass-send protocol + */ + CODE_48003(48003, "user not agree mass-send protocol"), + + /** + * api 接口被封禁,请登录 mp.weixin.qq.com 查看详情 api forbidden for irregularities, view detail on mp.weixin.qq.com + */ + CODE_48004(48004, "api 接口被封禁,请登录 mp.weixin.qq.com 查看详情"), + + /** + * api 禁止删除被自动回复和自定义菜单引用的素材 forbid to delete material used by auto-reply or menu + */ + CODE_48005(48005, "api 禁止删除被自动回复和自定义菜单引用的素材"), + + /** + * api 禁止清零调用次数,因为清零次数达到上限 forbid to clear quota because of reaching the limit + */ + CODE_48006(48006, "api 禁止清零调用次数,因为清零次数达到上限"), + + /** + * forbid to use other's voip call key + */ + CODE_48007(48007, "forbid to use other's voip call key"), + + /** + * 没有该类型消息的发送权限 no permission for this msgtype + */ + CODE_48008(48008, "没有该类型消息的发送权限"), + + /** + * this api is expired + */ + CODE_48009(48009, "this api is expired"), + + /** + * forbid to modify the material, please see more information on mp.weixin.qq.com + */ + CODE_48010(48010, "forbid to modify the material, please see more information on mp.weixin.qq.com"), + + /** + * disabled template id + */ + CODE_48011(48011, "disabled template id"), + + /** + * invalid token + */ + CODE_48012(48012, "invalid token"), + + /** + * 该视频非新接口上传,不能用于视频消息群发 + */ + CODE_48013(48013, "该视频非新接口上传,不能用于视频消息群发"), + + /** + * 该视频审核状态异常,请检查后重试 + */ + CODE_48014(48014, "该视频审核状态异常,请检查后重试"), + + /** + * 该账号无留言功能权限 + */ + CODE_48015(48015, "该账号无留言功能权限"), + + /** + * 该账号不满足智能配置"观看更多"视频条件 + */ + CODE_48016(48016, "该账号不满足智能配置\"观看更多\"视频条件"), + + /** + * not same appid with appid of access_token + */ + CODE_49001(49001, "not same appid with appid of access_token"), + + /** + * empty openid or transid + */ + CODE_49002(49002, "empty openid or transid"), + + /** + * not match openid with appid + */ + CODE_49003(49003, "not match openid with appid"), + + /** + * not match signature + */ + CODE_49004(49004, "not match signature"), + + /** + * not existed transid + */ + CODE_49005(49005, "not existed transid"), + + /** + * missing arg two_dim_code + */ + CODE_49006(49006, "missing arg two_dim_code"), + + /** + * invalid two_dim_code + */ + CODE_49007(49007, "invalid two_dim_code"), + + /** + * invalid qrcode + */ + CODE_49008(49008, "invalid qrcode"), + + /** + * missing arg qrcode + */ + CODE_49009(49009, "missing arg qrcode"), + + /** + * invalid partner id + */ + CODE_49010(49010, "invalid partner id"), + + /** + * not existed feedbackid + */ + CODE_49300(49300, "not existed feedbackid"), + + /** + * feedback exist + */ + CODE_49301(49301, "feedback exist"), + + /** + * feedback status already changed + */ + CODE_49302(49302, "feedback status already changed"), + + /** + * 用户未授权该 api api unauthorized or user unauthorized + */ + CODE_50001(50001, "用户未授权该 api"), + + /** + * 用户受限,可能是用户帐号被冻结或注销 user limited + */ + CODE_50002(50002, "用户受限,可能是用户帐号被冻结或注销"), + + /** + * user unexpected, maybe not in white list + */ + CODE_50003(50003, "user unexpected, maybe not in white list"), + + /** + * user not allow to use accesstoken, maybe for punishment + */ + CODE_50004(50004, "user not allow to use accesstoken, maybe for punishment"), + + /** + * 用户未关注公众号 user is unsubscribed + */ + CODE_50005(50005, "用户未关注公众号"), + + /** + * user has switched off friends authorization + */ + CODE_50006(50006, "user has switched off friends authorization"), + + /** + * enterprise father account not exist + */ + CODE_51000(51000, "enterprise father account not exist"), + + /** + * enterprise child account not belong to the father + */ + CODE_51001(51001, "enterprise child account not belong to the father"), + + /** + * enterprise verify message not correct + */ + CODE_51002(51002, "enterprise verify message not correct"), + + /** + * invalid enterprise child list size + */ + CODE_51003(51003, "invalid enterprise child list size"), + + /** + * not a enterprise father account + */ + CODE_51004(51004, "not a enterprise father account"), + + /** + * not a enterprise child account + */ + CODE_51005(51005, "not a enterprise child account"), + + /** + * invalid nick name + */ + CODE_51006(51006, "invalid nick name"), + + /** + * not a enterprise account + */ + CODE_51007(51007, "not a enterprise account"), + + /** + * invalid email + */ + CODE_51008(51008, "invalid email"), + + /** + * invalid pwd + */ + CODE_51009(51009, "invalid pwd"), + + /** + * repeated email + */ + CODE_51010(51010, "repeated email"), + + /** + * access deny + */ + CODE_51011(51011, "access deny"), + + /** + * need verify code + */ + CODE_51012(51012, "need verify code"), + + /** + * wrong verify code + */ + CODE_51013(51013, "wrong verify code"), + + /** + * need modify pwd + */ + CODE_51014(51014, "need modify pwd"), + + /** + * user not exist + */ + CODE_51015(51015, "user not exist"), + + /** + * tv info not exist + */ + CODE_51020(51020, "tv info not exist"), + + /** + * stamp crossed + */ + CODE_51021(51021, "stamp crossed"), + + /** + * invalid stamp range + */ + CODE_51022(51022, "invalid stamp range"), + + /** + * stamp not match date + */ + CODE_51023(51023, "stamp not match date"), + + /** + * empty program name + */ + CODE_51024(51024, "empty program name"), + + /** + * empty action url + */ + CODE_51025(51025, "empty action url"), + + /** + * program name size out of limit + */ + CODE_51026(51026, "program name size out of limit"), + + /** + * action url size out of limit + */ + CODE_51027(51027, "action url size out of limit"), + + /** + * invalid program name + */ + CODE_51028(51028, "invalid program name"), + + /** + * invalid action url + */ + CODE_51029(51029, "invalid action url"), + + /** + * invalid action id + */ + CODE_51030(51030, "invalid action id"), + + /** + * invalid action offset + */ + CODE_51031(51031, "invalid action offset"), + + /** + * empty action title + */ + CODE_51032(51032, "empty action title"), + + /** + * action title size out of limit + */ + CODE_51033(51033, "action title size out of limit"), + + /** + * empty action icon url + */ + CODE_51034(51034, "empty action icon url"), + + /** + * action icon url out of limit + */ + CODE_51035(51035, "action icon url out of limit"), + + /** + * pic is not from cdn + */ + CODE_52000(52000, "pic is not from cdn"), + + /** + * wechat price is not less than origin price + */ + CODE_52001(52001, "wechat price is not less than origin price"), + + /** + * category/sku is wrong + */ + CODE_52002(52002, "category/sku is wrong"), + + /** + * product id not existed + */ + CODE_52003(52003, "product id not existed"), + + /** + * category id is not exist, or doesn't has sub category + */ + CODE_52004(52004, "category id is not exist, or doesn't has sub category"), + + /** + * quantity is zero + */ + CODE_52005(52005, "quantity is zero"), + + /** + * area code is invalid + */ + CODE_52006(52006, "area code is invalid"), + + /** + * express template param is error + */ + CODE_52007(52007, "express template param is error"), + + /** + * express template id is not existed + */ + CODE_52008(52008, "express template id is not existed"), + + /** + * group name is empty + */ + CODE_52009(52009, "group name is empty"), + + /** + * group id is not existed + */ + CODE_52010(52010, "group id is not existed"), + + /** + * mod_action is invalid + */ + CODE_52011(52011, "mod_action is invalid"), + + /** + * shelf components count is greater than 20 + */ + CODE_52012(52012, "shelf components count is greater than 20"), + + /** + * shelf component is empty + */ + CODE_52013(52013, "shelf component is empty"), + + /** + * shelf id is not existed + */ + CODE_52014(52014, "shelf id is not existed"), + + /** + * order id is not existed + */ + CODE_52015(52015, "order id is not existed"), + + /** + * order filter param is invalid + */ + CODE_52016(52016, "order filter param is invalid"), + + /** + * order express param is invalid + */ + CODE_52017(52017, "order express param is invalid"), + + /** + * order delivery param is invalid + */ + CODE_52018(52018, "order delivery param is invalid"), + + /** + * brand name empty + */ + CODE_52019(52019, "brand name empty"), + + /** + * principal limit exceed + */ + CODE_53000(53000, "principal limit exceed"), + + /** + * principal in black list + */ + CODE_53001(53001, "principal in black list"), + + /** + * mobile limit exceed + */ + CODE_53002(53002, "mobile limit exceed"), + + /** + * idcard limit exceed + */ + CODE_53003(53003, "idcard limit exceed"), + + /** + * 名称格式不合法 nickname invalid + */ + CODE_53010(53010, "名称格式不合法"), + + /** + * 名称检测命中频率限制 check nickname too frequently + */ + CODE_53011(53011, "名称检测命中频率限制"), + + /** + * 禁止使用该名称 nickname ban + */ + CODE_53012(53012, "禁止使用该名称"), + + /** + * 公众号:名称与已有公众号名称重复;小程序:该名称与已有小程序名称重复 nickname has been occupied + */ + CODE_53013(53013, "公众号:名称与已有公众号名称重复;小程序:该名称与已有小程序名称重复"), + + /** + * 公众号:公众号已有{名称 A+}时,需与该帐号相同主体才可申请{名称 A};小程序:小程序已有{名称 A+}时,需与该帐号相同主体才可申请{名称 A} + */ + CODE_53014(53014, "公众号:公众号已有{名称 A+}时,需与该帐号相同主体才可申请{名称 A};小程序:小程序已有{名称 A+}时,需与该帐号相同主体才可申请{名称 A}"), + + /** + * 公众号:该名称与已有小程序名称重复,需与该小程序帐号相同主体才可申请;小程序:该名称与已有公众号名称重复,需与该公众号帐号相同主体才可申请 + */ + CODE_53015(53015, "公众号:该名称与已有小程序名称重复,需与该小程序帐号相同主体才可申请;小程序:该名称与已有公众号名称重复,需与该公众号帐号相同主体才可申请"), + + /** + * 公众号:该名称与已有多个小程序名称重复,暂不支持申请;小程序:该名称与已有多个公众号名称重复,暂不支持申请 + */ + CODE_53016(53016, "公众号:该名称与已有多个小程序名称重复,暂不支持申请;小程序:该名称与已有多个公众号名称重复,暂不支持申请"), + + /** + * 公众号:小程序已有{名称 A+}时,需与该帐号相同主体才可申请{名称 A};小程序:公众号已有{名称 A+}时,需与该帐号相同主体才可申请{名称 A} + */ + CODE_53017(53017, "公众号:小程序已有{名称 A+}时,需与该帐号相同主体才可申请{名称 A};小程序:公众号已有{名称 A+}时,需与该帐号相同主体才可申请{名称 A}"), + + /** + * 名称命中微信号 nickname hit alias + */ + CODE_53018(53018, "名称命中微信号"), + + /** + * 名称在保护期内 nickname protected by infringement + */ + CODE_53019(53019, "名称在保护期内"), + + /** + * order not found + */ + CODE_53100(53100, "订单不存在"), + + /** + * order already paid + */ + CODE_53101(53101, "已经支付的订单"), + + /** + * already has checking order, can not apply + */ + CODE_53102(53102, "已有检查单,不能申请"), + + /** + * order can not do refill + */ + CODE_53103(53103, "order can not do refill"), + + /** + * 本月功能介绍修改次数已用完 modify signature quota limit exceed + */ + CODE_53200(53200, "本月功能介绍修改次数已用完"), + + /** + * 功能介绍内容命中黑名单关键字 signature in black list, can not use + */ + CODE_53201(53201, "功能介绍内容命中黑名单关键字"), + + /** + * 本月头像修改次数已用完 modify avatar quota limit exceed + */ + CODE_53202(53202, "本月头像修改次数已用完"), + + /** + * can't be modified for the time being + */ + CODE_53203(53203, "暂时还不能修改"), + + /** + * signature invalid + */ + CODE_53204(53204, "无效签名"), + + /** + * 超出每月次数限制 + */ + CODE_53300(53300, "超出每月次数限制"), + + /** + * 超出可配置类目总数限制 + */ + CODE_53301(53301, "超出可配置类目总数限制"), + + /** + * 当前账号主体类型不允许设置此种类目 + */ + CODE_53302(53302, "当前账号主体类型不允许设置此种类目"), + + /** + * 提交的参数不合法 + */ + CODE_53303(53303, "提交的参数不合法"), + + /** + * 与已有类目重复 + */ + CODE_53304(53304, "与已有类目重复"), + + /** + * 包含未通过IPC校验的类目 + */ + CODE_53305(53305, "包含未通过IPC校验的类目"), + + /** + * 修改类目只允许修改类目资质,不允许修改类目ID + */ + CODE_53306(53306, "修改类目只允许修改类目资质,不允许修改类目ID"), + + /** + * 只有审核失败的类目允许修改 + */ + CODE_53307(53307, "只有审核失败的类目允许修改"), + + /** + * 审核中的类目不允许删除 + */ + CODE_53308(53308, "审核中的类目不允许删除"), + + /** + * 社交红包不允许删除 + */ + CODE_53309(53309, "社交红包不允许删除"), + + /** + * 类目超过上限,但是可以添加apply_reason参数申请更多类目 + */ + CODE_53310(53310, "类目超过上限,但是可以添加apply_reason参数申请更多类目"), + + /** + * 需要提交资料信息 + */ + CODE_53311(53311, "需要提交资料信息"), + + /** + * empty jsapi name + */ + CODE_60005(60005, "空的jsapi名称"), + + /** + * user cancel the auth + */ + CODE_60006(60006, "用户取消该授权"), + + /** + * invalid component type + */ + CODE_61000(61000, "无效的第三方类型"), + + /** + * component type and component appid is not match + */ + CODE_61001(61001, "第三方类型与第三方APPID不匹配"), + + /** + * the third appid is not open KF + */ + CODE_61002(61002, "第三方APPID没有开放客服"), + + /** + * component is not authorized by this account + */ + CODE_61003(61003, "帐号未授权"), + + /** + * api 功能未授权,请确认公众号/小程序已获得该接口,可以在公众平台官网 - 开发者中心页中查看接口权限 access clientip is not registered + */ + CODE_61004(61004, "api 功能未授权,请确认公众号/小程序已获得该接口,可以在公众平台官网 - 开发者中心页中查看接口权限"), + + /** + * component ticket is expired + */ + CODE_61005(61005, "ticket 已过期"), + + /** + * component ticket is invalid + */ + CODE_61006(61006, "无效 ticket"), + + /** + * api is unauthorized to component + */ + CODE_61007(61007, "接口未授权给第三方平台"), + + /** + * component req key is duplicated + */ + CODE_61008(61008, "第三方请求的key存在重复"), + + /** + * code is invalid + */ + CODE_61009(61009, "无效code"), + + /** + * code is expired + */ + CODE_61010(61010, "code已过期"), + + /** + * invalid component + */ + CODE_61011(61011, "无效的第三方平台"), + + /** + * invalid option name + */ + CODE_61012(61012, "无效的选项名称"), + + /** + * invalid option value + */ + CODE_61013(61013, "无效的选项值"), + + /** + * must use component token for component api + */ + CODE_61014(61014, "必须使用component接口的token"), + + /** + * must use biz account token for not component api + */ + CODE_61015(61015, "必须使用商业帐号token,而不是component api"), + + /** + * function category of API need be confirmed by component + */ + CODE_61016(61016, "function category of API need be confirmed by component"), + + /** + * function category is not authorized + */ + CODE_61017(61017, "function category is not authorized"), + + /** + * already confirm + */ + CODE_61018(61018, "已确认"), + + /** + * not need confirm + */ + CODE_61019(61019, "不需要确认"), + + /** + * err parameter + */ + CODE_61020(61020, "err parameter"), + + /** + * can't confirm + */ + CODE_61021(61021, "can't confirm"), + + /** + * can't resubmit + */ + CODE_61022(61022, "can't resubmit"), + + /** + * refresh_token is invalid + */ + CODE_61023(61023, "refresh_token is invalid"), + + /** + * must use api(api_component_token) to get token for component acct + */ + CODE_61024(61024, "must use api(api_component_token) to get token for component acct"), + + /** + * read-only option + */ + CODE_61025(61025, "read-only option"), + + /** + * register access deny + */ + CODE_61026(61026, "register access deny"), + + /** + * register limit exceed + */ + CODE_61027(61027, "register limit exceed"), + + /** + * component is unpublished + */ + CODE_61028(61028, "component is unpublished"), + + /** + * component need republish with base category + */ + CODE_61029(61029, "component need republish with base category"), + + /** + * component cancel authorization not allowed + */ + CODE_61030(61030, "component cancel authorization not allowed"), + + /** + * invalid realname type + */ + CODE_61051(61051, "invalid realname type"), + + /** + * need to be certified + */ + CODE_61052(61052, "need to be certified"), + + /** + * realname exceed limits + */ + CODE_61053(61053, "realname exceed limits"), + + /** + * realname in black list + */ + CODE_61054(61054, "realname in black list"), + + /** + * exceed quota per month + */ + CODE_61055(61055, "exceed quota per month"), + + /** + * copy_wx_verify is required option + */ + CODE_61056(61056, "copy_wx_verify is required option"), + + /** + * invalid ticket + */ + CODE_61058(61058, "invalid ticket"), + + /** + * overseas access deny + */ + CODE_61061(61061, "overseas access deny"), + + /** + * admin exceed limits + */ + CODE_61063(61063, "admin exceed limits"), + + /** + * admin in black list + */ + CODE_61064(61064, "admin in black list"), + + /** + * idcard exceed limits + */ + CODE_61065(61065, "idcard exceed limits"), + + /** + * idcard in black list + */ + CODE_61066(61066, "idcard in black list"), + + /** + * mobile exceed limits + */ + CODE_61067(61067, "mobile exceed limits"), + + /** + * mobile in black list + */ + CODE_61068(61068, "mobile in black list"), + + /** + * invalid admin + */ + CODE_61069(61069, "invalid admin"), + + /** + * name, idcard, wechat name not in accordance + */ + CODE_61070(61070, "name, idcard, wechat name not in accordance"), + + /** + * invalid url + */ + CODE_61100(61100, "invalid url"), + + /** + * invalid openid + */ + CODE_61101(61101, "invalid openid"), + + /** + * share relation not existed + */ + CODE_61102(61102, "share relation not existed"), + + /** + * product wording not set + */ + CODE_61200(61200, "product wording not set"), + + /** + * invalid base info + */ + CODE_61300(61300, "invalid base info"), + + /** + * invalid detail info + */ + CODE_61301(61301, "invalid detail info"), + + /** + * invalid action info + */ + CODE_61302(61302, "invalid action info"), + + /** + * brand info not exist + */ + CODE_61303(61303, "brand info not exist"), + + /** + * invalid product id + */ + CODE_61304(61304, "invalid product id"), + + /** + * invalid key info + */ + CODE_61305(61305, "invalid key info"), + + /** + * invalid appid + */ + CODE_61306(61306, "invalid appid"), + + /** + * invalid card id + */ + CODE_61307(61307, "invalid card id"), + + /** + * base info not exist + */ + CODE_61308(61308, "base info not exist"), + + /** + * detail info not exist + */ + CODE_61309(61309, "detail info not exist"), + + /** + * action info not exist + */ + CODE_61310(61310, "action info not exist"), + + /** + * invalid media info + */ + CODE_61311(61311, "invalid media info"), + + /** + * invalid buffer size + */ + CODE_61312(61312, "invalid buffer size"), + + /** + * invalid buffer + */ + CODE_61313(61313, "invalid buffer"), + + /** + * invalid qrcode extinfo + */ + CODE_61314(61314, "invalid qrcode extinfo"), + + /** + * invalid local ext info + */ + CODE_61315(61315, "invalid local ext info"), + + /** + * key conflict + */ + CODE_61316(61316, "key conflict"), + + /** + * ticket invalid + */ + CODE_61317(61317, "ticket invalid"), + + /** + * verify not pass + */ + CODE_61318(61318, "verify not pass"), + + /** + * category invalid + */ + CODE_61319(61319, "category invalid"), + + /** + * merchant info not exist + */ + CODE_61320(61320, "merchant info not exist"), + + /** + * cate id is a leaf node + */ + CODE_61321(61321, "cate id is a leaf node"), + + /** + * category id no permision + */ + CODE_61322(61322, "category id no permision"), + + /** + * barcode no permision + */ + CODE_61323(61323, "barcode no permision"), + + /** + * exceed max action num + */ + CODE_61324(61324, "exceed max action num"), + + /** + * brandinfo invalid store mgr type + */ + CODE_61325(61325, "brandinfo invalid store mgr type"), + + /** + * anti-spam blocked + */ + CODE_61326(61326, "anti-spam blocked"), + + /** + * comment reach limit + */ + CODE_61327(61327, "comment reach limit"), + + /** + * comment data is not the newest + */ + CODE_61328(61328, "comment data is not the newest"), + + /** + * comment hit ban word + */ + CODE_61329(61329, "comment hit ban word"), + + /** + * image already add + */ + CODE_61330(61330, "image already add"), + + /** + * image never add + */ + CODE_61331(61331, "image never add"), + + /** + * warning, image quanlity too low + */ + CODE_61332(61332, "warning, image quanlity too low"), + + /** + * warning, image simility too high + */ + CODE_61333(61333, "warning, image simility too high"), + + /** + * product not exists + */ + CODE_61334(61334, "product not exists"), + + /** + * key apply fail + */ + CODE_61335(61335, "key apply fail"), + + /** + * check status fail + */ + CODE_61336(61336, "check status fail"), + + /** + * product already exists + */ + CODE_61337(61337, "product already exists"), + + /** + * forbid delete + */ + CODE_61338(61338, "forbid delete"), + + /** + * firmcode claimed + */ + CODE_61339(61339, "firmcode claimed"), + + /** + * check firm info fail + */ + CODE_61340(61340, "check firm info fail"), + + /** + * too many white list uin + */ + CODE_61341(61341, "too many white list uin"), + + /** + * keystandard not match + */ + CODE_61342(61342, "keystandard not match"), + + /** + * keystandard error + */ + CODE_61343(61343, "keystandard error"), + + /** + * id map not exists + */ + CODE_61344(61344, "id map not exists"), + + /** + * invalid action code + */ + CODE_61345(61345, "invalid action code"), + + /** + * invalid actioninfo store + */ + CODE_61346(61346, "invalid actioninfo store"), + + /** + * invalid actioninfo media + */ + CODE_61347(61347, "invalid actioninfo media"), + + /** + * invalid actioninfo text + */ + CODE_61348(61348, "invalid actioninfo text"), + + /** + * invalid input data + */ + CODE_61350(61350, "invalid input data"), + + /** + * input data exceed max size + */ + CODE_61351(61351, "input data exceed max size"), + + /** + * kf_account error + */ + CODE_61400(61400, "kf_account error"), + + /** + * kf system alredy transfer + */ + CODE_61401(61401, "kf system alredy transfer"), + + /** + * 系统错误 (system error) + */ + CODE_61450(61450, "系统错误 (system error)"), + + /** + * 参数错误 (invalid parameter) + */ + CODE_61451(61451, "参数错误 (invalid parameter)"), + + /** + * 无效客服账号 (invalid kf_account) + */ + CODE_61452(61452, "无效客服账号 (invalid kf_account)"), + + /** + * 客服帐号已存在 (kf_account exsited) + */ + CODE_61453(61453, "客服帐号已存在 (kf_account exsited)"), + + /** + * 客服帐号名长度超过限制 ( 仅允许 10 个英文字符,不包括 @ 及 @ 后的公众号的微信号 )(invalid kf_acount length) + */ + CODE_61454(61454, "客服帐号名长度超过限制 ( 仅允许 10 个英文字符,不包括 @ 及 @ 后的公众号的微信号 )(invalid kf_acount length)"), + + /** + * 客服帐号名包含非法字符 ( 仅允许英文 + 数字 )(illegal character in kf_account) + */ + CODE_61455(61455, "客服帐号名包含非法字符 ( 仅允许英文 + 数字 )(illegal character in kf_account)"), + + /** + * 客服帐号个数超过限制 (10 个客服账号 )(kf_account count exceeded) + */ + CODE_61456(61456, "客服帐号个数超过限制 (10 个客服账号 )(kf_account count exceeded)"), + + /** + * 无效头像文件类型 (invalid file type) + */ + CODE_61457(61457, "无效头像文件类型 (invalid file type)"), + + /** + * 日期格式错误 date format error + */ + CODE_61500(61500, "日期格式错误"), + + /** + * date range error + */ + CODE_61501(61501, "date range error"), + + /** + * this is game miniprogram, data api is not supported + */ + CODE_61502(61502, "this is game miniprogram, data api is not supported"), + + /** + * data not ready, please try later + */ + CODE_61503(61503, "data not ready, please try later"), + + /** + * trying to access other's app + */ + CODE_62001(62001, "trying to access other's app"), + + /** + * app name already exists + */ + CODE_62002(62002, "app name already exists"), + + /** + * please provide at least one platform + */ + CODE_62003(62003, "please provide at least one platform"), + + /** + * invalid app name + */ + CODE_62004(62004, "invalid app name"), + + /** + * invalid app id + */ + CODE_62005(62005, "invalid app id"), + + /** + * 部分参数为空 some arguments is empty + */ + CODE_63001(63001, "部分参数为空"), + + /** + * 无效的签名 invalid signature + */ + CODE_63002(63002, "无效的签名"), + + /** + * invalid signature method + */ + CODE_63003(63003, "invalid signature method"), + + /** + * no authroize + */ + CODE_63004(63004, "no authroize"), + + /** + * gen ticket fail + */ + CODE_63149(63149, "gen ticket fail"), + + /** + * set ticket fail + */ + CODE_63152(63152, "set ticket fail"), + + /** + * shortid decode fail + */ + CODE_63153(63153, "shortid decode fail"), + + /** + * invalid status + */ + CODE_63154(63154, "invalid status"), + + /** + * invalid color + */ + CODE_63155(63155, "invalid color"), + + /** + * invalid tag + */ + CODE_63156(63156, "invalid tag"), + + /** + * invalid recommend + */ + CODE_63157(63157, "invalid recommend"), + + /** + * branditem out of limits + */ + CODE_63158(63158, "branditem out of limits"), + + /** + * retail_price empty + */ + CODE_63159(63159, "retail_price empty"), + + /** + * priceinfo invalid + */ + CODE_63160(63160, "priceinfo invalid"), + + /** + * antifake module num limit + */ + CODE_63161(63161, "antifake module num limit"), + + /** + * antifake native_type err + */ + CODE_63162(63162, "antifake native_type err"), + + /** + * antifake link not exists + */ + CODE_63163(63163, "antifake link not exists"), + + /** + * module type not exist + */ + CODE_63164(63164, "module type not exist"), + + /** + * module info not exist + */ + CODE_63165(63165, "module info not exist"), + + /** + * item is beding verified + */ + CODE_63166(63166, "item is beding verified"), + + /** + * item not published + */ + CODE_63167(63167, "item not published"), + + /** + * verify not pass + */ + CODE_63168(63168, "verify not pass"), + + /** + * already published + */ + CODE_63169(63169, "already published"), + + /** + * only banner or media + */ + CODE_63170(63170, "only banner or media"), + + /** + * card num limit + */ + CODE_63171(63171, "card num limit"), + + /** + * user num limit + */ + CODE_63172(63172, "user num limit"), + + /** + * text num limit + */ + CODE_63173(63173, "text num limit"), + + /** + * link card user sum limit + */ + CODE_63174(63174, "link card user sum limit"), + + /** + * detail info error + */ + CODE_63175(63175, "detail info error"), + + /** + * not this type + */ + CODE_63176(63176, "not this type"), + + /** + * src or secretkey or version or expired_time is wrong + */ + CODE_63177(63177, "src or secretkey or version or expired_time is wrong"), + + /** + * appid wrong + */ + CODE_63178(63178, "appid wrong"), + + /** + * openid num limit + */ + CODE_63179(63179, "openid num limit"), + + /** + * this app msg not found + */ + CODE_63180(63180, "this app msg not found"), + + /** + * get history app msg end + */ + CODE_63181(63181, "get history app msg end"), + + /** + * openid_list empty + */ + CODE_63182(63182, "openid_list empty"), + + /** + * unknown deeplink type + */ + CODE_65001(65001, "unknown deeplink type"), + + /** + * deeplink unauthorized + */ + CODE_65002(65002, "deeplink unauthorized"), + + /** + * bad deeplink + */ + CODE_65003(65003, "bad deeplink"), + + /** + * deeplinks of the very type are supposed to have short-life + */ + CODE_65004(65004, "deeplinks of the very type are supposed to have short-life"), + + /** + * invalid categories + */ + CODE_65104(65104, "invalid categories"), + + /** + * invalid photo url + */ + CODE_65105(65105, "invalid photo url"), + + /** + * poi audit state must be approved + */ + CODE_65106(65106, "poi audit state must be approved"), + + /** + * poi not allowed modify now + */ + CODE_65107(65107, "poi not allowed modify now"), + + /** + * invalid business name + */ + CODE_65109(65109, "invalid business name"), + + /** + * invalid address + */ + CODE_65110(65110, "invalid address"), + + /** + * invalid telephone + */ + CODE_65111(65111, "invalid telephone"), + + /** + * invalid city + */ + CODE_65112(65112, "invalid city"), + + /** + * invalid province + */ + CODE_65113(65113, "invalid province"), + + /** + * photo list empty + */ + CODE_65114(65114, "photo list empty"), + + /** + * poi_id is not exist + */ + CODE_65115(65115, "poi_id is not exist"), + + /** + * poi has been deleted + */ + CODE_65116(65116, "poi has been deleted"), + + /** + * cannot delete poi + */ + CODE_65117(65117, "cannot delete poi"), + + /** + * store status is invalid + */ + CODE_65118(65118, "store status is invalid"), + + /** + * lack of qualification for relevant principals + */ + CODE_65119(65119, "lack of qualification for relevant principals"), + + /** + * category info is not found + */ + CODE_65120(65120, "category info is not found"), + + /** + * room_name is empty, please check your input + */ + CODE_65201(65201, "room_name is empty, please check your input"), + + /** + * user_id is empty, please check your input + */ + CODE_65202(65202, "user_id is empty, please check your input"), + + /** + * invalid check ticket + */ + CODE_65203(65203, "invalid check ticket"), + + /** + * invalid check ticket opt code + */ + CODE_65204(65204, "invalid check ticket opt code"), + + /** + * check ticket out of time + */ + CODE_65205(65205, "check ticket out of time"), + + /** + * 不存在此 menuid 对应的个性化菜单 this menu is not conditionalmenu + */ + CODE_65301(65301, "不存在此 menuid 对应的个性化菜单"), + + /** + * 没有相应的用户 no such user + */ + CODE_65302(65302, "没有相应的用户"), + + /** + * 没有默认菜单,不能创建个性化菜单 there is no selfmenu, please create selfmenu first + */ + CODE_65303(65303, "没有默认菜单,不能创建个性化菜单"), + + /** + * MatchRule 信息为空 match rule empty + */ + CODE_65304(65304, "MatchRule 信息为空"), + + /** + * 个性化菜单数量受限 menu count limit + */ + CODE_65305(65305, "个性化菜单数量受限"), + + /** + * 不支持个性化菜单的帐号 conditional menu not support + */ + CODE_65306(65306, "不支持个性化菜单的帐号"), + + /** + * 个性化菜单信息为空 conditional menu is empty + */ + CODE_65307(65307, "个性化菜单信息为空"), + + /** + * 包含没有响应类型的 button exist empty button act + */ + CODE_65308(65308, "包含没有响应类型的 button"), + + /** + * 个性化菜单开关处于关闭状态 conditional menu switch is closed + */ + CODE_65309(65309, "个性化菜单开关处于关闭状态"), + + /** + * 填写了省份或城市信息,国家信息不能为空 region info: country is empty + */ + CODE_65310(65310, "填写了省份或城市信息,国家信息不能为空"), + + /** + * 填写了城市信息,省份信息不能为空 region info: province is empty + */ + CODE_65311(65311, "填写了城市信息,省份信息不能为空"), + + /** + * 不合法的国家信息 invalid country info + */ + CODE_65312(65312, "不合法的国家信息"), + + /** + * 不合法的省份信息 invalid province info + */ + CODE_65313(65313, "不合法的省份信息"), + + /** + * 不合法的城市信息 invalid city info + */ + CODE_65314(65314, "不合法的城市信息"), + + /** + * not fans + */ + CODE_65315(65315, "not fans"), + + /** + * 该公众号的菜单设置了过多的域名外跳(最多跳转到 3 个域名的链接) domain count reach limit + */ + CODE_65316(65316, "该公众号的菜单设置了过多的域名外跳(最多跳转到 3 个域名的链接)"), + + /** + * 不合法的 URL contain invalid url + */ + CODE_65317(65317, "不合法的 URL"), + + /** + * must use utf-8 charset + */ + CODE_65318(65318, "must use utf-8 charset"), + + /** + * not allow to create menu + */ + CODE_65319(65319, "not allow to create menu"), + + /** + * please enable new custom service, or wait for a while if you have enabled + */ + CODE_65400(65400, "please enable new custom service, or wait for a while if you have enabled"), + + /** + * invalid custom service account + */ + CODE_65401(65401, "invalid custom service account"), + + /** + * the custom service account need to bind a wechat user + */ + CODE_65402(65402, "the custom service account need to bind a wechat user"), + + /** + * illegal nickname + */ + CODE_65403(65403, "illegal nickname"), + + /** + * illegal custom service account + */ + CODE_65404(65404, "illegal custom service account"), + + /** + * custom service account number reach limit + */ + CODE_65405(65405, "custom service account number reach limit"), + + /** + * custom service account exists + */ + CODE_65406(65406, "custom service account exists"), + + /** + * the wechat user have been one of your workers + */ + CODE_65407(65407, "the wechat user have been one of your workers"), + + /** + * you have already invited the wechat user + */ + CODE_65408(65408, "you have already invited the wechat user"), + + /** + * wechat account invalid + */ + CODE_65409(65409, "wechat account invalid"), + + /** + * too many custom service accounts bound by the worker + */ + CODE_65410(65410, "too many custom service accounts bound by the worker"), + + /** + * a effective invitation to bind the custom service account exists + */ + CODE_65411(65411, "a effective invitation to bind the custom service account exists"), + + /** + * the custom service account have been bound by a wechat user + */ + CODE_65412(65412, "the custom service account have been bound by a wechat user"), + + /** + * no effective session for the customer + */ + CODE_65413(65413, "no effective session for the customer"), + + /** + * another worker is serving the customer + */ + CODE_65414(65414, "another worker is serving the customer"), + + /** + * the worker is not online + */ + CODE_65415(65415, "the worker is not online"), + + /** + * param invalid, please check + */ + CODE_65416(65416, "param invalid, please check"), + + /** + * it is too long from the starttime to endtime + */ + CODE_65417(65417, "it is too long from the starttime to endtime"), + + /** + * homepage not exists + */ + CODE_65450(65450, "homepage not exists"), + + /** + * invalid store type + */ + CODE_68002(68002, "invalid store type"), + + /** + * invalid store name + */ + CODE_68003(68003, "invalid store name"), + + /** + * invalid store wxa path + */ + CODE_68004(68004, "invalid store wxa path"), + + /** + * miss store wxa path + */ + CODE_68005(68005, "miss store wxa path"), + + /** + * invalid kefu type + */ + CODE_68006(68006, "invalid kefu type"), + + /** + * invalid kefu wxa path + */ + CODE_68007(68007, "invalid kefu wxa path"), + + /** + * invalid kefu phone number + */ + CODE_68008(68008, "invalid kefu phone number"), + + /** + * invalid sub mch id + */ + CODE_68009(68009, "invalid sub mch id"), + + /** + * store id has exist + */ + CODE_68010(68010, "store id has exist"), + + /** + * miss store name + */ + CODE_68011(68011, "miss store name"), + + /** + * miss create time + */ + CODE_68012(68012, "miss create time"), + + /** + * invalid status + */ + CODE_68013(68013, "invalid status"), + + /** + * invalid receiver info + */ + CODE_68014(68014, "invalid receiver info"), + + /** + * invalid product + */ + CODE_68015(68015, "invalid product"), + + /** + * invalid pay type + */ + CODE_68016(68016, "invalid pay type"), + + /** + * invalid fast mail no + */ + CODE_68017(68017, "invalid fast mail no"), + + /** + * invalid busi id + */ + CODE_68018(68018, "invalid busi id"), + + /** + * miss product sku + */ + CODE_68019(68019, "miss product sku"), + + /** + * invalid service type + */ + CODE_68020(68020, "invalid service type"), + + /** + * invalid service status + */ + CODE_68021(68021, "invalid service status"), + + /** + * invalid service_id + */ + CODE_68022(68022, "invalid service_id"), + + /** + * service_id has exist + */ + CODE_68023(68023, "service_id has exist"), + + /** + * miss service wxa path + */ + CODE_68024(68024, "miss service wxa path"), + + /** + * invalid product sku + */ + CODE_68025(68025, "invalid product sku"), + + /** + * invalid product spu + */ + CODE_68026(68026, "invalid product spu"), + + /** + * miss product spu + */ + CODE_68027(68027, "miss product spu"), + + /** + * can not find product spu and spu in order list + */ + CODE_68028(68028, "can not find product spu and spu in order list"), + + /** + * sku and spu duplicated + */ + CODE_68029(68029, "sku and spu duplicated"), + + /** + * busi_id has exist + */ + CODE_68030(68030, "busi_id has exist"), + + /** + * update fail + */ + CODE_68031(68031, "update fail"), + + /** + * busi_id not exist + */ + CODE_68032(68032, "busi_id not exist"), + + /** + * store no exist + */ + CODE_68033(68033, "store no exist"), + + /** + * miss product number + */ + CODE_68034(68034, "miss product number"), + + /** + * miss wxa order detail path + */ + CODE_68035(68035, "miss wxa order detail path"), + + /** + * there is no enough products to refund + */ + CODE_68036(68036, "there is no enough products to refund"), + + /** + * invalid refund info + */ + CODE_68037(68037, "invalid refund info"), + + /** + * shipped but no fast mail info + */ + CODE_68038(68038, "shipped but no fast mail info"), + + /** + * invalid wechat pay no + */ + CODE_68039(68039, "invalid wechat pay no"), + + /** + * all product has been refunded, the order can not be finished + */ + CODE_68040(68040, "all product has been refunded, the order can not be finished"), + + /** + * invalid service create time, it must bigger than the time of order + */ + CODE_68041(68041, "invalid service create time, it must bigger than the time of order"), + + /** + * invalid total cost, it must be smaller than the sum of product and shipping cost + */ + CODE_68042(68042, "invalid total cost, it must be smaller than the sum of product and shipping cost"), + + /** + * invalid role + */ + CODE_68043(68043, "invalid role"), + + /** + * invalid service_available args + */ + CODE_68044(68044, "invalid service_available args"), + + /** + * invalid order type + */ + CODE_68045(68045, "invalid order type"), + + /** + * invalid order deliver type + */ + CODE_68046(68046, "invalid order deliver type"), + + /** + * require store_id + */ + CODE_68500(68500, "require store_id"), + + /** + * invalid store_id + */ + CODE_68501(68501, "invalid store_id"), + + /** + * invalid parameter, parameter is zero or missing + */ + CODE_71001(71001, "invalid parameter, parameter is zero or missing"), + + /** + * invalid orderid, may be the other parameter not fit with orderid + */ + CODE_71002(71002, "invalid orderid, may be the other parameter not fit with orderid"), + + /** + * coin not enough + */ + CODE_71003(71003, "coin not enough"), + + /** + * card is expired + */ + CODE_71004(71004, "card is expired"), + + /** + * limit exe count + */ + CODE_71005(71005, "limit exe count"), + + /** + * {@code limit coin count, 1 <= coin_count <= 100000} + */ + CODE_71006(71006, "limit coin count, 1 <= coin_count <= 100000"), + + /** + * order finish + */ + CODE_71007(71007, "order finish"), + + /** + * order time out + */ + CODE_71008(71008, "order time out"), + + /** + * no match card + */ + CODE_72001(72001, "no match card"), + + /** + * mchid is not bind appid + */ + CODE_72002(72002, "mchid is not bind appid"), + + /** + * invalid card type, need member card + */ + CODE_72003(72003, "invalid card type, need member card"), + + /** + * mchid is occupied by the other appid + */ + CODE_72004(72004, "mchid is occupied by the other appid"), + + /** + * out of mchid size limit + */ + CODE_72005(72005, "out of mchid size limit"), + + /** + * invald title + */ + CODE_72006(72006, "invald title"), + + /** + * invalid reduce cost, can not less than 100 + */ + CODE_72007(72007, "invalid reduce cost, can not less than 100"), + + /** + * invalid least cost, most larger than reduce cost + */ + CODE_72008(72008, "invalid least cost, most larger than reduce cost"), + + /** + * invalid get limit, can not over 50 + */ + CODE_72009(72009, "invalid get limit, can not over 50"), + + /** + * invalid mchid + */ + CODE_72010(72010, "invalid mchid"), + + /** + * invalid activate_ticket.Maybe this ticket is not belong this AppId + */ + CODE_72011(72011, "invalid activate_ticket.Maybe this ticket is not belong this AppId"), + + /** + * activate_ticket has been expired + */ + CODE_72012(72012, "activate_ticket has been expired"), + + /** + * unauthorized order_id or authorization is expired + */ + CODE_72013(72013, "unauthorized order_id or authorization is expired"), + + /** + * task card share stock can not modify stock + */ + CODE_72014(72014, "task card share stock can not modify stock"), + + /** + * unauthorized create invoice + */ + CODE_72015(72015, "unauthorized create invoice"), + + /** + * unauthorized create member card + */ + CODE_72016(72016, "unauthorized create member card"), + + /** + * invalid invoice title + */ + CODE_72017(72017, "invalid invoice title"), + + /** + * duplicate order id, invoice had inserted to user + */ + CODE_72018(72018, "duplicate order id, invoice had inserted to user"), + + /** + * {@code limit msg operation card list size, must <= 5} + */ + CODE_72019(72019, "limit msg operation card list size, must <= 5"), + + /** + * limit consume in use limit + */ + CODE_72020(72020, "limit consume in use limit"), + + /** + * unauthorized create general card + */ + CODE_72021(72021, "unauthorized create general card"), + + /** + * user unexpected, please add user to white list + */ + CODE_72022(72022, "user unexpected, please add user to white list"), + + /** + * invoice has been lock by others + */ + CODE_72023(72023, "invoice has been lock by others"), + + /** + * invoice status error + */ + CODE_72024(72024, "invoice status error"), + + /** + * invoice token error + */ + CODE_72025(72025, "invoice token error"), + + /** + * need set wx_activate true + */ + CODE_72026(72026, "need set wx_activate true"), + + /** + * invoice action error + */ + CODE_72027(72027, "invoice action error"), + + /** + * invoice never set pay mch info + */ + CODE_72028(72028, "invoice never set pay mch info"), + + /** + * invoice never set auth field + */ + CODE_72029(72029, "invoice never set auth field"), + + /** + * invalid mchid + */ + CODE_72030(72030, "invalid mchid"), + + /** + * invalid params + */ + CODE_72031(72031, "invalid params"), + + /** + * pay gift card rule expired + */ + CODE_72032(72032, "pay gift card rule expired"), + + /** + * pay gift card rule status err + */ + CODE_72033(72033, "pay gift card rule status err"), + + /** + * invlid rule id + */ + CODE_72034(72034, "invlid rule id"), + + /** + * biz reject insert + */ + CODE_72035(72035, "biz reject insert"), + + /** + * invoice is busy, try again please + */ + CODE_72036(72036, "invoice is busy, try again please"), + + /** + * invoice owner error + */ + CODE_72037(72037, "invoice owner error"), + + /** + * invoice order never auth + */ + CODE_72038(72038, "invoice order never auth"), + + /** + * invoice must be lock first + */ + CODE_72039(72039, "invoice must be lock first"), + + /** + * invoice pdf error + */ + CODE_72040(72040, "invoice pdf error"), + + /** + * billing_code and billing_no invalid + */ + CODE_72041(72041, "billing_code and billing_no invalid"), + + /** + * billing_code and billing_no repeated + */ + CODE_72042(72042, "billing_code and billing_no repeated"), + + /** + * billing_code or billing_no size error + */ + CODE_72043(72043, "billing_code or billing_no size error"), + + /** + * scan text out of time + */ + CODE_72044(72044, "scan text out of time"), + + /** + * check_code is empty + */ + CODE_72045(72045, "check_code is empty"), + + /** + * pdf_url is invalid + */ + CODE_72046(72046, "pdf_url is invalid"), + + /** + * pdf billing_code or pdf billing_no is error + */ + CODE_72047(72047, "pdf billing_code or pdf billing_no is error"), + + /** + * insert too many invoice, need auth again + */ + CODE_72048(72048, "insert too many invoice, need auth again"), + + /** + * never auth + */ + CODE_72049(72049, "never auth"), + + /** + * auth expired, need auth again + */ + CODE_72050(72050, "auth expired, need auth again"), + + /** + * app type error + */ + CODE_72051(72051, "app type error"), + + /** + * get too many invoice + */ + CODE_72052(72052, "get too many invoice"), + + /** + * user never auth + */ + CODE_72053(72053, "user never auth"), + + /** + * invoices is inserting, wait a moment please + */ + CODE_72054(72054, "invoices is inserting, wait a moment please"), + + /** + * too many invoices + */ + CODE_72055(72055, "too many invoices"), + + /** + * order_id repeated, please check order_id + */ + CODE_72056(72056, "order_id repeated, please check order_id"), + + /** + * today insert limit + */ + CODE_72057(72057, "today insert limit"), + + /** + * callback biz error + */ + CODE_72058(72058, "callback biz error"), + + /** + * this invoice is giving to others, wait a moment please + */ + CODE_72059(72059, "this invoice is giving to others, wait a moment please"), + + /** + * this invoice has been cancelled, check the reimburse_status please + */ + CODE_72060(72060, "this invoice has been cancelled, check the reimburse_status please"), + + /** + * this invoice has been closed, check the reimburse_status please + */ + CODE_72061(72061, "this invoice has been closed, check the reimburse_status please"), + + /** + * this code_auth_key is limited, try other code_auth_key please + */ + CODE_72062(72062, "this code_auth_key is limited, try other code_auth_key please"), + + /** + * biz contact is empty, set contact first please + */ + CODE_72063(72063, "biz contact is empty, set contact first please"), + + /** + * tbc error + */ + CODE_72064(72064, "tbc error"), + + /** + * tbc logic error + */ + CODE_72065(72065, "tbc logic error"), + + /** + * the card is send for advertisement, not allow modify time and budget + */ + CODE_72066(72066, "the card is send for advertisement, not allow modify time and budget"), + + /** + * BatchInsertAuthKey_Expired + */ + CODE_72067(72067, "BatchInsertAuthKey_Expired"), + + /** + * BatchInsertAuthKey_Owner + */ + CODE_72068(72068, "BatchInsertAuthKey_Owner"), + + /** + * BATCHTASKRUN_ERROR + */ + CODE_72069(72069, "BATCHTASKRUN_ERROR"), + + /** + * BIZ_TITLE_KEY_OUT_TIME + */ + CODE_72070(72070, "BIZ_TITLE_KEY_OUT_TIME"), + + /** + * Discern_GaoPeng_Error + */ + CODE_72071(72071, "Discern_GaoPeng_Error"), + + /** + * Discern_Type_Error + */ + CODE_72072(72072, "Discern_Type_Error"), + + /** + * Fee_Error + */ + CODE_72073(72073, "Fee_Error"), + + /** + * HAS_Auth + */ + CODE_72074(72074, "HAS_Auth"), + + /** + * HAS_SEND + */ + CODE_72075(72075, "HAS_SEND"), + + /** + * INVOICESIGN + */ + CODE_72076(72076, "INVOICESIGN"), + + /** + * KEY_DELETED + */ + CODE_72077(72077, "KEY_DELETED"), + + /** + * KEY_EXPIRED + */ + CODE_72078(72078, "KEY_EXPIRED"), + + /** + * MOUNT_ERROR + */ + CODE_72079(72079, "MOUNT_ERROR"), + + /** + * NO_FOUND + */ + CODE_72080(72080, "NO_FOUND"), + + /** + * No_Pull_Pdf + */ + CODE_72081(72081, "No_Pull_Pdf"), + + /** + * PDF_CHECK_ERROR + */ + CODE_72082(72082, "PDF_CHECK_ERROR"), + + /** + * PULL_PDF_FAIL + */ + CODE_72083(72083, "PULL_PDF_FAIL"), + + /** + * PUSH_BIZ_EMPTY + */ + CODE_72084(72084, "PUSH_BIZ_EMPTY"), + + /** + * SDK_APPID_ERROR + */ + CODE_72085(72085, "SDK_APPID_ERROR"), + + /** + * SDK_BIZ_ERROR + */ + CODE_72086(72086, "SDK_BIZ_ERROR"), + + /** + * SDK_URL_ERROR + */ + CODE_72087(72087, "SDK_URL_ERROR"), + + /** + * Search_Title_Fail + */ + CODE_72088(72088, "Search_Title_Fail"), + + /** + * TITLE_BUSY + */ + CODE_72089(72089, "TITLE_BUSY"), + + /** + * TITLE_NO_FOUND + */ + CODE_72090(72090, "TITLE_NO_FOUND"), + + /** + * TOKEN_ERR + */ + CODE_72091(72091, "TOKEN_ERR"), + + /** + * USER_TITLE_NOT_FOUND + */ + CODE_72092(72092, "USER_TITLE_NOT_FOUND"), + + /** + * Verify_3rd_Fail + */ + CODE_72093(72093, "Verify_3rd_Fail"), + + /** + * sys error make out invoice failed + */ + CODE_73000(73000, "sys error make out invoice failed"), + + /** + * wxopenid error + */ + CODE_73001(73001, "wxopenid error"), + + /** + * ddh orderid empty + */ + CODE_73002(73002, "ddh orderid empty"), + + /** + * wxopenid empty + */ + CODE_73003(73003, "wxopenid empty"), + + /** + * fpqqlsh empty + */ + CODE_73004(73004, "fpqqlsh empty"), + + /** + * not a commercial + */ + CODE_73005(73005, "not a commercial"), + + /** + * kplx empty + */ + CODE_73006(73006, "kplx empty"), + + /** + * nsrmc empty + */ + CODE_73007(73007, "nsrmc empty"), + + /** + * nsrdz empty + */ + CODE_73008(73008, "nsrdz empty"), + + /** + * nsrdh empty + */ + CODE_73009(73009, "nsrdh empty"), + + /** + * ghfmc empty + */ + CODE_73010(73010, "ghfmc empty"), + + /** + * kpr empty + */ + CODE_73011(73011, "kpr empty"), + + /** + * jshj empty + */ + CODE_73012(73012, "jshj empty"), + + /** + * hjje empty + */ + CODE_73013(73013, "hjje empty"), + + /** + * hjse empty + */ + CODE_73014(73014, "hjse empty"), + + /** + * hylx empty + */ + CODE_73015(73015, "hylx empty"), + + /** + * nsrsbh empty + */ + CODE_73016(73016, "nsrsbh empty"), + + /** + * kaipiao plat error + */ + CODE_73100(73100, "kaipiao plat error"), + + /** + * nsrsbh not cmp + */ + CODE_73101(73101, "nsrsbh not cmp"), + + /** + * invalid wxa appid in url_cell, wxa appid is need to bind biz appid + */ + CODE_73103(73103, "invalid wxa appid in url_cell, wxa appid is need to bind biz appid"), + + /** + * reach frequency limit + */ + CODE_73104(73104, "reach frequency limit"), + + /** + * Kp plat make invoice timeout, please try again with the same fpqqlsh + */ + CODE_73105(73105, "Kp plat make invoice timeout, please try again with the same fpqqlsh"), + + /** + * Fpqqlsh exist with different ddh + */ + CODE_73106(73106, "Fpqqlsh exist with different ddh"), + + /** + * Fpqqlsh is processing, please wait and query later + */ + CODE_73107(73107, "Fpqqlsh is processing, please wait and query later"), + + /** + * This ddh with other fpqqlsh already exist + */ + CODE_73108(73108, "This ddh with other fpqqlsh already exist"), + + /** + * This Fpqqlsh not exist in kpplat + */ + CODE_73109(73109, "This Fpqqlsh not exist in kpplat"), + + /** + * get card detail by card id and code fail + */ + CODE_73200(73200, "get card detail by card id and code fail"), + + /** + * get cloud invoice record fail + */ + CODE_73201(73201, "get cloud invoice record fail"), + + /** + * get appinfo fail + */ + CODE_73202(73202, "get appinfo fail"), + + /** + * get invoice category or rule kv error + */ + CODE_73203(73203, "get invoice category or rule kv error"), + + /** + * request card not exist + */ + CODE_73204(73204, "request card not exist"), + + /** + * 朋友的券玩法升级中,当前暂停创建,请创建其他类型卡券 + */ + CODE_73205(73205, "朋友的券玩法升级中,当前暂停创建,请创建其他类型卡券"), + + /** + * 朋友的券玩法升级中,当前暂停券点充值,请创建其他类型卡券 + */ + CODE_73206(73206, "朋友的券玩法升级中,当前暂停券点充值,请创建其他类型卡券"), + + /** + * 朋友的券玩法升级中,当前暂停开通券点账户 + */ + CODE_73207(73207, "朋友的券玩法升级中,当前暂停开通券点账户"), + + /** + * 朋友的券玩法升级中,当前不支持修改库存 + */ + CODE_73208(73208, "朋友的券玩法升级中,当前不支持修改库存"), + + /** + * 朋友的券玩法升级中,当前不支持修改有效期 + */ + CODE_73209(73209, "朋友的券玩法升级中,当前不支持修改有效期"), + + /** + * 当前批次不支持修改卡券批次库存 + */ + CODE_73210(73210, "当前批次不支持修改卡券批次库存"), + + /** + * 不再支持配置网页链接跳转,请选择小程序替代 + */ + CODE_73211(73211, "不再支持配置网页链接跳转,请选择小程序替代"), + + /** + * unauthorized backup member + */ + CODE_73212(73212, "unauthorized backup member"), + + /** + * invalid code type + */ + CODE_73213(73213, "invalid code type"), + + /** + * the user is already a member + */ + CODE_73214(73214, "the user is already a member"), + + /** + * 支付打通券能力已下线,请直接使用微信支付代金券API:https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/marketing/convention/chapter1_1.shtml + */ + CODE_73215(73215, "支付打通券能力已下线,请直接使用微信支付代金券API:https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/marketing/convention/chapter1_1.shtml"), + + /** + * 不合法的按钮名字,请从中选择一个:使用礼品卡/立即使用/去点外卖 + */ + CODE_73216(73216, "不合法的按钮名字,请从中选择一个:使用礼品卡/立即使用/去点外卖"), + + /** + * 礼品卡本身没有设置appname和path,不允许在修改接口设置 + */ + CODE_73217(73217, "礼品卡本身没有设置appname和path,不允许在修改接口设置"), + + /** + * 未授权使用礼品卡落地页跳转小程序功能 + */ + CODE_73218(73218, "未授权使用礼品卡落地页跳转小程序功能"), + + /** + * not find this wx_hotel_id info + */ + CODE_74000(74000, "not find this wx_hotel_id info"), + + /** + * request some param empty + */ + CODE_74001(74001, "request some param empty"), + + /** + * request some param error + */ + CODE_74002(74002, "request some param error"), + + /** + * request some param error + */ + CODE_74003(74003, "request some param error"), + + /** + * datetime error + */ + CODE_74004(74004, "datetime error"), + + /** + * checkin mode error + */ + CODE_74005(74005, "checkin mode error"), + + /** + * carid from error + */ + CODE_74007(74007, "carid from error"), + + /** + * this hotel routecode not exist + */ + CODE_74008(74008, "this hotel routecode not exist"), + + /** + * this hotel routecode info error contract developer + */ + CODE_74009(74009, "this hotel routecode info error contract developer"), + + /** + * maybe not support report mode + */ + CODE_74010(74010, "maybe not support report mode"), + + /** + * pic deocde not ok maybe its not good picdata + */ + CODE_74011(74011, "pic deocde not ok maybe its not good picdata"), + + /** + * verify sys erro + */ + CODE_74021(74021, "verify sys erro"), + + /** + * inner police erro + */ + CODE_74022(74022, "inner police erro"), + + /** + * unable to detect the face + */ + CODE_74023(74023, "unable to detect the face"), + + /** + * report checkin 2 lvye sys erro + */ + CODE_74040(74040, "report checkin 2 lvye sys erro"), + + /** + * report checkou 2 lvye sys erro + */ + CODE_74041(74041, "report checkou 2 lvye sys erro"), + + /** + * some param emtpy please check + */ + CODE_75001(75001, "some param emtpy please check"), + + /** + * param illegal please check + */ + CODE_75002(75002, "param illegal please check"), + + /** + * sys error kv store error + */ + CODE_75003(75003, "sys error kv store error"), + + /** + * sys error kvstring store error + */ + CODE_75004(75004, "sys error kvstring store error"), + + /** + * product not exist please check your product_id + */ + CODE_75005(75005, "product not exist please check your product_id"), + + /** + * order not exist please check order_id and buyer_appid + */ + CODE_75006(75006, "order not exist please check order_id and buyer_appid"), + + /** + * do not allow this status to change please check this order_id status now + */ + CODE_75007(75007, "do not allow this status to change please check this order_id status now"), + + /** + * product has exist please use new id + */ + CODE_75008(75008, "product has exist please use new id"), + + /** + * notify order status failed + */ + CODE_75009(75009, "notify order status failed"), + + /** + * buyer bussiness info not exist + */ + CODE_75010(75010, "buyer bussiness info not exist"), + + /** + * you had registered + */ + CODE_75011(75011, "you had registered"), + + /** + * store image key to kv error, please try again + */ + CODE_75012(75012, "store image key to kv error, please try again"), + + /** + * get image fail, please check you image key + */ + CODE_75013(75013, "get image fail, please check you image key"), + + /** + * this key is not belong to you + */ + CODE_75014(75014, "this key is not belong to you"), + + /** + * this key is expired + */ + CODE_75015(75015, "this key is expired"), + + /** + * encrypt decode key fail + */ + CODE_75016(75016, "encrypt decode key fail"), + + /** + * encrypt encode key fail + */ + CODE_75017(75017, "encrypt encode key fail"), + + /** + * bind buyer business info fail please contact us + */ + CODE_75018(75018, "bind buyer business info fail please contact us"), + + /** + * this key is empty, user may not upload file + */ + CODE_75019(75019, "this key is empty, user may not upload file"), + + /** + * 系统错误,请稍后再试 + */ + CODE_80000(80000, "系统错误,请稍后再试"), + + /** + * 参数格式校验错误 + */ + CODE_80001(80001, "参数格式校验错误"), + + /** + * 签名失败 + */ + CODE_80002(80002, "签名失败"), + + /** + * 该日期订单未生成 + */ + CODE_80003(80003, "该日期订单未生成"), + + /** + * 用户未绑卡 + */ + CODE_80004(80004, "用户未绑卡"), + + /** + * 姓名不符 + */ + CODE_80005(80005, "姓名不符"), + + /** + * 身份证不符 + */ + CODE_80006(80006, "身份证不符"), + + /** + * 获取城市信息失败 + */ + CODE_80007(80007, "获取城市信息失败"), + + /** + * 未找到指定少儿信息 + */ + CODE_80008(80008, "未找到指定少儿信息"), + + /** + * 少儿身份证不符 + */ + CODE_80009(80009, "少儿身份证不符"), + + /** + * 少儿未绑定 + */ + CODE_80010(80010, "少儿未绑定"), + + /** + * 签约号不符 + */ + CODE_80011(80011, "签约号不符"), + + /** + * 该地区局方配置不存在 + */ + CODE_80012(80012, "该地区局方配置不存在"), + + /** + * 调用方appid与局方配置不匹配 + */ + CODE_80013(80013, "调用方appid与局方配置不匹配"), + + /** + * 获取消息账号失败 + */ + CODE_80014(80014, "获取消息账号失败"), + + /** + * 非法的插件版本 + */ + CODE_80066(80066, "非法的插件版本"), + + /** + * 找不到使用的插件 + */ + CODE_80067(80067, "找不到使用的插件"), + + /** + * 没有权限使用该插件 + */ + CODE_80082(80082, "没有权限使用该插件"), + + /** + * 商家未接入 + */ + CODE_80101(80101, "商家未接入"), + + /** + * 实名校验code不存在 + */ + CODE_80111(80111, "实名校验code不存在"), + + /** + * code并发冲突 + */ + CODE_80112(80112, "code并发冲突"), + + /** + * 无效code + */ + CODE_80113(80113, "无效code"), + + /** + * report_type无效 + */ + CODE_80201(80201, "report_type无效"), + + /** + * service_type无效 + */ + CODE_80202(80202, "service_type无效"), + + /** + * 申请单不存在 + */ + CODE_80300(80300, "申请单不存在"), + + /** + * 申请单不属于该账号 + */ + CODE_80301(80301, "申请单不属于该账号"), + + /** + * 激活号段有重叠 + */ + CODE_80302(80302, "激活号段有重叠"), + + /** + * 码格式错误 + */ + CODE_80303(80303, "码格式错误"), + + /** + * 该码未激活 + */ + CODE_80304(80304, "该码未激活"), + + /** + * 激活失败 + */ + CODE_80305(80305, "激活失败"), + + /** + * 码索引超出申请范围 + */ + CODE_80306(80306, "码索引超出申请范围"), + + /** + * 申请已存在 + */ + CODE_80307(80307, "申请已存在"), + + /** + * 子任务未完成 + */ + CODE_80308(80308, "子任务未完成"), + + /** + * 子任务文件过期 + */ + CODE_80309(80309, "子任务文件过期"), + + /** + * 子任务不存在 + */ + CODE_80310(80310, "子任务不存在"), + + /** + * 获取文件失败 + */ + CODE_80311(80311, "获取文件失败"), + + /** + * 加密数据失败 + */ + CODE_80312(80312, "加密数据失败"), + + /** + * 加密数据密钥不存在,请联系接口人申请 + */ + CODE_80313(80313, "加密数据密钥不存在,请联系接口人申请"), + + /** + * can not set page_id in AddGiftCardPage + */ + CODE_81000(81000, "can not set page_id in AddGiftCardPage"), + + /** + * card_list is empty + */ + CODE_81001(81001, "card_list is empty"), + + /** + * card_id is not giftcard + */ + CODE_81002(81002, "card_id is not giftcard"), + + /** + * banner_pic_url is empty + */ + CODE_81004(81004, "banner_pic_url is empty"), + + /** + * banner_pic_url is not from cdn + */ + CODE_81005(81005, "banner_pic_url is not from cdn"), + + /** + * giftcard_wrap_pic_url_list is empty + */ + CODE_81006(81006, "giftcard_wrap_pic_url_list is empty"), + + /** + * giftcard_wrap_pic_url_list is not from cdn + */ + CODE_81007(81007, "giftcard_wrap_pic_url_list is not from cdn"), + + /** + * address is empty + */ + CODE_81008(81008, "address is empty"), + + /** + * service_phone is invalid + */ + CODE_81009(81009, "service_phone is invalid"), + + /** + * biz_description is empty + */ + CODE_81010(81010, "biz_description is empty"), + + /** + * invalid page_id + */ + CODE_81011(81011, "invalid page_id"), + + /** + * invalid order_id + */ + CODE_81012(81012, "invalid order_id"), + + /** + * invalid TIME_RANGE, begin_time + 31day must less than end_time + */ + CODE_81013(81013, "invalid TIME_RANGE, begin_time + 31day must less than end_time"), + + /** + * invalid count! count must equal or less than 100 + */ + CODE_81014(81014, "invalid count! count must equal or less than 100"), + + /** + * invalid category_index OR category.title is empty OR is_banner but has_category_index + */ + CODE_81015(81015, "invalid category_index OR category.title is empty OR is_banner but has_category_index"), + + /** + * is_banner is more than 1 + */ + CODE_81016(81016, "is_banner is more than 1"), + + /** + * order status error, please check pay status or gifting_status + */ + CODE_81017(81017, "order status error, please check pay status or gifting_status"), + + /** + * refund reduplicate, the order is already refunded + */ + CODE_81018(81018, "refund reduplicate, the order is already refunded"), + + /** + * lock order fail! the order is refunding by another request + */ + CODE_81019(81019, "lock order fail! the order is refunding by another request"), + + /** + * Invalid Args! page_id.size!=0 but all==true, or page_id.size==0 but all==false. + */ + CODE_81020(81020, "Invalid Args! page_id.size!=0 but all==true, or page_id.size==0 but all==false."), + + /** + * Empty theme_pic_url. + */ + CODE_81021(81021, "Empty theme_pic_url."), + + /** + * Empty theme.title. + */ + CODE_81022(81022, "Empty theme.title."), + + /** + * Empty theme.title_title. + */ + CODE_81023(81023, "Empty theme.title_title."), + + /** + * Empty theme.item_list. + */ + CODE_81024(81024, "Empty theme.item_list."), + + /** + * Empty theme.pic_item_list. + */ + CODE_81025(81025, "Empty theme.pic_item_list."), + + /** + * Invalid theme.title.length . + */ + CODE_81026(81026, "Invalid theme.title.length ."), + + /** + * Empty background_pic_url. + */ + CODE_81027(81027, "Empty background_pic_url."), + + /** + * Empty default_gifting_msg. + */ + CODE_81028(81028, "Empty default_gifting_msg."), + + /** + * Duplicate order_id + */ + CODE_81029(81029, "Duplicate order_id"), + + /** + * PreAlloc code fail + */ + CODE_81030(81030, "PreAlloc code fail"), + + /** + * Too many theme participate_activity + */ + CODE_81031(81031, "Too many theme participate_activity"), + + /** + * biz_template_id not exist + */ + CODE_82000(82000, "biz_template_id not exist"), + + /** + * result_page_style_id not exist + */ + CODE_82001(82001, "result_page_style_id not exist"), + + /** + * deal_msg_style_id not exist + */ + CODE_82002(82002, "deal_msg_style_id not exist"), + + /** + * card_style_id not exist + */ + CODE_82003(82003, "card_style_id not exist"), + + /** + * biz template not audit OK + */ + CODE_82010(82010, "biz template not audit OK"), + + /** + * biz template banned + */ + CODE_82011(82011, "biz template banned"), + + /** + * user not use service first + */ + CODE_82020(82020, "user not use service first"), + + /** + * exceed long period + */ + CODE_82021(82021, "exceed long period"), + + /** + * exceed long period max send cnt + */ + CODE_82022(82022, "exceed long period max send cnt"), + + /** + * exceed short period max send cnt + */ + CODE_82023(82023, "exceed short period max send cnt"), + + /** + * exceed data size limit + */ + CODE_82024(82024, "exceed data size limit"), + + /** + * invalid url + */ + CODE_82025(82025, "invalid url"), + + /** + * service disabled + */ + CODE_82026(82026, "service disabled"), + + /** + * invalid miniprogram appid + */ + CODE_82027(82027, "invalid miniprogram appid"), + + /** + * wx_cs_code should not be empty. + */ + CODE_82100(82100, "wx_cs_code should not be empty."), + + /** + * wx_cs_code is invalid. + */ + CODE_82101(82101, "wx_cs_code is invalid."), + + /** + * wx_cs_code is expire. + */ + CODE_82102(82102, "wx_cs_code is expire."), + + /** + * user_ip should not be empty. + */ + CODE_82103(82103, "user_ip should not be empty."), + + /** + * 公众平台账号与服务id不匹配 + */ + CODE_82200(82200, "公众平台账号与服务id不匹配"), + + /** + * 该停车场已存在,请勿重复添加 + */ + CODE_82201(82201, "该停车场已存在,请勿重复添加"), + + /** + * 该停车场信息不存在,请先导入 + */ + CODE_82202(82202, "该停车场信息不存在,请先导入"), + + /** + * 停车场价格格式不正确 + */ + CODE_82203(82203, "停车场价格格式不正确"), + + /** + * appid与code不匹配 + */ + CODE_82204(82204, "appid与code不匹配"), + + /** + * wx_park_code字段为空 + */ + CODE_82205(82205, "wx_park_code字段为空"), + + /** + * wx_park_code无效或已过期 + */ + CODE_82206(82206, "wx_park_code无效或已过期"), + + /** + * 电话字段为空 + */ + CODE_82207(82207, "电话字段为空"), + + /** + * 关闭时间格式不正确 + */ + CODE_82208(82208, "关闭时间格式不正确"), + + /** + * 该appid不支持开通城市服务插件 + */ + CODE_82300(82300, "该appid不支持开通城市服务插件"), + + /** + * 添加插件失败 + */ + CODE_82301(82301, "添加插件失败"), + + /** + * 未添加城市服务插件 + */ + CODE_82302(82302, "未添加城市服务插件"), + + /** + * fileid无效 + */ + CODE_82303(82303, "fileid无效"), + + /** + * 临时文件过期 + */ + CODE_82304(82304, "临时文件过期"), + + /** + * there is some param not exist + */ + CODE_83000(83000, "there is some param not exist"), + + /** + * system error + */ + CODE_83001(83001, "system error"), + + /** + * create_url_sence_failed + */ + CODE_83002(83002, "create_url_sence_failed"), + + /** + * appid maybe error or retry + */ + CODE_83003(83003, "appid maybe error or retry"), + + /** + * create appid auth failed or retry + */ + CODE_83004(83004, "create appid auth failed or retry"), + + /** + * wxwebencrytoken errro + */ + CODE_83005(83005, "wxwebencrytoken errro"), + + /** + * wxwebencrytoken expired or no exist + */ + CODE_83006(83006, "wxwebencrytoken expired or no exist"), + + /** + * wxwebencrytoken expired + */ + CODE_83007(83007, "wxwebencrytoken expired"), + + /** + * wxwebencrytoken no auth + */ + CODE_83008(83008, "wxwebencrytoken no auth"), + + /** + * wxwebencrytoken not the mate with openid + */ + CODE_83009(83009, "wxwebencrytoken not the mate with openid"), + + /** + * no exist service + */ + CODE_83200(83200, "no exist service"), + + /** + * uin has not the service + */ + CODE_83201(83201, "uin has not the service"), + + /** + * params is not json or not json array + */ + CODE_83202(83202, "params is not json or not json array"), + + /** + * params num exceed 10 + */ + CODE_83203(83203, "params num exceed 10"), + + /** + * object has not key + */ + CODE_83204(83204, "object has not key"), + + /** + * key is not string + */ + CODE_83205(83205, "key is not string"), + + /** + * object has not value + */ + CODE_83206(83206, "object has not value"), + + /** + * value is not string + */ + CODE_83207(83207, "value is not string"), + + /** + * key or value is empty + */ + CODE_83208(83208, "key or value is empty"), + + /** + * key exist repeated + */ + CODE_83209(83209, "key exist repeated"), + + /** + * invalid identify id + */ + CODE_84001(84001, "invalid identify id"), + + /** + * user data expired + */ + CODE_84002(84002, "user data expired"), + + /** + * user data not exist + */ + CODE_84003(84003, "user data not exist"), + + /** + * video upload fail! + */ + CODE_84004(84004, "video upload fail!"), + + /** + * video download fail! please try again + */ + CODE_84005(84005, "video download fail! please try again"), + + /** + * name or id_card_number empty + */ + CODE_84006(84006, "name or id_card_number empty"), + + /** + * 微信号不存在或微信号设置为不可搜索 user not exist or user cannot be searched + */ + CODE_85001(85001, "微信号不存在或微信号设置为不可搜索"), + + /** + * 小程序绑定的体验者数量达到上限 number of tester reach bind limit + */ + CODE_85002(85002, "小程序绑定的体验者数量达到上限"), + + /** + * 微信号绑定的小程序体验者达到上限 user already bind too many weapps + */ + CODE_85003(85003, "微信号绑定的小程序体验者达到上限"), + + /** + * 微信号已经绑定 user already bind + */ + CODE_85004(85004, "微信号已经绑定"), + + /** + * appid not bind weapp + */ + CODE_85005(85005, "appid not bind weapp"), + + /** + * 标签格式错误 tag is in invalid format + */ + CODE_85006(85006, "标签格式错误"), + + /** + * 页面路径错误 page is in invalid format + */ + CODE_85007(85007, "页面路径错误"), + + /** + * 当前小程序没有已经审核通过的类目,请添加类目成功后重试 category is in invalid format + */ + CODE_85008(85008, "当前小程序没有已经审核通过的类目,请添加类目成功后重试"), + + /** + * 已经有正在审核的版本 already submit a version under auditing + */ + CODE_85009(85009, "已经有正在审核的版本"), + + /** + * item_list 有项目为空 missing required data + */ + CODE_85010(85010, "item_list 有项目为空"), + + /** + * 标题填写错误 title is in invalid format + */ + CODE_85011(85011, "标题填写错误"), + + /** + * 无效的审核 id invalid audit id + */ + CODE_85012(85012, "无效的审核 id"), + + /** + * 无效的自定义配置 invalid ext_json, parse fail or containing invalid path + */ + CODE_85013(85013, "无效的自定义配置"), + + /** + * 无效的模板编号 template not exist + */ + CODE_85014(85014, "无效的模板编号"), + + /** + * 该账号不是小程序账号/版本输入错误 + */ + CODE_85015(85015, "该账号不是小程序账号/版本输入错误"), + + /** + * 版本输入错误 + */ +// CODE_85015(85015, "版本输入错误"), + + /** + * 域名数量超过限制 ,总数不能超过1000 exceed valid domain count + */ + CODE_85016(85016, "域名数量超过限制 ,总数不能超过1000"), + + /** + * 没有新增域名,请确认小程序已经添加了域名或该域名是否没有在第三方平台添加 no domain to modify after filtered, please confirm the domain has been set in miniprogram or open + */ + CODE_85017(85017, "没有新增域名,请确认小程序已经添加了域名或该域名是否没有在第三方平台添加"), + + /** + * 域名没有在第三方平台设置 + */ + CODE_85018(85018, "域名没有在第三方平台设置"), + + /** + * 没有审核版本 no version is under auditing + */ + CODE_85019(85019, "没有审核版本"), + + /** + * 审核状态未满足发布 status not allowed + */ + CODE_85020(85020, "审核状态未满足发布"), + + /** + * status not allowed + */ + CODE_85021(85021, "status not allowed"), + + /** + * invalid action + */ + CODE_85022(85022, "invalid action"), + + /** + * 审核列表填写的项目数不在 1-5 以内 item size is not in valid range + */ + CODE_85023(85023, "审核列表填写的项目数不在 1-5 以内"), + + /** + * need complete material + */ + CODE_85024(85024, "need complete material"), + + /** + * this phone reach bind limit + */ + CODE_85025(85025, "this phone reach bind limit"), + + /** + * this wechat account reach bind limit + */ + CODE_85026(85026, "this wechat account reach bind limit"), + + /** + * this idcard reach bind limit + */ + CODE_85027(85027, "this idcard reach bind limit"), + + /** + * this contractor reach bind limit + */ + CODE_85028(85028, "this contractor reach bind limit"), + + /** + * nickname has used + */ + CODE_85029(85029, "nickname has used"), + + /** + * invalid nickname size(4-30) + */ + CODE_85030(85030, "invalid nickname size(4-30)"), + + /** + * nickname is forbidden + */ + CODE_85031(85031, "nickname is forbidden"), + + /** + * nickname is complained + */ + CODE_85032(85032, "nickname is complained"), + + /** + * nickname is illegal + */ + CODE_85033(85033, "nickname is illegal"), + + /** + * nickname is protected + */ + CODE_85034(85034, "nickname is protected"), + + /** + * nickname is forbidden for different contractor + */ + CODE_85035(85035, "nickname is forbidden for different contractor"), + + /** + * introduction is illegal + */ + CODE_85036(85036, "introduction is illegal"), + + /** + * store has added + */ + CODE_85038(85038, "store has added"), + + /** + * store has added by others + */ + CODE_85039(85039, "store has added by others"), + + /** + * store has added by yourseld + */ + CODE_85040(85040, "store has added by yourseld"), + + /** + * credential has used + */ + CODE_85041(85041, "credential has used"), + + /** + * nearby reach limit + */ + CODE_85042(85042, "nearby reach limit"), + + /** + * 模板错误 invalid template, something wrong? + */ + CODE_85043(85043, "模板错误"), + + /** + * 代码包超过大小限制 package exceed max limit + */ + CODE_85044(85044, "代码包超过大小限制"), + + /** + * ext_json 有不存在的路径 some path in ext_json not exist + */ + CODE_85045(85045, "ext_json 有不存在的路径"), + + /** + * tabBar 中缺少 path pagepath missing in tabbar list + */ + CODE_85046(85046, "tabBar 中缺少 path"), + + /** + * pages 字段为空 pages are empty + */ + CODE_85047(85047, "pages 字段为空"), + + /** + * ext_json 解析失败 parse ext_json fail + */ + CODE_85048(85048, "ext_json 解析失败"), + + /** + * reach headimg or introduction quota limit + */ + CODE_85049(85049, "reach headimg or introduction quota limit"), + + /** + * verifying, don't apply again + */ + CODE_85050(85050, "verifying, don't apply again"), + + /** + * version_desc或者preview_info超限 data too large + */ + CODE_85051(85051, "version_desc或者preview_info超限"), + + /** + * app is already released + */ + CODE_85052(85052, "app is already released"), + + /** + * please apply merchant first + */ + CODE_85053(85053, "please apply merchant first"), + + /** + * poi_id is null, please upgrade first + */ + CODE_85054(85054, "poi_id is null, please upgrade first"), + + /** + * map_poi_id is invalid + */ + CODE_85055(85055, "map_poi_id is invalid"), + + /** + * mediaid is invalid + */ + CODE_85056(85056, "mediaid is invalid"), + + /** + * invalid widget data format + */ + CODE_85057(85057, "invalid widget data format"), + + /** + * no valid audit_id exist + */ + CODE_85058(85058, "no valid audit_id exist"), + + /** + * overseas access deny + */ + CODE_85059(85059, "overseas access deny"), + + /** + * invalid taskid + */ + CODE_85060(85060, "invalid taskid"), + + /** + * this phone reach bind limit + */ + CODE_85061(85061, "this phone reach bind limit"), + + /** + * this phone in black list + */ + CODE_85062(85062, "this phone in black list"), + + /** + * idcard in black list + */ + CODE_85063(85063, "idcard in black list"), + + /** + * 找不到模板 template not found + */ + CODE_85064(85064, "找不到模板"), + + /** + * 模板库已满 template list is full + */ + CODE_85065(85065, "模板库已满"), + + /** + * 链接错误 illegal prefix + */ + CODE_85066(85066, "链接错误"), + + /** + * input data error + */ + CODE_85067(85067, "input data error"), + + /** + * 测试链接不是子链接 test url is not the sub prefix + */ + CODE_85068(85068, "测试链接不是子链接"), + + /** + * 校验文件失败 check confirm file fail + */ + CODE_85069(85069, "校验文件失败"), + + /** + * 个人类型小程序无法设置二维码规则 prefix in black list + */ + CODE_85070(85070, "个人类型小程序无法设置二维码规则"), + + /** + * 已添加该链接,请勿重复添加 prefix added repeated + */ + CODE_85071(85071, "已添加该链接,请勿重复添加"), + + /** + * 该链接已被占用 prefix owned by other + */ + CODE_85072(85072, "该链接已被占用"), + + /** + * 二维码规则已满 prefix beyond limit + */ + CODE_85073(85073, "二维码规则已满"), + + /** + * 小程序未发布, 小程序必须先发布代码才可以发布二维码跳转规则 not published + */ + CODE_85074(85074, "小程序未发布, 小程序必须先发布代码才可以发布二维码跳转规则"), + + /** + * 个人类型小程序无法设置二维码规则 can not access + */ + CODE_85075(85075, "个人类型小程序无法设置二维码规则"), + + /** + * 小程序类目信息失效(类目中含有官方下架的类目,请重新选择类目) some category you choose is no longger supported, please choose other category + */ + CODE_85077(85077, "小程序类目信息失效(类目中含有官方下架的类目,请重新选择类目)"), + + /** + * operator info error + */ + CODE_85078(85078, "operator info error"), + + /** + * 小程序没有线上版本,不能进行灰度 miniprogram has no online release + */ + CODE_85079(85079, "小程序没有线上版本,不能进行灰度"), + + /** + * 小程序提交的审核未审核通过 miniprogram commit not approved + */ + CODE_85080(85080, "小程序提交的审核未审核通过"), + + /** + * 无效的发布比例 invalid gray percentage + */ + CODE_85081(85081, "无效的发布比例"), + + /** + * 当前的发布比例需要比之前设置的高 gray percentage too low + */ + CODE_85082(85082, "当前的发布比例需要比之前设置的高"), + + /** + * 搜索标记位被封禁,无法修改 search status is banned + */ + CODE_85083(85083, "搜索标记位被封禁,无法修改"), + + /** + * 非法的 status 值,只能填 0 或者 1 search status invalid + */ + CODE_85084(85084, "非法的 status 值,只能填 0 或者 1"), + + /** + * 小程序提审数量已达本月上限,请点击查看 submit audit reach limit pleasetry later + */ + CODE_85085(85085, "小程序提审数量已达本月上限,请点击查看"), + + /** + * 提交代码审核之前需提前上传代码 must commit before submit audit + */ + CODE_85086(85086, "提交代码审核之前需提前上传代码"), + + /** + * 小程序已使用 api navigateToMiniProgram,请声明跳转 appid 列表后再次提交 navigatetominiprogram appid list empty + */ + CODE_85087(85087, "小程序已使用 api navigateToMiniProgram,请声明跳转 appid 列表后再次提交"), + + /** + * no qbase privilege + */ + CODE_85088(85088, "no qbase privilege"), + + /** + * config not found + */ + CODE_85089(85089, "config not found"), + + /** + * wait and commit for this exappid later + */ + CODE_85090(85090, "wait and commit for this exappid later"), + + /** + * 小程序的搜索开关被关闭。请访问设置页面打开开关再重试 search status was turned off + */ + CODE_85091(85091, "小程序的搜索开关被关闭。请访问设置页面打开开关再重试"), + + /** + * preview_info格式错误 invalid preview_info format + */ + CODE_85092(85092, "preview_info格式错误"), + + /** + * preview_info 视频或者图片个数超限 invalid preview_info size + */ + CODE_85093(85093, "preview_info 视频或者图片个数超限"), + + /** + * 需提供审核机制说明信息 need add ugc declare + */ + CODE_85094(85094, "需提供审核机制说明信息"), + + /** + * 小程序不能发送该运动类型或运动类型不存在 + */ + CODE_85101(85101, "小程序不能发送该运动类型或运动类型不存在"), + + /** + * 数值异常 + */ + CODE_85102(85102, "数值异常"), + + /** + * 不是由第三方代小程序进行调用 should be called only from third party + */ + CODE_86000(86000, "不是由第三方代小程序进行调用"), + + /** + * 不存在第三方的已经提交的代码 component experience version not exists + */ + CODE_86001(86001, "不存在第三方的已经提交的代码"), + + /** + * 小程序还未设置昵称、头像、简介。请先设置完后再重新提交 miniprogram have not completed init procedure + */ + CODE_86002(86002, "小程序还未设置昵称、头像、简介。请先设置完后再重新提交"), + + /** + * component do not has category mall + */ + CODE_86003(86003, "component do not has category mall"), + + /** + * invalid wechat + */ + CODE_86004(86004, "invalid wechat"), + + /** + * wechat limit frequency + */ + CODE_86005(86005, "wechat limit frequency"), + + /** + * has no quota to send group msg + */ + CODE_86006(86006, "has no quota to send group msg"), + + /** + * 小程序禁止提交 + */ + CODE_86007(86007, "小程序禁止提交"), + + /** + * 服务商被处罚,限制全部代码提审能力 + */ + CODE_86008(86008, "服务商被处罚,限制全部代码提审能力"), + + /** + * 服务商新增小程序代码提审能力被限制 + */ + CODE_86009(86009, "服务商新增小程序代码提审能力被限制"), + + /** + * 服务商迭代小程序代码提审能力被限制 + */ + CODE_86010(86010, "服务商迭代小程序代码提审能力被限制"), + + /** + * 小游戏不能提交 this is game miniprogram, submit audit is forbidden + */ + CODE_87006(87006, "小游戏不能提交"), + + /** + * session_key is not existd or expired + */ + CODE_87007(87007, "session_key is not existd or expired"), + + /** + * invalid sig_method + */ + CODE_87008(87008, "invalid sig_method"), + + /** + * 无效的签名 invalid signature + */ + CODE_87009(87009, "无效的签名"), + + /** + * invalid buffer size + */ + CODE_87010(87010, "invalid buffer size"), + + /** + * 现网已经在灰度发布,不能进行版本回退 wxa has a gray release plan, forbid revert release + */ + CODE_87011(87011, "现网已经在灰度发布,不能进行版本回退"), + + /** + * 该版本不能回退,可能的原因:1:无上一个线上版用于回退 2:此版本为已回退版本,不能回退 3:此版本为回退功能上线之前的版本,不能回退 forbid revert this version release + */ + CODE_87012(87012, "该版本不能回退,可能的原因:1:无上一个线上版用于回退 2:此版本为已回退版本,不能回退 3:此版本为回退功能上线之前的版本,不能回退"), + + /** + * 撤回次数达到上限(每天5次,每个月 10 次) no quota to undo code + */ + CODE_87013(87013, "撤回次数达到上限(每天5次,每个月 10 次)"), + + /** + * risky content + */ + CODE_87014(87014, "risky content"), + + /** + * query timeout, try a content with less size + */ + CODE_87015(87015, "query timeout, try a content with less size"), + + /** + * some key-value in list meet length exceed + */ + CODE_87016(87016, "some key-value in list meet length exceed"), + + /** + * user storage size exceed, delete some keys and try again + */ + CODE_87017(87017, "user storage size exceed, delete some keys and try again"), + + /** + * user has stored too much keys. delete some keys and try again + */ + CODE_87018(87018, "user has stored too much keys. delete some keys and try again"), + + /** + * some keys in list meet length exceed + */ + CODE_87019(87019, "some keys in list meet length exceed"), + + /** + * need friend + */ + CODE_87080(87080, "need friend"), + + /** + * invalid openid + */ + CODE_87081(87081, "invalid openid"), + + /** + * invalid key + */ + CODE_87082(87082, "invalid key"), + + /** + * invalid operation + */ + CODE_87083(87083, "invalid operation"), + + /** + * invalid opnum + */ + CODE_87084(87084, "invalid opnum"), + + /** + * check fail + */ + CODE_87085(87085, "check fail"), + + /** + * without comment privilege + */ + CODE_88000(88000, "without comment privilege"), + + /** + * msg_data is not exists + */ + CODE_88001(88001, "msg_data is not exists"), + + /** + * the article is limit for safety + */ + CODE_88002(88002, "the article is limit for safety"), + + /** + * elected comment upper limit + */ + CODE_88003(88003, "elected comment upper limit"), + + /** + * comment was deleted by user + */ + CODE_88004(88004, "comment was deleted by user"), + + /** + * already reply + */ + CODE_88005(88005, "already reply"), + + /** + * reply content beyond max len or content len is zero + */ + CODE_88007(88007, "reply content beyond max len or content len is zero"), + + /** + * comment is not exists + */ + CODE_88008(88008, "comment is not exists"), + + /** + * reply is not exists + */ + CODE_88009(88009, "reply is not exists"), + + /** + * {@code count range error. cout <= 0 or count > 50} + */ + CODE_88010(88010, "count range error. cout <= 0 or count > 50"), + + /** + * the article is limit for safety + */ + CODE_88011(88011, "the article is limit for safety"), + + /** + * account has bound open,该公众号/小程序已经绑定了开放平台帐号 account has bound open + */ + CODE_89000(89000, "account has bound open,该公众号/小程序已经绑定了开放平台帐号"), + + /** + * not same contractor,Authorizer 与开放平台帐号主体不相同 not same contractor + */ + CODE_89001(89001, "not same contractor,Authorizer 与开放平台帐号主体不相同"), + + /** + * open not exists,该公众号/小程序未绑定微信开放平台帐号。 open not exists + */ + CODE_89002(89002, "open not exists,该公众号/小程序未绑定微信开放平台帐号。"), + + /** + * 该开放平台帐号并非通过 api 创建,不允许操作 open is not created by api + */ + CODE_89003(89003, "该开放平台帐号并非通过 api 创建,不允许操作"), + + /** + * 该开放平台帐号所绑定的公众号/小程序已达上限(100 个) + */ + CODE_89004(89004, "该开放平台帐号所绑定的公众号/小程序已达上限(100 个)"), + + /** + * without add video ability, the ability was banned + */ + CODE_89005(89005, "without add video ability, the ability was banned"), + + /** + * without upload video ability, the ability was banned + */ + CODE_89006(89006, "without upload video ability, the ability was banned"), + + /** + * wxa quota limit + */ + CODE_89007(89007, "wxa quota limit"), + + /** + * overseas account can not link + */ + CODE_89008(89008, "overseas account can not link"), + + /** + * wxa reach link limit + */ + CODE_89009(89009, "wxa reach link limit"), + + /** + * link message has sent + */ + CODE_89010(89010, "link message has sent"), + + /** + * can not unlink nearby wxa + */ + CODE_89011(89011, "can not unlink nearby wxa"), + + /** + * can not unlink store or mall + */ + CODE_89012(89012, "can not unlink store or mall"), + + /** + * wxa is banned + */ + CODE_89013(89013, "wxa is banned"), + + /** + * support version error + */ + CODE_89014(89014, "support version error"), + + /** + * has linked wxa + */ + CODE_89015(89015, "has linked wxa"), + + /** + * reach same realname quota + */ + CODE_89016(89016, "reach same realname quota"), + + /** + * reach different realname quota + */ + CODE_89017(89017, "reach different realname quota"), + + /** + * unlink message has sent + */ + CODE_89018(89018, "unlink message has sent"), + + /** + * 业务域名无更改,无需重复设置 webview domain not change + */ + CODE_89019(89019, "业务域名无更改,无需重复设置"), + + /** + * 尚未设置小程序业务域名,请先在第三方平台中设置小程序业务域名后在调用本接口 open's webview domain is null! Need to set open's webview domain first! + */ + CODE_89020(89020, "尚未设置小程序业务域名,请先在第三方平台中设置小程序业务域名后在调用本接口"), + + /** + * 请求保存的域名不是第三方平台中已设置的小程序业务域名或子域名 request domain is not open's webview domain! + */ + CODE_89021(89021, "请求保存的域名不是第三方平台中已设置的小程序业务域名或子域名"), + + /** + * delete domain is not exist! + */ + CODE_89022(89022, "delete domain is not exist!"), + + /** + * 业务域名数量超过限制,最多可以添加100个业务域名 webview domain exceed limit + */ + CODE_89029(89029, "业务域名数量超过限制,最多可以添加100个业务域名"), + + /** + * operation reach month limit + */ + CODE_89030(89030, "operation reach month limit"), + + /** + * user bind reach limit + */ + CODE_89031(89031, "user bind reach limit"), + + /** + * weapp bind members reach limit + */ + CODE_89032(89032, "weapp bind members reach limit"), + + /** + * empty wx or openid + */ + CODE_89033(89033, "empty wx or openid"), + + /** + * userstr is invalid + */ + CODE_89034(89034, "userstr is invalid"), + + /** + * linking from mp + */ + CODE_89035(89035, "linking from mp"), + + /** + * 个人小程序不支持调用 setwebviewdomain 接口 not support single + */ + CODE_89231(89231, "个人小程序不支持调用 setwebviewdomain 接口"), + + /** + * hit black contractor + */ + CODE_89235(89235, "hit black contractor"), + + /** + * 该插件不能申请 this plugin can not apply + */ + CODE_89236(89236, "该插件不能申请"), + + /** + * 已经添加该插件 plugin has send apply message or already bind + */ + CODE_89237(89237, "已经添加该插件"), + + /** + * 申请或使用的插件已经达到上限 plugin count reach limit + */ + CODE_89238(89238, "申请或使用的插件已经达到上限"), + + /** + * 该插件不存在 plugin no exist + */ + CODE_89239(89239, "该插件不存在"), + + /** + * only applying status can be agreed or refused + */ + CODE_89240(89240, "only applying status can be agreed or refused"), + + /** + * only refused status can be deleted, please refused first + */ + CODE_89241(89241, "only refused status can be deleted, please refused first"), + + /** + * appid is no in the apply list, make sure appid is right + */ + CODE_89242(89242, "appid is no in the apply list, make sure appid is right"), + + /** + * 该申请为“待确认”状态,不可删除 can not delete apply request in 24 hours + */ + CODE_89243(89243, "该申请为“待确认”状态,不可删除"), + + /** + * 不存在该插件 appid plugin appid is no in the plugin list, make sure plugin appid is right + */ + CODE_89244(89244, "不存在该插件 appid"), + + /** + * mini program forbidden to link + */ + CODE_89245(89245, "mini program forbidden to link"), + + /** + * plugins with special category are used only by specific apps + */ + CODE_89246(89246, "plugins with special category are used only by specific apps"), + + /** + * 系统内部错误 inner error, retry after some while + */ + CODE_89247(89247, "系统内部错误"), + + /** + * invalid code type + */ + CODE_89248(89248, "invalid code type"), + + /** + * task running + */ + CODE_89249(89249, "task running"), + + /** + * 内部错误 inner error, retry after some while + */ + CODE_89250(89250, "内部错误"), + + /** + * 模板消息已下发,待法人人脸核身校验 legal person checking + */ + CODE_89251(89251, "模板消息已下发,待法人人脸核身校验"), + + /** + * {@code 法人&企业信息一致性校验中 front checking} + */ + CODE_89253(89253, "法人&企业信息一致性校验中"), + + /** + * lack of some component rights + */ + CODE_89254(89254, "lack of some component rights"), + + /** + * code参数无效,请检查code长度以及内容是否正确;注意code_type的值不同需要传的code长度不一样 enterprise code invalid + */ + CODE_89255(89255, "code参数无效,请检查code长度以及内容是否正确;注意code_type的值不同需要传的code长度不一样"), + + /** + * token 信息有误 no component info + */ + CODE_89256(89256, "token 信息有误"), + + /** + * 该插件版本不支持快速更新 no such version + */ + CODE_89257(89257, "该插件版本不支持快速更新"), + + /** + * 当前小程序帐号存在灰度发布中的版本,不可操作快速更新 code is gray online + */ + CODE_89258(89258, "当前小程序帐号存在灰度发布中的版本,不可操作快速更新"), + + /** + * zhibo plugin is not allow to delete + */ + CODE_89259(89259, "zhibo plugin is not allow to delete"), + + /** + * 订单无效 invalid trade + */ + CODE_89300(89300, "订单无效"), + + /** + * 系统不稳定,请稍后再试,如多次失败请通过社区反馈 + */ + CODE_89401(89401, "系统不稳定,请稍后再试,如多次失败请通过社区反馈"), + + /** + * 该小程序不在待审核队列,请检查是否已提交审核或已审完 + */ + CODE_89402(89402, "该小程序不在待审核队列,请检查是否已提交审核或已审完"), + + /** + * 本单属于平台不支持加急种类,请等待正常审核流程 + */ + CODE_89403(89403, "本单属于平台不支持加急种类,请等待正常审核流程"), + + /** + * 本单已加速成功,请勿重复提交 + */ + CODE_89404(89404, "本单已加速成功,请勿重复提交"), + + /** + * 本月加急额度已用完,请提高提审质量以获取更多额度 + */ + CODE_89405(89405, "本月加急额度已用完,请提高提审质量以获取更多额度"), + + /** + * 公众号有未处理的确认请求,请稍候重试 + */ + CODE_89501(89501, "公众号有未处理的确认请求,请稍候重试"), + + /** + * 请耐心等待管理员确认 + */ + CODE_89502(89502, "请耐心等待管理员确认"), + + /** + * 此次调用需要管理员确认,请耐心等候 + */ + CODE_89503(89503, "此次调用需要管理员确认,请耐心等候"), + + /** + * 正在等管理员确认,请耐心等待 + */ + CODE_89504(89504, "正在等管理员确认,请耐心等待"), + + /** + * 正在等管理员确认,请稍候重试 + */ + CODE_89505(89505, "正在等管理员确认,请稍候重试"), + + /** + * 该IP调用求请求已被公众号管理员拒绝,请24小时后再试,建议调用前与管理员沟通确认 + */ + CODE_89506(89506, "该IP调用求请求已被公众号管理员拒绝,请24小时后再试,建议调用前与管理员沟通确认"), + + /** + * 该IP调用求请求已被公众号管理员拒绝,请1小时后再试,建议调用前与管理员沟通确认 + */ + CODE_89507(89507, "该IP调用求请求已被公众号管理员拒绝,请1小时后再试,建议调用前与管理员沟通确认"), + + /** + * invalid order id + */ + CODE_90001(90001, "invalid order id"), + + /** + * invalid busi id + */ + CODE_90002(90002, "invalid busi id"), + + /** + * invalid bill date + */ + CODE_90003(90003, "invalid bill date"), + + /** + * invalid bill type + */ + CODE_90004(90004, "invalid bill type"), + + /** + * invalid platform + */ + CODE_90005(90005, "invalid platform"), + + /** + * bill not exists + */ + CODE_90006(90006, "bill not exists"), + + /** + * invalid openid + */ + CODE_90007(90007, "invalid openid"), + + /** + * mp_sig error + */ + CODE_90009(90009, "mp_sig error"), + + /** + * no session + */ + CODE_90010(90010, "no session"), + + /** + * sig error + */ + CODE_90011(90011, "sig error"), + + /** + * order exist + */ + CODE_90012(90012, "order exist"), + + /** + * balance not enough + */ + CODE_90013(90013, "balance not enough"), + + /** + * order has been confirmed + */ + CODE_90014(90014, "order has been confirmed"), + + /** + * order has been canceled + */ + CODE_90015(90015, "order has been canceled"), + + /** + * order is being processed + */ + CODE_90016(90016, "order is being processed"), + + /** + * no privilege + */ + CODE_90017(90017, "no privilege"), + + /** + * invalid parameter + */ + CODE_90018(90018, "invalid parameter"), + + /** + * 不是公众号快速创建的小程序 not fast register + */ + CODE_91001(91001, "不是公众号快速创建的小程序"), + + /** + * 小程序发布后不可改名 has published + */ + CODE_91002(91002, "小程序发布后不可改名"), + + /** + * 改名状态不合法 invalid change stat + */ + CODE_91003(91003, "改名状态不合法"), + + /** + * 昵称不合法 invalid nickname + */ + CODE_91004(91004, "昵称不合法"), + + /** + * 昵称 15 天主体保护 nickname protected + */ + CODE_91005(91005, "昵称 15 天主体保护"), + + /** + * 昵称命中微信号 nickname used by username + */ + CODE_91006(91006, "昵称命中微信号"), + + /** + * 昵称已被占用 nickname used + */ + CODE_91007(91007, "昵称已被占用"), + + /** + * 昵称命中 7 天侵权保护期 nickname protected + */ + CODE_91008(91008, "昵称命中 7 天侵权保护期"), + + /** + * 需要提交材料 nickname need proof + */ + CODE_91009(91009, "需要提交材料"), + + /** + * 其他错误 + */ + CODE_91010(91010, "其他错误"), + + /** + * 查不到昵称修改审核单信息 + */ + CODE_91011(91011, "查不到昵称修改审核单信息"), + + /** + * 其它错误 + */ + CODE_91012(91012, "其它错误"), + + /** + * 占用名字过多 lock name too more + */ + CODE_91013(91013, "占用名字过多"), + + /** + * +号规则 同一类型关联名主体不一致 diff master plus + */ + CODE_91014(91014, "+号规则 同一类型关联名主体不一致"), + + /** + * 原始名不同类型主体不一致 diff master + */ + CODE_91015(91015, "原始名不同类型主体不一致"), + + /** + * 名称占用者 ≥2 name more owner + */ + CODE_91016(91016, "名称占用者 ≥2"), + + /** + * +号规则 不同类型关联名主体不一致 other diff master plus + */ + CODE_91017(91017, "+号规则 不同类型关联名主体不一致"), + + /** + * 组织类型小程序发布后,侵权被清空昵称,需走认证改名 + */ + CODE_91018(91018, "组织类型小程序发布后,侵权被清空昵称,需走认证改名"), + + /** + * 小程序正在审核中 + */ + CODE_91019(91019, "小程序正在审核中"), + + /** + * 该经营资质已添加,请勿重复添加 + */ + CODE_92000(92000, "该经营资质已添加,请勿重复添加"), + + /** + * 附近地点添加数量达到上线,无法继续添加 + */ + CODE_92002(92002, "附近地点添加数量达到上线,无法继续添加"), + + /** + * 地点已被其它小程序占用 + */ + CODE_92003(92003, "地点已被其它小程序占用"), + + /** + * 附近功能被封禁 + */ + CODE_92004(92004, "附近功能被封禁"), + + /** + * 地点正在审核中 + */ + CODE_92005(92005, "地点正在审核中"), + + /** + * 地点正在展示小程序 + */ + CODE_92006(92006, "地点正在展示小程序"), + + /** + * 地点审核失败 + */ + CODE_92007(92007, "地点审核失败"), + + /** + * 小程序未展示在该地点 + */ + CODE_92008(92008, "小程序未展示在该地点"), + + /** + * 小程序未上架或不可见 + */ + CODE_93009(93009, "小程序未上架或不可见"), + + /** + * 地点不存在 + */ + CODE_93010(93010, "地点不存在"), + + /** + * 个人类型小程序不可用 + */ + CODE_93011(93011, "个人类型小程序不可用"), + + /** + * 非普通类型小程序(门店小程序、小店小程序等)不可用 + */ + CODE_93012(93012, "非普通类型小程序(门店小程序、小店小程序等)不可用"), + + /** + * 从腾讯地图获取地址详细信息失败 + */ + CODE_93013(93013, "从腾讯地图获取地址详细信息失败"), + + /** + * 同一资质证件号重复添加 + */ + CODE_93014(93014, "同一资质证件号重复添加"), + + /** + * 附近类目审核中 + */ + CODE_93015(93015, "附近类目审核中"), + + /** + * 服务标签个数超限制(官方最多5个,自定义最多4个) + */ + CODE_93016(93016, "服务标签个数超限制(官方最多5个,自定义最多4个)"), + + /** + * 服务标签或者客服的名字不符合要求 + */ + CODE_93017(93017, "服务标签或者客服的名字不符合要求"), + + /** + * 服务能力中填写的小程序appid不是同主体小程序 + */ + CODE_93018(93018, "服务能力中填写的小程序appid不是同主体小程序"), + + /** + * 申请类目之后才能添加附近地点 + */ + CODE_93019(93019, "申请类目之后才能添加附近地点"), + + /** + * qualification_list无效 + */ + CODE_93020(93020, "qualification_list无效"), + + /** + * company_name字段为空 + */ + CODE_93021(93021, "company_name字段为空"), + + /** + * credential字段为空 + */ + CODE_93022(93022, "credential字段为空"), + + /** + * address字段为空 + */ + CODE_93023(93023, "address字段为空"), + + /** + * qualification_list字段为空 + */ + CODE_93024(93024, "qualification_list字段为空"), + + /** + * 服务appid对应的path不存在 + */ + CODE_93025(93025, "服务appid对应的path不存在"), + + /** + * missing cert_serialno + */ + CODE_94001(94001, "missing cert_serialno"), + + /** + * use not register wechat pay + */ + CODE_94002(94002, "use not register wechat pay"), + + /** + * invalid sign + */ + CODE_94003(94003, "invalid sign"), + + /** + * user do not has real name info + */ + CODE_94004(94004, "user do not has real name info"), + + /** + * invalid user token + */ + CODE_94005(94005, "invalid user token"), + + /** + * appid unauthorized + */ + CODE_94006(94006, "appid unauthorized"), + + /** + * appid unbind mchid + */ + CODE_94007(94007, "appid unbind mchid"), + + /** + * invalid timestamp + */ + CODE_94008(94008, "invalid timestamp"), + + /** + * invalid cert_serialno, cert_serialno's size should be 40 + */ + CODE_94009(94009, "invalid cert_serialno, cert_serialno's size should be 40"), + + /** + * invalid mch_id + */ + CODE_94010(94010, "invalid mch_id"), + + /** + * timestamp expired + */ + CODE_94011(94011, "timestamp expired"), + + /** + * appid和商户号的绑定关系不存在 + */ + CODE_94012(94012, "appid和商户号的绑定关系不存在"), + + /** + * wxcode decode fail + */ + CODE_95001(95001, "wxcode decode fail"), + + /** + * wxcode recognize unautuorized + */ + CODE_95002(95002, "wxcode recognize unautuorized"), + + /** + * get product by page args invalid + */ + CODE_95101(95101, "get product by page args invalid"), + + /** + * get product materials by cond args invalid + */ + CODE_95102(95102, "get product materials by cond args invalid"), + + /** + * material id list size out of limit + */ + CODE_95103(95103, "material id list size out of limit"), + + /** + * import product frequence out of limit + */ + CODE_95104(95104, "import product frequence out of limit"), + + /** + * mp is importing products, api is rejected to import + */ + CODE_95105(95105, "mp is importing products, api is rejected to import"), + + /** + * api is rejected to import, need to set commission ratio on mp first + */ + CODE_95106(95106, "api is rejected to import, need to set commission ratio on mp first"), + + /** + * invalid image url + */ + CODE_101000(101000, "无效图片链接"), + + /** + * certificate not found + */ + CODE_101001(101001, "未找到证书"), + + /** + * not enough market quota + */ + CODE_101002(101002, "not enough market quota"), + + /** + * 入参错误 + */ + CODE_200002(200002, "入参错误"), + + /** + * 此账号已被封禁,无法操作 + */ + CODE_200011(200011, "此账号已被封禁,无法操作"), + + /** + * 个人模板数已达上限,上限25个 + */ + CODE_200012(200012, "个人模板数已达上限,上限25个"), + + /** + * 此模板已被封禁,无法选用 + */ + CODE_200013(200013, "此模板已被封禁,无法选用"), + + /** + * 模板 tid 参数错误 + */ + CODE_200014(200014, "模板 tid 参数错误"), + + /** + * start 参数错误 + */ + CODE_200016(200016, "start 参数错误"), + + /** + * limit 参数错误 + */ + CODE_200017(200017, "limit 参数错误"), + + /** + * 类目 ids 缺失 + */ + CODE_200018(200018, "类目 ids 缺失"), + + /** + * 类目 ids 不合法 + */ + CODE_200019(200019, "类目 ids 不合法"), + + /** + * 关键词列表 kidList 参数错误 + */ + CODE_200020(200020, "关键词列表 kidList 参数错误"), + + /** + * 场景描述 sceneDesc 参数错误 + */ + CODE_200021(200021, "场景描述 sceneDesc 参数错误"), + + /** + * {@code 禁止创建/更新商品(如商品创建功能被封禁) 或 禁止编辑&更新房间} + */ + CODE_300001(300001, "禁止创建/更新商品(如商品创建功能被封禁) 或 禁止编辑&更新房间"), + + /** + * 名称长度不符合规则 + */ + CODE_300002(300002, "名称长度不符合规则"), + + /** + * 价格输入不合规(如现价比原价大、传入价格非数字等) + */ + CODE_300003(300003, "价格输入不合规(如现价比原价大、传入价格非数字等)"), + + /** + * 商品名称存在违规违法内容 + */ + CODE_300004(300004, "商品名称存在违规违法内容"), + + /** + * 商品图片存在违规违法内容 + */ + CODE_300005(300005, "商品图片存在违规违法内容"), + + /** + * 图片上传失败(如mediaID过期) + */ + CODE_300006(300006, "图片上传失败(如mediaID过期)"), + + /** + * 线上小程序版本不存在该链接 + */ + CODE_300007(300007, "线上小程序版本不存在该链接"), + + /** + * 添加商品失败 + */ + CODE_300008(300008, "添加商品失败"), + + /** + * 商品审核撤回失败 + */ + CODE_300009(300009, "商品审核撤回失败"), + + /** + * 商品审核状态不对(如商品审核中) + */ + CODE_300010(300010, "商品审核状态不对(如商品审核中)"), + + /** + * 操作非法(API不允许操作非API创建的商品) + */ + CODE_300011(300011, "操作非法(API不允许操作非API创建的商品)"), + + /** + * 没有提审额度(每天500次提审额度) + */ + CODE_300012(300012, "没有提审额度(每天500次提审额度)"), + + /** + * 提审失败 + */ + CODE_300013(300013, "提审失败"), + + /** + * 审核中,无法删除(非零代表失败) + */ + CODE_300014(300014, "审核中,无法删除(非零代表失败)"), + + /** + * 商品未提审 + */ + CODE_300017(300017, "商品未提审"), + + /** + * 商品添加成功,审核失败 + */ + CODE_300021(300021, "商品添加成功,审核失败"), + + /** + * 此房间号不存在 + */ + CODE_300022(300022, "此房间号不存在"), + + /** + * 房间状态 拦截(当前房间状态不允许此操作) + */ + CODE_300023(300023, "房间状态 拦截(当前房间状态不允许此操作)"), + + /** + * 商品不存在 + */ + CODE_300024(300024, "商品不存在"), + + /** + * 商品审核未通过 + */ + CODE_300025(300025, "商品审核未通过"), + + /** + * 房间商品数量已经满额 + */ + CODE_300026(300026, "房间商品数量已经满额"), + + /** + * 导入商品失败 + */ + CODE_300027(300027, "导入商品失败"), + + /** + * 房间名称违规 + */ + CODE_300028(300028, "房间名称违规"), + + /** + * 主播昵称违规 + */ + CODE_300029(300029, "主播昵称违规"), + + /** + * 主播微信号不合法 + */ + CODE_300030(300030, "主播微信号不合法"), + + /** + * 直播间封面图不合规 + */ + CODE_300031(300031, "直播间封面图不合规"), + + /** + * 直播间分享图违规 + */ + CODE_300032(300032, "直播间分享图违规"), + + /** + * 添加商品超过直播间上限 + */ + CODE_300033(300033, "添加商品超过直播间上限"), + + /** + * 主播微信昵称长度不符合要求 + */ + CODE_300034(300034, "主播微信昵称长度不符合要求"), + + /** + * 主播微信号不存在 + */ + CODE_300035(300035, "主播微信号不存在"), + + /** + * 主播微信号未实名认证 + */ + CODE_300036(300036, "主播微信号未实名认证"), + + /** + * invalid file name + */ + CODE_600001(600001, "invalid file name"), + + /** + * no permission to upload file + */ + CODE_600002(600002, "no permission to upload file"), + + /** + * invalid size of source + */ + CODE_600003(600003, "invalid size of source"), + + /** + * 票据已存在 + */ + CODE_928000(928000, "票据已存在"), + + /** + * 票据不存在 + */ + CODE_928001(928001, "票据不存在"), + + /** + * sysem error + */ + CODE_930555(930555, "sysem error"), + + /** + * delivery timeout + */ + CODE_930556(930556, "delivery timeout"), + + /** + * delivery system error + */ + CODE_930557(930557, "delivery system error"), + + /** + * delivery logic error + */ + CODE_930558(930558, "delivery logic error"), + + /** + * 沙盒环境openid无效 invaild openid + */ + CODE_930559(930559, "沙盒环境openid无效"), + + /** + * shopid need bind first + */ + CODE_930560(930560, "shopid need bind first"), + + /** + * 参数错误 args error + */ + CODE_930561(930561, "参数错误"), + + /** + * order already exists + */ + CODE_930562(930562, "order already exists"), + + /** + * 订单不存在 order not exists + */ + CODE_930563(930563, "订单不存在"), + + /** + * 沙盒环境调用无配额 quota run out, try next day + */ + CODE_930564(930564, "沙盒环境调用无配额"), + + /** + * order finished + */ + CODE_930565(930565, "order finished"), + + /** + * not support, plz auth at mp.weixin.qq.com + */ + CODE_930566(930566, "not support, plz auth at mp.weixin.qq.com"), + + /** + * shop arg error + */ + CODE_930567(930567, "shop arg error"), + + /** + * 不支持个人类型小程序 not personal account + */ + CODE_930568(930568, "不支持个人类型小程序"), + + /** + * 已经开通不需要再开通 already open + */ + CODE_930569(930569, "已经开通不需要再开通"), + + /** + * cargo_first_class or cargo_second_class invalid + */ + CODE_930570(930570, "cargo_first_class or cargo_second_class invalid"), + + /** + * 该商户没有内测权限,请先申请权限: https://wj.qq.com/s2/7243532/fcfb/ + */ + CODE_930571(930571, "该商户没有内测权限,请先申请权限: https://wj.qq.com/s2/7243532/fcfb/"), + + /** + * fee already set + */ + CODE_931010(931010, "fee already set"), + + /** + * unbind download url + */ + CODE_6000100(6000100, "unbind download url"), + + /** + * no response data + */ + CODE_6000101(6000101, "no response data"), + + /** + * response data too big + */ + CODE_6000102(6000102, "response data too big"), + + /** + * POST 数据参数不合法 + */ + CODE_9001001(9001001, "POST 数据参数不合法"), + + /** + * 远端服务不可用 + */ + CODE_9001002(9001002, "远端服务不可用"), + + /** + * Ticket 不合法 + */ + CODE_9001003(9001003, "Ticket 不合法"), + + /** + * 获取摇周边用户信息失败 + */ + CODE_9001004(9001004, "获取摇周边用户信息失败"), + + /** + * 获取商户信息失败 + */ + CODE_9001005(9001005, "获取商户信息失败"), + + /** + * 获取 OpenID 失败 + */ + CODE_9001006(9001006, "获取 OpenID 失败"), + + /** + * 上传文件缺失 + */ + CODE_9001007(9001007, "上传文件缺失"), + + /** + * 上传素材的文件类型不合法 + */ + CODE_9001008(9001008, "上传素材的文件类型不合法"), + + /** + * 上传素材的文件尺寸不合法 + */ + CODE_9001009(9001009, "上传素材的文件尺寸不合法"), + + /** + * 上传失败 + */ + CODE_9001010(9001010, "上传失败"), + + /** + * 帐号不合法 + */ + CODE_9001020(9001020, "帐号不合法"), + + /** + * 已有设备激活率低于 50% ,不能新增设备 + */ + CODE_9001021(9001021, "已有设备激活率低于 50% ,不能新增设备"), + + /** + * 设备申请数不合法,必须为大于 0 的数字 + */ + CODE_9001022(9001022, "设备申请数不合法,必须为大于 0 的数字"), + + /** + * 已存在审核中的设备 ID 申请 + */ + CODE_9001023(9001023, "已存在审核中的设备 ID 申请"), + + /** + * 一次查询设备 ID 数量不能超过 50 + */ + CODE_9001024(9001024, "一次查询设备 ID 数量不能超过 50"), + + /** + * 设备 ID 不合法 + */ + CODE_9001025(9001025, "设备 ID 不合法"), + + /** + * 页面 ID 不合法 + */ + CODE_9001026(9001026, "页面 ID 不合法"), + + /** + * 页面参数不合法 + */ + CODE_9001027(9001027, "页面参数不合法"), + + /** + * 一次删除页面 ID 数量不能超过 10 + */ + CODE_9001028(9001028, "一次删除页面 ID 数量不能超过 10"), + + /** + * 页面已应用在设备中,请先解除应用关系再删除 + */ + CODE_9001029(9001029, "页面已应用在设备中,请先解除应用关系再删除"), + + /** + * 一次查询页面 ID 数量不能超过 50 + */ + CODE_9001030(9001030, "一次查询页面 ID 数量不能超过 50"), + + /** + * 时间区间不合法 + */ + CODE_9001031(9001031, "时间区间不合法"), + + /** + * 保存设备与页面的绑定关系参数错误 + */ + CODE_9001032(9001032, "保存设备与页面的绑定关系参数错误"), + + /** + * 门店 ID 不合法 + */ + CODE_9001033(9001033, "门店 ID 不合法"), + + /** + * 设备备注信息过长 + */ + CODE_9001034(9001034, "设备备注信息过长"), + + /** + * 设备申请参数不合法 + */ + CODE_9001035(9001035, "设备申请参数不合法"), + + /** + * 查询起始值 begin 不合法 + */ + CODE_9001036(9001036, "查询起始值 begin 不合法"), + + /** + * params invalid + */ + CODE_9002008(9002008, "params invalid"), + + /** + * shop id not exist + */ + CODE_9002009(9002009, "shop id not exist"), + + /** + * ssid or password should start with "WX" + */ + CODE_9002010(9002010, "ssid or password should start with \"WX\""), + + /** + * ssid can not include chinese + */ + CODE_9002011(9002011, "ssid can not include chinese"), + + /** + * passsword can not include chinese + */ + CODE_9002012(9002012, "passsword can not include chinese"), + + /** + * password must be between 8 and 24 characters + */ + CODE_9002013(9002013, "password must be between 8 and 24 characters"), + + /** + * device exist + */ + CODE_9002016(9002016, "device exist"), + + /** + * device not exist + */ + CODE_9002017(9002017, "device not exist"), + + /** + * the size of query list reach limit + */ + CODE_9002026(9002026, "the size of query list reach limit"), + + /** + * not allowed to modify, ensure you are an certified or component account + */ + CODE_9002041(9002041, "not allowed to modify, ensure you are an certified or component account"), + + /** + * invalid ssid, can not include none utf8 characters, and should be between 1 and 32 bytes + */ + CODE_9002044(9002044, "invalid ssid, can not include none utf8 characters, and should be between 1 and 32 bytes"), + + /** + * shop id has not be audited, this bar type is not enable + */ + CODE_9002052(9002052, "shop id has not be audited, this bar type is not enable"), + + /** + * protocol type is not same with the exist device + */ + CODE_9007003(9007003, "protocol type is not same with the exist device"), + + /** + * ssid not exist + */ + CODE_9007004(9007004, "ssid not exist"), + + /** + * device count limit + */ + CODE_9007005(9007005, "device count limit"), + + /** + * card info not exist + */ + CODE_9008001(9008001, "card info not exist"), + + /** + * card expiration time is invalid + */ + CODE_9008002(9008002, "card expiration time is invalid"), + + /** + * url size invalid, keep less than 255 + */ + CODE_9008003(9008003, "url size invalid, keep less than 255"), + + /** + * url can not include chinese + */ + CODE_9008004(9008004, "url can not include chinese"), + + /** + * order_id not exist + */ + CODE_9200001(9200001, "order_id not exist"), + + /** + * order of other biz + */ + CODE_9200002(9200002, "order of other biz"), + + /** + * blocked + */ + CODE_9200003(9200003, "blocked"), + + /** + * payment notice disabled + */ + CODE_9200211(9200211, "payment notice disabled"), + + /** + * payment notice not exist + */ + CODE_9200231(9200231, "payment notice not exist"), + + /** + * payment notice paid + */ + CODE_9200232(9200232, "payment notice paid"), + + /** + * payment notice canceled + */ + CODE_9200233(9200233, "payment notice canceled"), + + /** + * payment notice expired + */ + CODE_9200235(9200235, "payment notice expired"), + + /** + * bank not allow + */ + CODE_9200236(9200236, "bank not allow"), + + /** + * freq limit + */ + CODE_9200295(9200295, "freq limit"), + + /** + * suspend payment at current time + */ + CODE_9200297(9200297, "suspend payment at current time"), + + /** + * 3rd resp decrypt error + */ + CODE_9200298(9200298, "3rd resp decrypt error"), + + /** + * 3rd resp system error + */ + CODE_9200299(9200299, "3rd resp system error"), + + /** + * 3rd resp sign error + */ + CODE_9200300(9200300, "3rd resp sign error"), + + /** + * desc empty + */ + CODE_9201000(9201000, "desc empty"), + + /** + * fee not equal items' + */ + CODE_9201001(9201001, "fee not equal items'"), + + /** + * payment info incorrect + */ + CODE_9201002(9201002, "payment info incorrect"), + + /** + * fee is 0 + */ + CODE_9201003(9201003, "fee is 0"), + + /** + * payment expire date format error + */ + CODE_9201004(9201004, "payment expire date format error"), + + /** + * appid error + */ + CODE_9201005(9201005, "appid error"), + + /** + * payment order id error + */ + CODE_9201006(9201006, "payment order id error"), + + /** + * openid error + */ + CODE_9201007(9201007, "openid error"), + + /** + * return_url error + */ + CODE_9201008(9201008, "return_url error"), + + /** + * ip error + */ + CODE_9201009(9201009, "ip error"), + + /** + * order_id error + */ + CODE_9201010(9201010, "order_id error"), + + /** + * reason error + */ + CODE_9201011(9201011, "reason error"), + + /** + * mch_id error + */ + CODE_9201012(9201012, "mch_id error"), + + /** + * bill_date error + */ + CODE_9201013(9201013, "bill_date error"), + + /** + * bill_type error + */ + CODE_9201014(9201014, "bill_type error"), + + /** + * trade_type error + */ + CODE_9201015(9201015, "trade_type error"), + + /** + * bank_id error + */ + CODE_9201016(9201016, "bank_id error"), + + /** + * bank_account error + */ + CODE_9201017(9201017, "bank_account error"), + + /** + * payment_notice_no error + */ + CODE_9201018(9201018, "payment_notice_no error"), + + /** + * department_code error + */ + CODE_9201019(9201019, "department_code error"), + + /** + * payment_notice_type error + */ + CODE_9201020(9201020, "payment_notice_type error"), + + /** + * region_code error + */ + CODE_9201021(9201021, "region_code error"), + + /** + * department_name error + */ + CODE_9201022(9201022, "department_name error"), + + /** + * fee not equal finance's + */ + CODE_9201023(9201023, "fee not equal finance's"), + + /** + * refund_out_id error + */ + CODE_9201024(9201024, "refund_out_id error"), + + /** + * not combined order_id + */ + CODE_9201026(9201026, "not combined order_id"), + + /** + * partial sub order is test + */ + CODE_9201027(9201027, "partial sub order is test"), + + /** + * desc too long + */ + CODE_9201029(9201029, "desc too long"), + + /** + * sub order list size error + */ + CODE_9201031(9201031, "sub order list size error"), + + /** + * sub order repeated + */ + CODE_9201032(9201032, "sub order repeated"), + + /** + * auth_code empty + */ + CODE_9201033(9201033, "auth_code empty"), + + /** + * bank_id empty but mch_id not empty + */ + CODE_9201034(9201034, "bank_id empty but mch_id not empty"), + + /** + * sum of other fees exceed total fee + */ + CODE_9201035(9201035, "sum of other fees exceed total fee"), + + /** + * other user paying + */ + CODE_9202000(9202000, "other user paying"), + + /** + * pay process not finish + */ + CODE_9202001(9202001, "pay process not finish"), + + /** + * no refund permission + */ + CODE_9202002(9202002, "no refund permission"), + + /** + * ip limit + */ + CODE_9202003(9202003, "ip limit"), + + /** + * freq limit + */ + CODE_9202004(9202004, "freq limit"), + + /** + * user weixin account abnormal + */ + CODE_9202005(9202005, "user weixin account abnormal"), + + /** + * account balance not enough + */ + CODE_9202006(9202006, "account balance not enough"), + + /** + * refund request repeated + */ + CODE_9202010(9202010, "refund request repeated"), + + /** + * has refunded + */ + CODE_9202011(9202011, "has refunded"), + + /** + * refund exceed total fee + */ + CODE_9202012(9202012, "refund exceed total fee"), + + /** + * busi_id dup + */ + CODE_9202013(9202013, "busi_id dup"), + + /** + * not check sign + */ + CODE_9202016(9202016, "not check sign"), + + /** + * check sign failed + */ + CODE_9202017(9202017, "check sign failed"), + + /** + * sub order error + */ + CODE_9202018(9202018, "sub order error"), + + /** + * order status error + */ + CODE_9202020(9202020, "order status error"), + + /** + * unified order repeatedly + */ + CODE_9202021(9202021, "unified order repeatedly"), + + /** + * request to notification url fail + */ + CODE_9203000(9203000, "request to notification url fail"), + + /** + * http request fail + */ + CODE_9203001(9203001, "http request fail"), + + /** + * http response data error + */ + CODE_9203002(9203002, "http response data error"), + + /** + * http response data RSA decrypt fail + */ + CODE_9203003(9203003, "http response data RSA decrypt fail"), + + /** + * http response data AES decrypt fail + */ + CODE_9203004(9203004, "http response data AES decrypt fail"), + + /** + * system busy, please try again later + */ + CODE_9203999(9203999, "system busy, please try again later"), + + /** + * getrealname token error + */ + CODE_9204000(9204000, "getrealname token error"), + + /** + * getrealname user or token error + */ + CODE_9204001(9204001, "getrealname user or token error"), + + /** + * getrealname appid or token error + */ + CODE_9204002(9204002, "getrealname appid or token error"), + + /** + * finance conf not exist + */ + CODE_9205000(9205000, "finance conf not exist"), + + /** + * bank conf not exist + */ + CODE_9205001(9205001, "bank conf not exist"), + + /** + * wei ban ju conf not exist + */ + CODE_9205002(9205002, "wei ban ju conf not exist"), + + /** + * symmetric key conf not exist + */ + CODE_9205010(9205010, "symmetric key conf not exist"), + + /** + * out order id not exist + */ + CODE_9205101(9205101, "out order id not exist"), + + /** + * bill not exist + */ + CODE_9205201(9205201, "bill not exist"), + + /** + * 3rd resp pay_channel empty + */ + CODE_9206000(9206000, "3rd resp pay_channel empty"), + + /** + * 3rd resp order_id empty + */ + CODE_9206001(9206001, "3rd resp order_id empty"), + + /** + * 3rd resp bill_type_code empty + */ + CODE_9206002(9206002, "3rd resp bill_type_code empty"), + + /** + * 3rd resp bill_no empty + */ + CODE_9206003(9206003, "3rd resp bill_no empty"), + + /** + * 3rd resp empty + */ + CODE_9206200(9206200, "3rd resp empty"), + + /** + * 3rd resp not json + */ + CODE_9206201(9206201, "3rd resp not json"), + + /** + * connect 3rd error + */ + CODE_9206900(9206900, "connect 3rd error"), + + /** + * connect 3rd timeout + */ + CODE_9206901(9206901, "connect 3rd timeout"), + + /** + * read 3rd resp error + */ + CODE_9206910(9206910, "read 3rd resp error"), + + /** + * read 3rd resp timeout + */ + CODE_9206911(9206911, "read 3rd resp timeout"), + + /** + * boss error + */ + CODE_9207000(9207000, "boss error"), + + /** + * wechat pay error + */ + CODE_9207001(9207001, "wechat pay error"), + + /** + * boss param error + */ + CODE_9207002(9207002, "boss param error"), + + /** + * pay error + */ + CODE_9207003(9207003, "pay error"), + + /** + * auth_code expired + */ + CODE_9207004(9207004, "auth_code expired"), + + /** + * user balance not enough + */ + CODE_9207005(9207005, "user balance not enough"), + + /** + * card not support + */ + CODE_9207006(9207006, "card not support"), + + /** + * order reversed + */ + CODE_9207007(9207007, "order reversed"), + + /** + * user paying, need input password + */ + CODE_9207008(9207008, "user paying, need input password"), + + /** + * auth_code error + */ + CODE_9207009(9207009, "auth_code error"), + + /** + * auth_code invalid + */ + CODE_9207010(9207010, "auth_code invalid"), + + /** + * not allow to reverse when user paying + */ + CODE_9207011(9207011, "not allow to reverse when user paying"), + + /** + * order paid + */ + CODE_9207012(9207012, "order paid"), + + /** + * order closed + */ + CODE_9207013(9207013, "order closed"), + + /** + * vehicle not exists + */ + CODE_9207028(9207028, "vehicle not exists"), + + /** + * vehicle request blocked + */ + CODE_9207029(9207029, "vehicle request blocked"), + + /** + * vehicle auth error + */ + CODE_9207030(9207030, "vehicle auth error"), + + /** + * contract over limit + */ + CODE_9207031(9207031, "contract over limit"), + + /** + * trade error + */ + CODE_9207032(9207032, "trade error"), + + /** + * trade time invalid + */ + CODE_9207033(9207033, "trade time invalid"), + + /** + * channel type invalid + */ + CODE_9207034(9207034, "channel type invalid"), + + /** + * expire_time range error + */ + CODE_9207050(9207050, "expire_time range error"), + + /** + * query finance error + */ + CODE_9210000(9210000, "query finance error"), + + /** + * openid error + */ + CODE_9291000(9291000, "openid error"), + + /** + * openid appid not match + */ + CODE_9291001(9291001, "openid appid not match"), + + /** + * app_appid not exist + */ + CODE_9291002(9291002, "app_appid not exist"), + + /** + * app_appid not app + */ + CODE_9291003(9291003, "app_appid not app"), + + /** + * appid empty + */ + CODE_9291004(9291004, "appid empty"), + + /** + * appid not match access_token + */ + CODE_9291005(9291005, "appid not match access_token"), + + /** + * invalid sign + */ + CODE_9291006(9291006, "invalid sign"), + + /** + * backend logic error + */ + CODE_9299999(9299999, "backend logic error"), + + /** + * begin_time can not before now + */ + CODE_9300001(9300001, "begin_time can not before now"), + + /** + * end_time can not before now + */ + CODE_9300002(9300002, "end_time can not before now"), + + /** + * begin_time must less than end_time + */ + CODE_9300003(9300003, "begin_time must less than end_time"), + + /** + * {@code end_time - begin_time > 1year} + */ + CODE_9300004(9300004, "end_time - begin_time > 1year"), + + /** + * invalid max_partic_times + */ + CODE_9300005(9300005, "invalid max_partic_times"), + + /** + * invalid activity status + */ + CODE_9300006(9300006, "invalid activity status"), + + /** + * {@code gift_num must >0 and <=15} + */ + CODE_9300007(9300007, "gift_num must >0 and <=15"), + + /** + * invalid tiny appid + */ + CODE_9300008(9300008, "invalid tiny appid"), + + /** + * activity can not finish + */ + CODE_9300009(9300009, "activity can not finish"), + + /** + * {@code card_info_list must >= 2} + */ + CODE_9300010(9300010, "card_info_list must >= 2"), + + /** + * invalid card_id + */ + CODE_9300011(9300011, "invalid card_id"), + + /** + * card_id must belong this appid + */ + CODE_9300012(9300012, "card_id must belong this appid"), + + /** + * card_id is not swipe_card or pay.cash + */ + CODE_9300013(9300013, "card_id is not swipe_card or pay.cash"), + + /** + * some card_id is out of stock + */ + CODE_9300014(9300014, "some card_id is out of stock"), + + /** + * some card_id is invalid status + */ + CODE_9300015(9300015, "some card_id is invalid status"), + + /** + * membership or new/old tinyapp user only support one + */ + CODE_9300016(9300016, "membership or new/old tinyapp user only support one"), + + /** + * invalid logic for membership + */ + CODE_9300017(9300017, "invalid logic for membership"), + + /** + * invalid logic for tinyapp new/old user + */ + CODE_9300018(9300018, "invalid logic for tinyapp new/old user"), + + /** + * invalid activity type + */ + CODE_9300019(9300019, "invalid activity type"), + + /** + * invalid activity_id + */ + CODE_9300020(9300020, "invalid activity_id"), + + /** + * invalid help_max_times + */ + CODE_9300021(9300021, "invalid help_max_times"), + + /** + * invalid cover_url + */ + CODE_9300022(9300022, "invalid cover_url"), + + /** + * invalid gen_limit + */ + CODE_9300023(9300023, "invalid gen_limit"), + + /** + * card's end_time cannot early than act's end_time + */ + CODE_9300024(9300024, "card's end_time cannot early than act's end_time"), + + /** + * 快递侧逻辑错误,详细原因需要看 delivery_resultcode, 请先确认一下编码方式,python建议 json.dumps(b, ensure_ascii=False),php建议 json_encode($arr, JSON_UNESCAPED_UNICODE) Delivery side error + */ + CODE_9300501(9300501, "快递侧逻辑错误,详细原因需要看 delivery_resultcode, 请先确认一下编码方式,python建议 json.dumps(b, ensure_ascii=False),php建议 json_encode($arr, JSON_UNESCAPED_UNICODE)"), + + /** + * 快递公司系统错误 Delivery side sys error + */ + CODE_9300502(9300502, "快递公司系统错误"), + + /** + * delivery_id 不存在 Specified delivery id is not registerred + */ + CODE_9300503(9300503, "delivery_id 不存在"), + + /** + * service_type 不存在 Specified delivery id has beed banned + */ + CODE_9300504(9300504, "service_type 不存在"), + + /** + * Shop banned + */ + CODE_9300505(9300505, "Shop banned"), + + /** + * 运单 ID 已经存在轨迹,不可取消 Order can't cancel + */ + CODE_9300506(9300506, "运单 ID 已经存在轨迹,不可取消"), + + /** + * Token 不正确 invalid token, can't decryption or decryption result is different from the plaintext + */ + CODE_9300507(9300507, "Token 不正确"), + + /** + * order id has been used + */ + CODE_9300508(9300508, "order id has been used"), + + /** + * speed limit, retry too fast + */ + CODE_9300509(9300509, "speed limit, retry too fast"), + + /** + * invalid service type + */ + CODE_9300510(9300510, "invalid service type"), + + /** + * invalid branch id + */ + CODE_9300511(9300511, "invalid branch id"), + + /** + * 模板格式错误,渲染失败 invalid waybill template format + */ + CODE_9300512(9300512, "模板格式错误,渲染失败"), + + /** + * out of quota + */ + CODE_9300513(9300513, "out of quota"), + + /** + * add net branch fail, try update branch api + */ + CODE_9300514(9300514, "add net branch fail, try update branch api"), + + /** + * wxa appid not exist + */ + CODE_9300515(9300515, "wxa appid not exist"), + + /** + * wxa appid and current bizuin is not linked or not the same owner + */ + CODE_9300516(9300516, "wxa appid and current bizuin is not linked or not the same owner"), + + /** + * update_type 不正确,请使用"bind" 或者“unbind” invalid update_type, please use [bind] or [unbind] + */ + CODE_9300517(9300517, "update_type 不正确,请使用\"bind\" 或者“unbind”"), + + /** + * invalid delivery id + */ + CODE_9300520(9300520, "invalid delivery id"), + + /** + * the orderid is in our system, and waybill is generating + */ + CODE_9300521(9300521, "the orderid is in our system, and waybill is generating"), + + /** + * this orderid is repeated + */ + CODE_9300522(9300522, "this orderid is repeated"), + + /** + * quota is not enough; go to charge please + */ + CODE_9300523(9300523, "quota is not enough; go to charge please"), + + /** + * 订单已取消(一般为重复取消订单) order already canceled + */ + CODE_9300524(9300524, "订单已取消(一般为重复取消订单)"), + + /** + * bizid未绑定 biz id not bind + */ + CODE_9300525(9300525, "bizid未绑定"), + + /** + * 参数字段长度不正确 arg size exceed limit + */ + CODE_9300526(9300526, "参数字段长度不正确"), + + /** + * delivery does not support quota + */ + CODE_9300527(9300527, "delivery does not support quota"), + + /** + * invalid waybill_id + */ + CODE_9300528(9300528, "invalid waybill_id"), + + /** + * 账号已绑定过 biz_id already binded + */ + CODE_9300529(9300529, "账号已绑定过"), + + /** + * 解绑的biz_id不存在 biz_id is not exist + */ + CODE_9300530(9300530, "解绑的biz_id不存在"), + + /** + * bizid无效 或者密码错误 invalid biz_id or password + */ + CODE_9300531(9300531, "bizid无效 或者密码错误"), + + /** + * 绑定已提交,审核中 bind submit, and is checking + */ + CODE_9300532(9300532, "绑定已提交,审核中"), + + /** + * invalid tagid_list + */ + CODE_9300533(9300533, "invalid tagid_list"), + + /** + * add_source=2时,wx_appid和当前小程序不同主体 invalid appid, not same body + */ + CODE_9300534(9300534, "add_source=2时,wx_appid和当前小程序不同主体"), + + /** + * shop字段商品缩略图 url、商品名称为空或者非法,或者商品数量为0 invalid shop arg + */ + CODE_9300535(9300535, "shop字段商品缩略图 url、商品名称为空或者非法,或者商品数量为0"), + + /** + * add_source=2时,wx_appid无效 invalid wxa_appid + */ + CODE_9300536(9300536, "add_source=2时,wx_appid无效"), + + /** + * freq limit + */ + CODE_9300537(9300537, "freq limit"), + + /** + * input task empty + */ + CODE_9300538(9300538, "input task empty"), + + /** + * too many task + */ + CODE_9300539(9300539, "too many task"), + + /** + * task not exist + */ + CODE_9300540(9300540, "task not exist"), + + /** + * delivery callback error + */ + CODE_9300541(9300541, "delivery callback error"), + + /** + * id_card_no is invalid + */ + CODE_9300601(9300601, "id_card_no is invalid"), + + /** + * name is invalid + */ + CODE_9300602(9300602, "name is invalid"), + + /** + * plate_no is invalid + */ + CODE_9300603(9300603, "plate_no is invalid"), + + /** + * auth_key decode error + */ + CODE_9300604(9300604, "auth_key decode error"), + + /** + * auth_key is expired + */ + CODE_9300605(9300605, "auth_key is expired"), + + /** + * auth_key and appinfo not match + */ + CODE_9300606(9300606, "auth_key and appinfo not match"), + + /** + * user not confirm + */ + CODE_9300607(9300607, "user not confirm"), + + /** + * user confirm is expired + */ + CODE_9300608(9300608, "user confirm is expired"), + + /** + * api exceed limit + */ + CODE_9300609(9300609, "api exceed limit"), + + /** + * car license info is invalid + */ + CODE_9300610(9300610, "car license info is invalid"), + + /** + * varification type not support + */ + CODE_9300611(9300611, "varification type not support"), + + /** + * input param error + */ + CODE_9300701(9300701, "input param error"), + + /** + * this code has been used + */ + CODE_9300702(9300702, "this code has been used"), + + /** + * invalid date + */ + CODE_9300703(9300703, "invalid date"), + + /** + * not currently available + */ + CODE_9300704(9300704, "not currently available"), + + /** + * code not exist or expired + */ + CODE_9300705(9300705, "code not exist or expired"), + + /** + * code not exist or expired + */ + CODE_9300706(9300706, "code not exist or expired"), + + /** + * wxpay error + */ + CODE_9300707(9300707, "wxpay error"), + + /** + * wxpay overlimit + */ + CODE_9300708(9300708, "wxpay overlimit"), + + /** + * 无效的微信号 + */ + CODE_9300801(9300801, "无效的微信号"), + + /** + * 服务号未开通导购功能 + */ + CODE_9300802(9300802, "服务号未开通导购功能"), + + /** + * 微信号已经绑定为导购 + */ + CODE_9300803(9300803, "微信号已经绑定为导购"), + + /** + * 该微信号不是导购 + */ + CODE_9300804(9300804, "该微信号不是导购"), + + /** + * 微信号已经被其他账号绑定为导购 + */ + CODE_9300805(9300805, "微信号已经被其他账号绑定为导购"), + + /** + * 粉丝和导购不存在绑定关系 + */ + CODE_9300806(9300806, "粉丝和导购不存在绑定关系"), + + /** + * 标签值无效,不是可选标签值 + */ + CODE_9300807(9300807, "标签值无效,不是可选标签值"), + + /** + * 标签值不存在 + */ + CODE_9300808(9300808, "标签值不存在"), + + /** + * 展示标签值不存在 + */ + CODE_9300809(9300809, "展示标签值不存在"), + + /** + * 导购昵称太长,最多16个字符 + */ + CODE_9300810(9300810, "导购昵称太长,最多16个字符"), + + /** + * 只支持mmbiz.qpic.cn域名的图片 + */ + CODE_9300811(9300811, "只支持mmbiz.qpic.cn域名的图片"), + + /** + * 达到导购绑定个数限制 + */ + CODE_9300812(9300812, "达到导购绑定个数限制"), + + /** + * 达到导购粉丝绑定个数限制 + */ + CODE_9300813(9300813, "达到导购粉丝绑定个数限制"), + + /** + * 敏感词个数超过上限 + */ + CODE_9300814(9300814, "敏感词个数超过上限"), + + /** + * 快捷回复个数超过上限 + */ + CODE_9300815(9300815, "快捷回复个数超过上限"), + + /** + * 文字素材个数超过上限 + */ + CODE_9300816(9300816, "文字素材个数超过上限"), + + /** + * 小程序卡片素材个数超过上限 + */ + CODE_9300817(9300817, "小程序卡片素材个数超过上限"), + + /** + * 图片素材个数超过上限 + */ + CODE_9300818(9300818, "图片素材个数超过上限"), + + /** + * mediaid 有误 + */ + CODE_9300819(9300819, "mediaid 有误"), + + /** + * 可查询标签类别超过上限 + */ + CODE_9300820(9300820, "可查询标签类别超过上限"), + + /** + * 小程序卡片内appid不符合要求 + */ + CODE_9300821(9300821, "小程序卡片内appid不符合要求"), + + /** + * 标签类别的名字无效 + */ + CODE_9300822(9300822, "标签类别的名字无效"), + + /** + * 查询聊天记录时间参数有误 + */ + CODE_9300823(9300823, "查询聊天记录时间参数有误"), + + /** + * 自动回复字数太长 + */ + CODE_9300824(9300824, "自动回复字数太长"), + + /** + * 导购群组id错误 + */ + CODE_9300825(9300825, "导购群组id错误"), + + /** + * 维护中 + */ + CODE_9300826(9300826, "维护中"), + + /** + * invalid parameter + */ + CODE_9301001(9301001, "invalid parameter"), + + /** + * call api service failed + */ + CODE_9301002(9301002, "call api service failed"), + + /** + * internal exception + */ + CODE_9301003(9301003, "internal exception"), + + /** + * save data error + */ + CODE_9301004(9301004, "save data error"), + + /** + * invalid appid + */ + CODE_9301006(9301006, "invalid appid"), + + /** + * invalid api config + */ + CODE_9301007(9301007, "invalid api config"), + + /** + * invalid api info + */ + CODE_9301008(9301008, "invalid api info"), + + /** + * add result check failed + */ + CODE_9301009(9301009, "add result check failed"), + + /** + * consumption failure + */ + CODE_9301010(9301010, "consumption failure"), + + /** + * frequency limit reached + */ + CODE_9301011(9301011, "frequency limit reached"), + + /** + * service timeout + */ + CODE_9301012(9301012, "service timeout"), + + /** + * 该开发小程序已开通小程序直播权限,不支持发布版本。如需发版,请解绑开发小程序后再操作。 + */ + CODE_9400001(9400001, "该开发小程序已开通小程序直播权限,不支持发布版本。如需发版,请解绑开发小程序后再操作。"), + + /** + * 商品已存在 + */ + CODE_9401001(9401001, "商品已存在"), + + /** + * 商品不存在 + */ + CODE_9401002(9401002, "商品不存在"), + + /** + * 类目已存在 + */ + CODE_9401003(9401003, "类目已存在"), + + /** + * 类目不存在 + */ + CODE_9401004(9401004, "类目不存在"), + + /** + * SKU已存在 + */ + CODE_9401005(9401005, "SKU已存在"), + + /** + * SKU不存在 + */ + CODE_9401006(9401006, "SKU不存在"), + + /** + * 属性已存在 + */ + CODE_9401007(9401007, "属性已存在"), + + /** + * 属性不存在 + */ + CODE_9401008(9401008, "属性不存在"), + + /** + * 非法参数 + */ + CODE_9401020(9401020, "非法参数"), + + /** + * 没有商品权限 + */ + CODE_9401021(9401021, "没有商品权限"), + + /** + * SPU NOT ALLOW + */ + CODE_9401022(9401022, "SPU NOT ALLOW"), + + /** + * SPU_NOT_ALLOW_EDIT + */ + CODE_9401023(9401023, "SPU_NOT_ALLOW_EDIT"), + + /** + * SKU NOT ALLOW + */ + CODE_9401024(9401024, "SKU NOT ALLOW"), + + /** + * SKU_NOT_ALLOW_EDIT + */ + CODE_9401025(9401025, "SKU_NOT_ALLOW_EDIT"), + + /** + * limit too large + */ + CODE_9402001(9402001, "limit too large"), + + /** + * single send been blocked + */ + CODE_9402002(9402002, "single send been blocked"), + + /** + * all send been blocked + */ + CODE_9402003(9402003, "all send been blocked"), + + /** + * invalid msg id + */ + CODE_9402004(9402004, "invalid msg id"), + + /** + * send msg too quick + */ + CODE_9402005(9402005, "send msg too quick"), + + /** + * send to single user too quick + */ + CODE_9402006(9402006, "send to single user too quick"), + + /** + * send to all user too quick + */ + CODE_9402007(9402007, "send to all user too quick"), + + /** + * send type error + */ + CODE_9402008(9402008, "send type error"), + + /** + * can not send this msg + */ + CODE_9402009(9402009, "can not send this msg"), + + /** + * content too long or no content + */ + CODE_9402010(9402010, "content too long or no content"), + + /** + * path not exist + */ + CODE_9402011(9402011, "path not exist"), + + /** + * contain evil word + */ + CODE_9402012(9402012, "contain evil word"), + + /** + * path need html suffix + */ + CODE_9402013(9402013, "path need html suffix"), + + /** + * not open to personal body type + */ + CODE_9402014(9402014, "not open to personal body type"), + + /** + * not open to violation body type + */ + CODE_9402015(9402015, "not open to violation body type"), + + /** + * not open to low quality provider + */ + CODE_9402016(9402016, "not open to low quality provider"), + + /** + * invalid product_id + */ + CODE_9402101(9402101, "invalid product_id"), + + /** + * device_id count more than limit + */ + CODE_9402102(9402102, "device_id count more than limit"), + + /** + * 请勿频繁提交,待上一次操作完成后再提交 concurrent limit + */ + CODE_9402202(9402202, "请勿频繁提交,待上一次操作完成后再提交"), + + /** + * user not book this ad id + */ + CODE_9402301(9402301, "user not book this ad id"), + + /** + * 消息类型错误! + */ + CODE_9403000(9403000, "消息类型错误!"), + + /** + * 消息字段的内容过长! + */ + CODE_9403001(9403001, "消息字段的内容过长!"), + + /** + * 消息字段的内容违规! + */ + CODE_9403002(9403002, "消息字段的内容违规!"), + + /** + * 发送的微信号太多! + */ + CODE_9403003(9403003, "发送的微信号太多!"), + + /** + * 存在错误的微信号! + */ + CODE_9403004(9403004, "存在错误的微信号!"), + + /** + * 直播间列表为空 live room not exsits + */ + CODE_9410000(9410000, "直播间列表为空"), + + /** + * 获取房间失败 inner error: get room fail + */ + CODE_9410001(9410001, "获取房间失败"), + + /** + * 获取商品失败 inner error: get goods fail + */ + CODE_9410002(9410002, "获取商品失败"), + + /** + * 获取回放失败 inner error: get replay url fail + */ + CODE_9410003(9410003, "获取回放失败"); + + + private final int code; + private final String msg; + + WxOpenErrorMsgEnum(int code, String msg) { + this.code = code; + this.msg = msg; + } + + static final Map valueMap = Maps.newHashMap(); + + static { + for (WxOpenErrorMsgEnum value : WxOpenErrorMsgEnum.values()) { + valueMap.put(value.code, value.msg); + } + } + + /** + * 通过错误代码查找其中文含义. + */ + public static String findMsgByCode(int code) { + return valueMap.getOrDefault(code, null); + } +} diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/error/WxRuntimeException.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/error/WxRuntimeException.java new file mode 100644 index 0000000000..e94e03db5d --- /dev/null +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/error/WxRuntimeException.java @@ -0,0 +1,23 @@ +package me.chanjar.weixin.common.error; + +/** + * WxJava专用的runtime exception. + * + * @author Binary Wang + * created on 2020-09-26 + */ +public class WxRuntimeException extends RuntimeException { + private static final long serialVersionUID = 4881698471192264412L; + + public WxRuntimeException(Throwable e) { + super(e); + } + + public WxRuntimeException(String msg) { + super(msg); + } + + public WxRuntimeException(String msg, Throwable e) { + super(msg, e); + } +} 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 new file mode 100644 index 0000000000..a93cbe1e99 --- /dev/null +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/executor/CommonUploadRequestExecutor.java @@ -0,0 +1,58 @@ +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; + +/** + * 通用文件上传执行器 + * + * @author 广州跨界 + * created on 2024/01/11 + */ +public abstract class CommonUploadRequestExecutor implements RequestExecutor { + + protected RequestHttp requestHttp; + + public CommonUploadRequestExecutor(RequestHttp requestHttp) { + this.requestHttp = requestHttp; + } + + @Override + public void execute(String uri, CommonUploadParam data, ResponseHandler handler, WxType wxType) throws WxErrorException, IOException { + handler.handle(this.execute(uri, data, wxType)); + } + + /** + * 构造通用文件上传执行器 + * + * @param requestHttp 请求信息 + * @return 执行器 + */ + @SuppressWarnings("unchecked") + public static RequestExecutor create(RequestHttp requestHttp) { + switch (requestHttp.getRequestType()) { + case APACHE_HTTP: + return new CommonUploadRequestExecutorApacheImpl( + (RequestHttp) requestHttp); + case JODD_HTTP: + return new CommonUploadRequestExecutorJoddHttpImpl((RequestHttp) requestHttp); + case OK_HTTP: + 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 new file mode 100644 index 0000000000..7f19241cdb --- /dev/null +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/executor/CommonUploadRequestExecutorApacheImpl.java @@ -0,0 +1,78 @@ +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.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.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.entity.mime.content.InputStreamBody; +import org.apache.http.impl.client.CloseableHttpClient; + +import java.io.IOException; +import java.io.InputStream; + +/** + * Apache HttpClient 通用文件上传器 + * + * @author 广州跨界 + * created on 2024/01/11 + */ +public class CommonUploadRequestExecutorApacheImpl extends CommonUploadRequestExecutor { + + public CommonUploadRequestExecutorApacheImpl(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.RFC6532) + .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/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/executor/CommonUploadRequestExecutorJoddHttpImpl.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/executor/CommonUploadRequestExecutorJoddHttpImpl.java new file mode 100644 index 0000000000..36e8660f77 --- /dev/null +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/executor/CommonUploadRequestExecutorJoddHttpImpl.java @@ -0,0 +1,91 @@ +package me.chanjar.weixin.common.executor; + +import jodd.http.HttpConnectionProvider; +import jodd.http.HttpRequest; +import jodd.http.HttpResponse; +import jodd.http.ProxyInfo; +import jodd.http.upload.Uploadable; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.SneakyThrows; +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 java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; + +/** + * JoddHttp 通用文件上传器 + * + * @author 广州跨界 + * created on 2024/01/11 + */ +public class CommonUploadRequestExecutorJoddHttpImpl extends CommonUploadRequestExecutor { + + public CommonUploadRequestExecutorJoddHttpImpl(RequestHttp requestHttp) { + super(requestHttp); + } + + @Override + public String execute(String uri, CommonUploadParam param, WxType wxType) throws WxErrorException, IOException { + HttpRequest request = HttpRequest.post(uri); + if (requestHttp.getRequestHttpProxy() != null) { + requestHttp.getRequestHttpClient().useProxy(requestHttp.getRequestHttpProxy()); + } + request.withConnectionProvider(requestHttp.getRequestHttpClient()); + request.form(param.getName(), new CommonUploadParamToUploadableAdapter(param.getData())); + HttpResponse response = request.send(); + response.charset(StandardCharsets.UTF_8.name()); + String responseContent = response.bodyText(); + if (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; + } + + /** + * 通用上传参数 到 Uploadable 的适配器 + */ + @Getter + @AllArgsConstructor + public static class CommonUploadParamToUploadableAdapter implements Uploadable { + + private CommonUploadData content; + + @SneakyThrows + @Override + public byte[] getBytes() { + return content.readAllBytes(); + } + + @Override + public String getFileName() { + return content.getFileName(); + } + + @Override + public String getMimeType() { + return null; + } + + @SneakyThrows + @Override + public int getSize() { + return (int) content.getLength(); + } + + @Override + public InputStream openInputStream() { + return content.getInputStream(); + } + } +} diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/executor/CommonUploadRequestExecutorOkHttpImpl.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/executor/CommonUploadRequestExecutorOkHttpImpl.java new file mode 100644 index 0000000000..40a4622b89 --- /dev/null +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/executor/CommonUploadRequestExecutorOkHttpImpl.java @@ -0,0 +1,91 @@ +package me.chanjar.weixin.common.executor; + +import lombok.AllArgsConstructor; +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.okhttp.OkHttpProxyInfo; +import okhttp3.*; +import okio.BufferedSink; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.io.IOException; +import java.io.InputStream; + +/** + * OkHttp 通用文件上传器 + * + * @author 广州跨界 + * created on 2024/01/11 + */ +public class CommonUploadRequestExecutorOkHttpImpl extends CommonUploadRequestExecutor { + + public CommonUploadRequestExecutorOkHttpImpl(RequestHttp requestHttp) { + super(requestHttp); + } + + @Override + public String execute(String uri, CommonUploadParam param, WxType wxType) throws WxErrorException, IOException { + RequestBody requestBody = new CommonUpdateDataToRequestBodyAdapter(param.getData()); + RequestBody body = new MultipartBody.Builder() + .setType(MediaType.get("multipart/form-data")) + .addFormDataPart(param.getName(), param.getData().getFileName(), requestBody) + .build(); + Request request = new Request.Builder().url(uri).post(body).build(); + + try (Response response = requestHttp.getRequestHttpClient().newCall(request).execute()) { + ResponseBody responseBody = response.body(); + String responseContent = responseBody == null ? "" : responseBody.string(); + if (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; + } + } + + /** + * 通用上传输入 到 OkHttp 请求提 适配器 + */ + @AllArgsConstructor + public static class CommonUpdateDataToRequestBodyAdapter extends RequestBody { + + private static final MediaType CONTENT_TYPE = MediaType.get("application/octet-stream"); + + private CommonUploadData data; + + @Override + public long contentLength() { + return data.getLength(); + } + + @Nullable + @Override + public MediaType contentType() { + return CONTENT_TYPE; + } + + @Override + public void writeTo(@NotNull BufferedSink bufferedSink) throws IOException { + InputStream inputStream = data.getInputStream(); + int count; + byte[] buffer = new byte[4096]; + while ((count = inputStream.read(buffer)) != -1) { + bufferedSink.write(buffer, 0, count); + } + inputStream.close(); + } + + @Override + public boolean isOneShot() { + return true; + } + } +} diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/redis/BaseWxRedisOps.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/redis/BaseWxRedisOps.java new file mode 100644 index 0000000000..17e992ab25 --- /dev/null +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/redis/BaseWxRedisOps.java @@ -0,0 +1,37 @@ +package me.chanjar.weixin.common.redis; + +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.Lock; + +/** + * 微信redis操作基本类 + *

+ * 非内置实现redis相关操作, 请实现该类 + */ +public abstract class BaseWxRedisOps implements WxRedisOps { + + @Override + public String getValue(String key) { + throw new UnsupportedOperationException(); + } + + @Override + public void setValue(String key, String value, int expire, TimeUnit timeUnit) { + throw new UnsupportedOperationException(); + } + + @Override + public Long getExpire(String key) { + throw new UnsupportedOperationException(); + } + + @Override + public void expire(String key, int expire, TimeUnit timeUnit) { + throw new UnsupportedOperationException(); + } + + @Override + public Lock getLock(String key) { + throw new UnsupportedOperationException(); + } +} diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/redis/JedisWxRedisOps.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/redis/JedisWxRedisOps.java new file mode 100644 index 0000000000..e6e3f28d7e --- /dev/null +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/redis/JedisWxRedisOps.java @@ -0,0 +1,58 @@ +package me.chanjar.weixin.common.redis; + +import com.github.jedis.lock.JedisLock; +import lombok.RequiredArgsConstructor; +import me.chanjar.weixin.common.util.locks.JedisDistributedLock; +import redis.clients.jedis.Jedis; +import redis.clients.jedis.util.Pool; + +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.Lock; + +/** + * @author Mario Luo + * + * @deprecated 该类底层使用过时的8年不维护的 {@link JedisLock}组件,不可靠,不建议继续使用。请 + * 使用:{@link RedisTemplateWxRedisOps}、{@link RedissonWxRedisOps} 替换 + */ +@RequiredArgsConstructor +public class JedisWxRedisOps implements WxRedisOps { + private final Pool jedisPool; + + @Override + public String getValue(String key) { + try (Jedis jedis = this.jedisPool.getResource()) { + return jedis.get(key); + } + } + + @Override + public void setValue(String key, String value, int expire, TimeUnit timeUnit) { + try (Jedis jedis = this.jedisPool.getResource()) { + if (expire <= 0) { + jedis.set(key, value); + } else { + jedis.psetex(key, timeUnit.toMillis(expire), value); + } + } + } + + @Override + public Long getExpire(String key) { + try (Jedis jedis = this.jedisPool.getResource()) { + return jedis.ttl(key); + } + } + + @Override + public void expire(String key, int expire, TimeUnit timeUnit) { + try (Jedis jedis = this.jedisPool.getResource()) { + jedis.pexpire(key, timeUnit.toMillis(expire)); + } + } + + @Override + public Lock getLock(String key) { + return new JedisDistributedLock(jedisPool, key); + } +} diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/redis/RedisTemplateWxRedisOps.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/redis/RedisTemplateWxRedisOps.java new file mode 100644 index 0000000000..d531a2a307 --- /dev/null +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/redis/RedisTemplateWxRedisOps.java @@ -0,0 +1,44 @@ +package me.chanjar.weixin.common.redis; + +import lombok.NonNull; +import lombok.RequiredArgsConstructor; +import me.chanjar.weixin.common.util.locks.RedisTemplateSimpleDistributedLock; +import org.springframework.data.redis.core.StringRedisTemplate; + +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.Lock; + +@RequiredArgsConstructor +public class RedisTemplateWxRedisOps implements WxRedisOps { + + private final StringRedisTemplate redisTemplate; + + @Override + public String getValue(String key) { + return redisTemplate.opsForValue().get(key); + } + + @Override + public void setValue(String key, String value, int expire, TimeUnit timeUnit) { + if (expire <= 0) { + redisTemplate.opsForValue().set(key, value); + } else { + redisTemplate.opsForValue().set(key, value, expire, timeUnit); + } + } + + @Override + public Long getExpire(String key) { + return redisTemplate.getExpire(key, TimeUnit.SECONDS); + } + + @Override + public void expire(String key, int expire, TimeUnit timeUnit) { + redisTemplate.expire(key, expire, timeUnit); + } + + @Override + public Lock getLock(@NonNull String key) { + return new RedisTemplateSimpleDistributedLock(redisTemplate, key, 60 * 1000); + } +} diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/redis/RedissonWxRedisOps.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/redis/RedissonWxRedisOps.java new file mode 100644 index 0000000000..d51cd3e1ad --- /dev/null +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/redis/RedissonWxRedisOps.java @@ -0,0 +1,47 @@ +package me.chanjar.weixin.common.redis; + +import lombok.RequiredArgsConstructor; +import org.redisson.api.RedissonClient; + +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.Lock; + +@RequiredArgsConstructor +public class RedissonWxRedisOps implements WxRedisOps { + + private final RedissonClient redissonClient; + + @Override + public String getValue(String key) { + Object value = redissonClient.getBucket(key).get(); + return value == null ? null : value.toString(); + } + + @Override + public void setValue(String key, String value, int expire, TimeUnit timeUnit) { + if (expire <= 0) { + redissonClient.getBucket(key).set(value); + } else { + redissonClient.getBucket(key).set(value, expire, timeUnit); + } + } + + @Override + public Long getExpire(String key) { + long expire = redissonClient.getBucket(key).remainTimeToLive(); + if (expire > 0) { + expire = expire / 1000; + } + return expire; + } + + @Override + public void expire(String key, int expire, TimeUnit timeUnit) { + redissonClient.getBucket(key).expire(expire, timeUnit); + } + + @Override + public Lock getLock(String key) { + return redissonClient.getLock(key); + } +} diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/redis/WxRedisOps.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/redis/WxRedisOps.java new file mode 100644 index 0000000000..4912cbc5d8 --- /dev/null +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/redis/WxRedisOps.java @@ -0,0 +1,28 @@ +package me.chanjar.weixin.common.redis; + +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.Lock; + +/** + * 微信Redis相关操作 + *

+ * 该接口不承诺稳定, 外部实现请继承{@link BaseWxRedisOps} + * + * @author Mario Luo + * @see BaseWxRedisOps 实现需要继承该类 + * @see JedisWxRedisOps jedis实现 + * @see RedissonWxRedisOps redisson实现 + * @see RedisTemplateWxRedisOps redisTemplate实现 + */ +public interface WxRedisOps { + + String getValue(String key); + + void setValue(String key, String value, int expire, TimeUnit timeUnit); + + Long getExpire(String key); + + void expire(String key, int expire, TimeUnit timeUnit); + + Lock getLock(String key); +} 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 new file mode 100644 index 0000000000..03bec013dd --- /dev/null +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/requestexecuter/ocr/OcrDiscernApacheHttpRequestExecutor.java @@ -0,0 +1,52 @@ +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.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; + +/** + * . + * + * @author : zhayueran + * created on 2019/6/27 14:06 + */ +public class OcrDiscernApacheHttpRequestExecutor extends OcrDiscernRequestExecutor { + public OcrDiscernApacheHttpRequestExecutor(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.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 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 new file mode 100644 index 0000000000..542ab4a378 --- /dev/null +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/requestexecuter/ocr/OcrDiscernRequestExecutor.java @@ -0,0 +1,45 @@ +package me.chanjar.weixin.common.requestexecuter.ocr; + +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 org.apache.http.HttpHost; +import org.apache.http.impl.client.CloseableHttpClient; + +import java.io.File; +import java.io.IOException; + +/** + * . + * + * @author zhayueran + * created on 2019/6/27 15:06 + */ +public abstract class OcrDiscernRequestExecutor implements RequestExecutor { + protected RequestHttp requestHttp; + + public OcrDiscernRequestExecutor(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)); + } + + @SuppressWarnings("unchecked") + public static RequestExecutor create(RequestHttp requestHttp) { + switch (requestHttp.getRequestType()) { + case APACHE_HTTP: + return new OcrDiscernApacheHttpRequestExecutor( + (RequestHttp) requestHttp); + case HTTP_COMPONENTS: + return new OcrDiscernHttpComponentsRequestExecutor( + (RequestHttp) requestHttp); + default: + throw new IllegalArgumentException("不支持的http执行器类型:" + requestHttp.getRequestType()); + } + } +} diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/service/WxImgProcService.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/service/WxImgProcService.java new file mode 100644 index 0000000000..a9ef694ad5 --- /dev/null +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/service/WxImgProcService.java @@ -0,0 +1,121 @@ +package me.chanjar.weixin.common.service; + +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.common.bean.imgproc.WxImgProcAiCropResult; +import me.chanjar.weixin.common.bean.imgproc.WxImgProcQrCodeResult; +import me.chanjar.weixin.common.bean.imgproc.WxImgProcSuperResolutionResult; + +import java.io.File; + +/** + * 多项图像处理能力相关的API. + * https://developers.weixin.qq.com/doc/offiaccount/Intelligent_Interface/Img_Proc.html + * + * @author Theo Nie + */ +public interface WxImgProcService { + + /** + * 二维码/条码识别接口 + * 说明: + * 1.图片支持使用img参数实时上传,也支持使用img_url参数传送图片地址,由微信后台下载图片进行识别 + * 2.文件大小限制:小于2M + * 3.支持条码、二维码、DataMatrix和PDF417的识别。 + * 4.二维码、DataMatrix会返回位置坐标,条码和PDF417暂不返回位置坐标。 + * + * @param imgUrl 图片url地址 + * @return WxMpImgProcQrCodeResult + * @throws WxErrorException . + */ + WxImgProcQrCodeResult qrCode(String imgUrl) throws WxErrorException; + + /** + * 二维码/条码识别接口 + * 说明: + * 1.图片支持使用img参数实时上传,也支持使用img_url参数传送图片地址,由微信后台下载图片进行识别 + * 2.文件大小限制:小于2M + * 3.支持条码、二维码、DataMatrix和PDF417的识别。 + * 4.二维码、DataMatrix会返回位置坐标,条码和PDF417暂不返回位置坐标。 + * + * @param imgFile 图片文件对象 + * @return WxMpImgProcQrCodeResult + * @throws WxErrorException . + */ + WxImgProcQrCodeResult qrCode(File imgFile) throws WxErrorException; + + /** + * 图片高清化接口 + * 说明: + * 1.图片支持使用img参数实时上传,也支持使用img_url参数传送图片地址,由微信后台下载图片进行识别 + * 2.文件大小限制:小于2M + * 3.目前支持将图片超分辨率高清化2倍,即生成图片分辨率为原图2倍大小 + * 返回的media_id有效期为3天,期间可以通过“获取临时素材”接口获取图片二进制 + * + * @param imgUrl 图片url地址 + * @return WxMpImgProcSuperResolutionResult + * @throws WxErrorException . + */ + WxImgProcSuperResolutionResult superResolution(String imgUrl) throws WxErrorException; + + /** + * 图片高清化接口 + * 说明: + * 1.图片支持使用img参数实时上传,也支持使用img_url参数传送图片地址,由微信后台下载图片进行识别 + * 2.文件大小限制:小于2M + * 3.目前支持将图片超分辨率高清化2倍,即生成图片分辨率为原图2倍大小 + * 返回的media_id有效期为3天,期间可以通过“获取临时素材”接口获取图片二进制 + * + * @param imgFile 图片文件对象 + * @return WxMpImgProcSuperResolutionResult + * @throws WxErrorException . + */ + WxImgProcSuperResolutionResult superResolution(File imgFile) throws WxErrorException; + + /** + * 图片智能裁剪接口 + * 说明: + * 1.图片支持使用img参数实时上传,也支持使用img_url参数传送图片地址,由微信后台下载图片进行识别 + * 2.文件大小限制:小于2M + * 3.该接口默认使用最佳宽高比 + * @param imgUrl 图片url地址 + * @return WxMpImgProcAiCropResult + * @throws WxErrorException . + */ + WxImgProcAiCropResult aiCrop(String imgUrl) throws WxErrorException; + + /** + * 图片智能裁剪接口 + * 说明: + * 1.图片支持使用img参数实时上传,也支持使用img_url参数传送图片地址,由微信后台下载图片进行识别 + * 2.文件大小限制:小于2M + * @param imgUrl 图片url地址 + * @param ratios 宽高比,最多支持5个,请以英文逗号分隔 + * @return WxMpImgProcAiCropResult + * @throws WxErrorException . + */ + WxImgProcAiCropResult aiCrop(String imgUrl, String ratios) throws WxErrorException; + + /** + * 图片智能裁剪接口 + * 说明: + * 1.图片支持使用img参数实时上传,也支持使用img_url参数传送图片地址,由微信后台下载图片进行识别 + * 2.文件大小限制:小于2M + * 3.该接口默认使用最佳宽高比 + * @param imgFile 图片文件对象 + * @return WxMpImgProcAiCropResult + * @throws WxErrorException . + */ + WxImgProcAiCropResult aiCrop(File imgFile) throws WxErrorException; + + /** + * 图片智能裁剪接口 + * 说明: + * 1.图片支持使用img参数实时上传,也支持使用img_url参数传送图片地址,由微信后台下载图片进行识别 + * 2.文件大小限制:小于2M + * @param imgFile 图片文件对象 + * @param ratios 宽高比,最多支持5个,请以英文逗号分隔 + * @return WxMpImgProcAiCropResult + * @throws WxErrorException . + */ + WxImgProcAiCropResult aiCrop(File imgFile, String ratios) throws WxErrorException; +} diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/service/WxOAuth2Service.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/service/WxOAuth2Service.java new file mode 100644 index 0000000000..5dea04928e --- /dev/null +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/service/WxOAuth2Service.java @@ -0,0 +1,83 @@ +package me.chanjar.weixin.common.service; + +import me.chanjar.weixin.common.bean.WxOAuth2UserInfo; +import me.chanjar.weixin.common.bean.oauth2.WxOAuth2AccessToken; +import me.chanjar.weixin.common.error.WxErrorException; + +/** + * oauth2 相关接口. + * + * @author Binary Wang + * created on 2020-08-08 + */ +public interface WxOAuth2Service { + /** + *

+   * 构造oauth2授权的url连接.
+   * 详情请见: 网页授权
+   * 
+ * + * @param redirectUri 用户授权完成后的重定向链接,无需urlencode, 方法内会进行encode + * @param scope scope,静默:snsapi_base, 带信息授权:snsapi_userinfo + * @param state state + * @return url + */ + String buildAuthorizationUrl(String redirectUri, String scope, String state); + + /** + *
+   * 用code换取oauth2的access token.
+   * 详情请见: 网页授权获取用户基本信息
+   * 
+ * + * @param code code + * @return token对象 + * @throws WxErrorException . + */ + WxOAuth2AccessToken getAccessToken(String code) throws WxErrorException; + + /** + * 用code换取oauth2的access token. + * + * @param appId the appid + * @param appSecret the secret + * @param code code + * @return token对象 + * @throws WxErrorException . + */ + WxOAuth2AccessToken getAccessToken(String appId, String appSecret, String code) throws WxErrorException; + + /** + *
+   * 刷新oauth2的access token.
+   * 
+ * + * @param refreshToken 刷新token + * @return 新的token对象 + * @throws WxErrorException . + */ + WxOAuth2AccessToken refreshAccessToken(String refreshToken) throws WxErrorException; + + /** + *
+   * 用oauth2获取用户信息, 当前面引导授权时的scope是snsapi_userinfo的时候才可以.
+   * 
+ * + * @param oAuth2AccessToken token对象 + * @param lang zh_CN, zh_TW, en + * @return 用户对象 + * @throws WxErrorException . + */ + WxOAuth2UserInfo getUserInfo(WxOAuth2AccessToken oAuth2AccessToken, String lang) throws WxErrorException; + + /** + *
+   * 验证oauth2的access token是否有效.
+   * 
+ * + * @param oAuth2AccessToken token对象 + * @return 是否有效 + */ + boolean validateAccessToken(WxOAuth2AccessToken oAuth2AccessToken); + +} diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/service/WxOAuth2ServiceDecorator.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/service/WxOAuth2ServiceDecorator.java new file mode 100644 index 0000000000..a495dbf828 --- /dev/null +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/service/WxOAuth2ServiceDecorator.java @@ -0,0 +1,16 @@ +package me.chanjar.weixin.common.service; + +import lombok.AllArgsConstructor; +import lombok.experimental.Delegate; + +/** + * 微信 oauth2服务 装饰器 + * + * @author 广州跨界 + */ +@AllArgsConstructor +public class WxOAuth2ServiceDecorator implements WxOAuth2Service { + + @Delegate + private final WxOAuth2Service wxOAuth2Service; +} diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/service/WxOcrService.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/service/WxOcrService.java new file mode 100644 index 0000000000..d0aeef8491 --- /dev/null +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/service/WxOcrService.java @@ -0,0 +1,133 @@ +package me.chanjar.weixin.common.service; + +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.common.bean.ocr.WxOcrBankCardResult; +import me.chanjar.weixin.common.bean.ocr.WxOcrBizLicenseResult; +import me.chanjar.weixin.common.bean.ocr.WxOcrCommResult; +import me.chanjar.weixin.common.bean.ocr.WxOcrDrivingLicenseResult; +import me.chanjar.weixin.common.bean.ocr.WxOcrDrivingResult; +import me.chanjar.weixin.common.bean.ocr.WxOcrIdCardResult; + +import java.io.File; + +/** + * 基于小程序或 H5 的身份证、银行卡、行驶证 OCR 识别. + *

+ * 参考:{@code https://mp.weixin.qq.com/wiki?t=resource/res_main&id=21516712284rHWMX} + *

+ * + * @author Binary Wang + * created on 2019-06-22 + */ +public interface WxOcrService { + + /** + * 身份证OCR识别接口. + * + * @param imgUrl 图片url地址 + * @return WxMpOcrIdCardResult + * @throws WxErrorException . + */ + WxOcrIdCardResult idCard(String imgUrl) throws WxErrorException; + + /** + * 身份证OCR识别接口. + * + * @param imgFile 图片文件对象 + * @return WxMpOcrIdCardResult + * @throws WxErrorException . + */ + WxOcrIdCardResult idCard(File imgFile) throws WxErrorException; + + /** + * 银行卡OCR识别接口 + * 文件大小限制:小于2M + * @param imgUrl 图片url地址 + * @return WxMpOcrBankCardResult + * @throws WxErrorException . + */ + WxOcrBankCardResult bankCard(String imgUrl) throws WxErrorException; + + /** + * 银行卡OCR识别接口 + * 文件大小限制:小于2M + * @param imgFile 图片文件对象 + * @return WxMpOcrBankCardResult + * @throws WxErrorException . + */ + WxOcrBankCardResult bankCard(File imgFile) throws WxErrorException; + + /** + * 行驶证OCR识别接口 + * 文件大小限制:小于2M + * @param imgUrl 图片url地址 + * @return WxMpOcrDrivingResult + * @throws WxErrorException . + */ + WxOcrDrivingResult driving(String imgUrl) throws WxErrorException; + + /** + * 行驶证OCR识别接口 + * 文件大小限制:小于2M + * @param imgFile 图片文件对象 + * @return WxMpOcrDrivingResult + * @throws WxErrorException . + */ + WxOcrDrivingResult driving(File imgFile) throws WxErrorException; + + /** + * 驾驶证OCR识别接口 + * 文件大小限制:小于2M + * @param imgUrl 图片url地址 + * @return WxMpOcrDrivingLicenseResult + * @throws WxErrorException . + */ + WxOcrDrivingLicenseResult drivingLicense(String imgUrl) throws WxErrorException; + + /** + * 驾驶证OCR识别接口 + * 文件大小限制:小于2M + * @param imgFile 图片文件对象 + * @return WxMpOcrDrivingLicenseResult + * @throws WxErrorException . + */ + WxOcrDrivingLicenseResult drivingLicense(File imgFile) throws WxErrorException; + + /** + * 营业执照OCR识别接口 + * 文件大小限制:小于2M + * @param imgUrl 图片url地址 + * @return WxMpOcrBizLicenseResult + * @throws WxErrorException . + */ + WxOcrBizLicenseResult bizLicense(String imgUrl) throws WxErrorException; + + /** + * 营业执照OCR识别接口 + * 文件大小限制:小于2M + * @param imgFile 图片文件对象 + * @return WxMpOcrBizLicenseResult + * @throws WxErrorException . + */ + WxOcrBizLicenseResult bizLicense(File imgFile) throws WxErrorException; + + /** + * 通用印刷体OCR识别接口 + * 文件大小限制:小于2M + * 适用于屏幕截图、印刷体照片等场景 + * @param imgUrl 图片url地址 + * @return WxMpOcrCommResult + * @throws WxErrorException . + */ + WxOcrCommResult comm(String imgUrl) throws WxErrorException; + + /** + * 通用印刷体OCR识别接口 + * 文件大小限制:小于2M + * 适用于屏幕截图、印刷体照片等场景 + * @param imgFile 图片文件对象 + * @return WxMpOcrCommResult + * @throws WxErrorException . + */ + WxOcrCommResult comm(File imgFile) throws WxErrorException; +} diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/service/WxService.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/service/WxService.java new file mode 100644 index 0000000000..f894cba44f --- /dev/null +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/service/WxService.java @@ -0,0 +1,74 @@ +package me.chanjar.weixin.common.service; + +import com.google.gson.JsonObject; +import me.chanjar.weixin.common.bean.CommonUploadParam; +import me.chanjar.weixin.common.bean.ToJson; +import me.chanjar.weixin.common.error.WxErrorException; + +/** + * 微信服务接口. + * + * @author Binary Wang + * created on 2020-04-25 + */ +public interface WxService { + /** + * 当本Service没有实现某个API的时候,可以用这个,针对所有微信API中的GET请求. + * + * @param queryParam 参数 + * @param url 请求接口地址 + * @return 接口响应字符串 + * @throws WxErrorException 异常 + */ + String get(String url, String queryParam) throws WxErrorException; + + /** + * 当本Service没有实现某个API的时候,可以用这个,针对所有微信API中的POST请求. + * + * @param postData 请求参数json值 + * @param url 请求接口地址 + * @return 接口响应字符串 + * @throws WxErrorException 异常 + */ + String post(String url, String postData) throws WxErrorException; + + /** + * 当本Service没有实现某个API的时候,可以用这个,针对所有微信API中的POST请求. + * + * @param url 请求接口地址 + * @param obj 请求对象 + * @return 接口响应字符串 + * @throws WxErrorException 异常 + */ + String post(String url, Object obj) throws WxErrorException; + + /** + * 当本Service没有实现某个API的时候,可以用这个,针对所有微信API中的POST请求. + * + * @param url 请求接口地址 + * @param jsonObject 请求对象 + * @return 接口响应字符串 + * @throws WxErrorException 异常 + */ + String post(String url, JsonObject jsonObject) throws WxErrorException; + + /** + * 当本Service没有实现某个API的时候,可以用这个,针对所有微信API中的POST请求. + * + * @param url 请求接口地址 + * @param obj 请求对象,实现了ToJson接口 + * @return 接口响应字符串 + * @throws WxErrorException 异常 + */ + String post(String url, ToJson obj) throws WxErrorException; + + /** + * 当本Service没有实现某个上传API的时候,可以用这个,针对所有微信API中的POST文件上传请求 + * + * @param url 请求接口地址 + * @param param 文件上传对象 + * @return 接口响应字符串 + * @throws WxErrorException 异常 + */ + String upload(String url, CommonUploadParam param) throws WxErrorException; +} diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/session/InternalSessionManager.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/session/InternalSessionManager.java index e3d9ab8351..24ea58ef38 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/session/InternalSessionManager.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/session/InternalSessionManager.java @@ -7,13 +7,12 @@ public interface InternalSessionManager { /** * Return the active Session, associated with this Manager, with the - * specified session id (if any); otherwise return null. + * specified session id (if any); otherwise return {@code null}. * * @param id The session id for the session to be returned + * @return the session or null * @throws IllegalStateException if a new session cannot be * instantiated for any reason - * @throws java.io.IOException if an input/output error occurs while - * processing this request */ InternalSession findSession(String id); diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/session/StandardSessionManager.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/session/StandardSessionManager.java index 2472cb44b8..8d994b9c36 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/session/StandardSessionManager.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/session/StandardSessionManager.java @@ -1,13 +1,12 @@ package me.chanjar.weixin.common.session; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.atomic.AtomicBoolean; - +import me.chanjar.weixin.common.util.res.StringManager; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import me.chanjar.weixin.common.util.res.StringManager; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicBoolean; /** * 基于内存的session manager. @@ -15,7 +14,6 @@ * @author Daniel Qian */ public class StandardSessionManager implements WxSessionManager, InternalSessionManager { - protected static final StringManager SM = StringManager.getManager(Constants.PACKAGE); /** * The descriptive name of this Manager implementation (for logging). @@ -51,7 +49,9 @@ public class StandardSessionManager implements WxSessionManager, InternalSession */ protected int maxInactiveInterval = 30 * 60; - // Number of sessions created by this manager + /** + * Number of sessions created by this manager + */ protected long sessionCounter = 0; protected volatile int maxActive = 0; @@ -98,7 +98,7 @@ public WxSession getSession(String sessionId, boolean create) { // Create a new session if requested and the response is not committed if (!create) { - return (null); + return null; } session = createSession(sessionId); @@ -127,7 +127,7 @@ public void remove(InternalSession session, boolean update) { @Override public InternalSession findSession(String id) { if (id == null) { - return (null); + return null; } return this.sessions.get(id); } @@ -154,12 +154,10 @@ public InternalSession createSession(String sessionId) { session.setValid(true); session.setCreationTime(System.currentTimeMillis()); session.setMaxInactiveInterval(this.maxInactiveInterval); - String id = sessionId; - session.setId(id); + session.setId(sessionId); this.sessionCounter++; - return (session); - + return session; } @@ -181,24 +179,19 @@ protected InternalSession getNewSession() { return new StandardSession(this); } - @Override public void add(InternalSession session) { - // 当第一次有session创建的时候,开启session清理线程 if (!this.backgroundProcessStarted.getAndSet(true)) { - Thread t = new Thread(new Runnable() { - @Override - public void run() { - while (true) { - try { - // 每秒清理一次 - Thread.sleep(StandardSessionManager.this.backgroundProcessorDelay * 1000L); - backgroundProcess(); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - StandardSessionManager.this.log.error("SessionManagerImpl.backgroundProcess error", e); - } + Thread t = new Thread(() -> { + while (true) { + try { + // 每秒清理一次 + Thread.sleep(StandardSessionManager.this.backgroundProcessorDelay * 1000L); + backgroundProcess(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + StandardSessionManager.this.log.error("SessionManagerImpl.backgroundProcess error", e); } } }); @@ -258,7 +251,7 @@ public void processExpires() { if (this.log.isDebugEnabled()) { this.log.debug("End expire sessions {} processingTime {} expired sessions: {}", getName(), timeEnd - timeNow, expireHere); } - this.processingTime += (timeEnd - timeNow); + this.processingTime += timeEnd - timeNow; } @@ -296,7 +289,7 @@ public void setBackgroundProcessorDelay(int backgroundProcessorDelay) { */ public String getName() { - return (name); + return name; } 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 4b7f9be6a7..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; @@ -50,16 +49,15 @@ public static void checkRequiredFields(Object bean) throws WxErrorException { } } field.setAccessible(isAccessible); - } catch (SecurityException | IllegalArgumentException - | IllegalAccessException e) { + } catch (SecurityException | IllegalArgumentException | IllegalAccessException e) { log.error(e.getMessage(), e); } } if (!requiredFields.isEmpty()) { - String msg = "必填字段 " + requiredFields + " 必须提供值"; + String msg = String.format("必填字段【%s】必须提供值!", requiredFields); log.debug(msg); - throw new WxErrorException(WxError.builder().errorMsg(msg).build()); + throw new WxErrorException(msg); } } 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/LogExceptionHandler.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/LogExceptionHandler.java index 7487a0fe29..35b0eea822 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/LogExceptionHandler.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/LogExceptionHandler.java @@ -1,20 +1,17 @@ package me.chanjar.weixin.common.util; +import lombok.extern.slf4j.Slf4j; import me.chanjar.weixin.common.api.WxErrorExceptionHandler; import me.chanjar.weixin.common.error.WxErrorException; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - +/** + * @author Daniel Qian + */ +@Slf4j public class LogExceptionHandler implements WxErrorExceptionHandler { - - private Logger log = LoggerFactory.getLogger(WxErrorExceptionHandler.class); - @Override public void handle(WxErrorException e) { - - this.log.error("Error happens", e); - + log.error("Error happens", e); } } diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/RandomUtils.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/RandomUtils.java index bbb11992bc..a9017c0d16 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/RandomUtils.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/RandomUtils.java @@ -4,12 +4,24 @@ public class RandomUtils { private static final String RANDOM_STR = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; - private static final java.util.Random RANDOM = new java.util.Random(); + private static volatile java.util.Random random; + + private static java.util.Random getRandom() { + if (random == null) { + synchronized (RandomUtils.class) { + if (random == null) { + random = new java.util.Random(); + } + } + } + return random; + } public static String getRandomStr() { StringBuilder sb = new StringBuilder(); + java.util.Random r = getRandom(); for (int i = 0; i < 16; i++) { - sb.append(RANDOM_STR.charAt(RANDOM.nextInt(RANDOM_STR.length()))); + sb.append(RANDOM_STR.charAt(r.nextInt(RANDOM_STR.length()))); } return sb.toString(); } diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/SignUtils.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/SignUtils.java index fc3579d45c..1886209f98 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/SignUtils.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/SignUtils.java @@ -25,6 +25,7 @@ public class SignUtils { * * @param message 签名数据 * @param key 签名密钥 + * @return 签名结果 */ public static String createHmacSha256Sign(String message, String key) { try { diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/XmlUtils.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/XmlUtils.java index cd3a7a984c..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 @@ -1,21 +1,20 @@ package me.chanjar.weixin.common.util; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import com.google.common.collect.Sets; +import me.chanjar.weixin.common.error.WxRuntimeException; +import org.dom4j.*; +import org.dom4j.io.SAXReader; +import org.dom4j.tree.DefaultText; +import org.xml.sax.SAXException; + import java.io.StringReader; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; - -import org.dom4j.Document; -import org.dom4j.DocumentException; -import org.dom4j.Element; -import org.dom4j.Node; -import org.dom4j.io.SAXReader; -import org.dom4j.tree.DefaultText; - -import com.google.common.collect.Lists; -import com.google.common.collect.Maps; -import com.google.common.collect.Sets; +import java.util.stream.Collectors; /** *
@@ -31,32 +30,51 @@ public static Map xml2Map(String xmlString) {
     Map map = new HashMap<>(16);
     try {
       SAXReader saxReader = new SAXReader();
+      saxReader.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
+      saxReader.setFeature("http://javax.xml.XMLConstants/feature/secure-processing", true);
+      saxReader.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
+      saxReader.setFeature("http://xml.org/sax/features/external-general-entities", false);
+      saxReader.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
       Document doc = saxReader.read(new StringReader(xmlString));
       Element root = doc.getRootElement();
       List elements = root.elements();
       for (Element element : elements) {
-          map.put(element.getName(), element2MapOrString(element));
+        String elementName = element.getName();
+        if (map.containsKey(elementName)) {
+          if (map.get(elementName) instanceof List) {
+            ((List) map.get(elementName)).add(element2MapOrString(element));
+          } else {
+            List value = Lists.newArrayList(map.get(elementName));
+            value.add(element2MapOrString(element));
+            map.put(elementName, value);
+          }
+        } else {
+          map.put(elementName, element2MapOrString(element));
+        }
       }
-    } catch (DocumentException e) {
-      throw new RuntimeException(e);
+    } catch (DocumentException | SAXException e) {
+      throw new WxRuntimeException(e);
     }
 
     return map;
   }
 
   private static Object element2MapOrString(Element element) {
-    Map result = Maps.newHashMap();
 
-    final List content = element.content();
-    if (content.size() <= 1) {
+    final List nodes = element.content();
+    final List names = names(nodes);
+
+    // 判断节点下有无非文本节点(非Text和CDATA),如无,直接取Text文本内容
+    if (names.isEmpty()) {
       return element.getText();
     }
 
-    final Set names = names(content);
-    if (names.size() == 1) {
+    Map result = Maps.newHashMap();
+    Set distinctNames = Sets.newHashSet(names);
+    if (distinctNames.size() == 1) {
       // 说明是个列表,各个子对象是相同的name
       List list = Lists.newArrayList();
-      for (Node node : content) {
+      for (Node node : nodes) {
         if (node instanceof DefaultText) {
           continue;
         }
@@ -67,8 +85,8 @@ private static Object element2MapOrString(Element element) {
       }
 
       result.put(names.iterator().next(), list);
-    } else {
-      for (Node node : content) {
+    } else if (distinctNames.size() == names.size()) {
+      for (Node node : nodes) {
         if (node instanceof DefaultText) {
           continue;
         }
@@ -77,15 +95,41 @@ private static Object element2MapOrString(Element element) {
           result.put(node.getName(), element2MapOrString((Element) node));
         }
       }
+    } else {
+      // 说明有重复name,但不是全部都相同
+      Map namesCountMap = names.stream().collect(Collectors.groupingBy(a -> a, Collectors.counting()));
+      for (Node node : nodes) {
+        if (node instanceof DefaultText) {
+          continue;
+        }
+
+        if (node instanceof Element) {
+          String nodeName = node.getName();
+          if (namesCountMap.get(nodeName) == 1) {
+            result.put(nodeName, element2MapOrString((Element) node));
+          } else {
+            List values;
+            if (result.containsKey(nodeName)) {
+              values = (List) result.get(nodeName);
+            } else {
+              values = Lists.newArrayList();
+              result.put(nodeName, values);
+            }
+
+            values.add(element2MapOrString((Element) node));
+          }
+        }
+      }
     }
 
     return result;
   }
 
-  private static Set names(List nodes) {
-    Set names = Sets.newHashSet();
+  private static List names(List nodes) {
+    List names = Lists.newArrayList();
     for (Node node : nodes) {
-      if (node instanceof DefaultText) {
+      // 如果节点类型是Text或CDATA跳过
+      if (node instanceof DefaultText || node instanceof CDATA) {
         continue;
       }
       names.add(node.getName());
diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/crypto/SHA1.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/crypto/SHA1.java
index c82f94d871..43cc54b43d 100644
--- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/crypto/SHA1.java
+++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/crypto/SHA1.java
@@ -8,7 +8,7 @@
 /**
  *
  * @author Daniel Qian
- * @date 14/10/19
+ * created on  14/10/19
  */
 public class SHA1 {
 
@@ -29,7 +29,10 @@ public static String gen(String... arr) {
   }
 
   /**
-   * 用&串接arr参数,生成sha1 digest.
+   * {@code 用&串接arr参数,生成sha1 digest.}
+   *
+   * @param arr 参数数组
+   * @return sha1摘要
    */
   public static String genWithAmple(String... arr) {
     if (StringUtils.isAnyEmpty(arr)) {
diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/crypto/WxCryptUtil.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/crypto/WxCryptUtil.java
index 5c16e71cec..50362636fc 100755
--- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/crypto/WxCryptUtil.java
+++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/crypto/WxCryptUtil.java
@@ -1,21 +1,25 @@
 package me.chanjar.weixin.common.util.crypto;
 
-import java.io.StringReader;
-import java.nio.charset.Charset;
-import java.nio.charset.StandardCharsets;
-import java.util.Arrays;
-import java.util.Random;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import me.chanjar.weixin.common.error.WxRuntimeException;
+import org.apache.commons.codec.binary.Base64;
+import org.apache.commons.lang3.StringUtils;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.xml.sax.InputSource;
+
 import javax.crypto.Cipher;
 import javax.crypto.spec.IvParameterSpec;
 import javax.crypto.spec.SecretKeySpec;
 import javax.xml.parsers.DocumentBuilder;
 import javax.xml.parsers.DocumentBuilderFactory;
 import javax.xml.parsers.ParserConfigurationException;
-
-import org.apache.commons.codec.binary.Base64;
-import org.w3c.dom.Document;
-import org.w3c.dom.Element;
-import org.xml.sax.InputSource;
+import java.io.StringReader;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.Random;
 
 /**
  * 
@@ -33,19 +37,29 @@ public class WxCryptUtil {
   private static final Base64 BASE64 = new Base64();
   private static final Charset CHARSET = StandardCharsets.UTF_8;
 
-  private static final ThreadLocal BUILDER_LOCAL = new ThreadLocal() {
-    @Override
-    protected DocumentBuilder initialValue() {
-      try {
-        final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
-        factory.setExpandEntityReferences(false);
-        factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
-        return factory.newDocumentBuilder();
-      } catch (ParserConfigurationException exc) {
-        throw new IllegalArgumentException(exc);
+  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();
+      factory.setExpandEntityReferences(false);
+      factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
+      return factory.newDocumentBuilder();
+    } catch (ParserConfigurationException exc) {
+      throw new IllegalArgumentException(exc);
+    }
+  });
 
   protected byte[] aesKey;
   protected String token;
@@ -64,7 +78,7 @@ public WxCryptUtil() {
   public WxCryptUtil(String token, String encodingAesKey, String appidOrCorpid) {
     this.token = token;
     this.appidOrCorpid = appidOrCorpid;
-    this.aesKey = Base64.decodeBase64(encodingAesKey + "=");
+    this.aesKey = Base64.decodeBase64(StringUtils.remove(encodingAesKey, " "));
   }
 
   private static String extractEncryptPart(String xml) {
@@ -75,7 +89,7 @@ private static String extractEncryptPart(String xml) {
       Element root = document.getDocumentElement();
       return root.getElementsByTagName("Encrypt").item(0).getTextContent();
     } catch (Exception e) {
-      throw new RuntimeException(e);
+      throw new WxRuntimeException(e);
     }
   }
 
@@ -108,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();
@@ -157,13 +171,37 @@ public String encrypt(String plainText) {
     return generateXml(encryptedXml, signature, timeStamp, nonce);
   }
 
+  /**
+   * 将公众平台回复用户的消息加密打包.
+   * 
    + *
  1. 对要发送的消息进行AES-CBC加密
  2. + *
  3. 生成安全签名
  4. + *
  5. 将消息密文和安全签名打包成xml格式
  6. + *
+ * + * @param plainText 公众平台待回复用户的消息,xml格式的字符串 + * @return 加密消息所需的值对象 + */ + public EncryptContext encryptContext(String plainText) { + // 加密 + String encryptedXml = encrypt(genRandomStr(), plainText); + + // 生成安全签名 + String timeStamp = Long.toString(System.currentTimeMillis() / 1000L); + String nonce = genRandomStr(); + + String signature = SHA1.gen(this.token, timeStamp, nonce, encryptedXml); + return new EncryptContext(encryptedXml, signature, timeStamp, nonce); + } + /** * 对明文进行加密. * + * @param randomStr 随机字符串 * @param plainText 需要加密的明文 * @return 加密后base64编码的字符串 */ - protected String encrypt(String randomStr, String plainText) { + public String encrypt(String randomStr, String plainText) { ByteGroup byteCollector = new ByteGroup(); byte[] randomStringBytes = randomStr.getBytes(CHARSET); byte[] plainTextBytes = plainText.getBytes(CHARSET); @@ -196,7 +234,7 @@ protected String encrypt(String randomStr, String plainText) { // 使用BASE64对加密后的字符串进行编码 return BASE64.encodeToString(encrypted); } catch (Exception e) { - throw new RuntimeException(e); + throw new WxRuntimeException(e); } } @@ -211,22 +249,58 @@ protected String encrypt(String randomStr, String plainText) { * @param msgSignature 签名串,对应URL参数的msg_signature * @param timeStamp 时间戳,对应URL参数的timestamp * @param nonce 随机串,对应URL参数的nonce - * @param encryptedXml 密文,对应POST请求的数据 + * @param encryptedXml 包含 Encrypt 密文的 xml,对应POST请求的数据 * @return 解密后的原文 */ - public String decrypt(String msgSignature, String timeStamp, String nonce, String encryptedXml) { + public String decryptXml(String msgSignature, String timeStamp, String nonce, String encryptedXml) { // 密钥,公众账号的app corpSecret // 提取密文 String cipherText = extractEncryptPart(encryptedXml); + return decryptContent(msgSignature, timeStamp, nonce, cipherText); + } + /** + * 检验消息的真实性,并且获取解密后的明文. + *
    + *
  1. 利用收到的密文生成安全签名,进行签名验证
  2. + *
  3. 若验证通过,则提取xml中的加密消息
  4. + *
  5. 对消息进行解密
  6. + *
+ * + * @param msgSignature 签名串,对应URL参数的msg_signature + * @param timeStamp 时间戳,对应URL参数的timestamp + * @param nonce 随机串,对应URL参数的nonce + * @param encryptedXml 包含 Encrypt 密文的 xml,对应POST请求的数据 + * @return 解密后的原文 + * @deprecated 由于语义不清晰,置为过时方法,请查看替代方法 {@link #decryptXml} + */ + @Deprecated + public String decrypt(String msgSignature, String timeStamp, String nonce, String encryptedXml) { + return decryptXml(msgSignature, timeStamp, nonce, encryptedXml); + } + + /** + * 检验消息的真实性,并且获取解密后的明文. + *
    + *
  1. 利用收到的密文生成安全签名,进行签名验证
  2. + *
  3. 若验证通过,则提取xml中的加密消息
  4. + *
  5. 对消息进行解密
  6. + *
+ * + * @param msgSignature 签名串,对应URL参数的msg_signature + * @param timeStamp 时间戳,对应URL参数的timestamp + * @param nonce 随机串,对应URL参数的nonce + * @param encryptedContent 加密文本体 + * @return 解密后的原文 + */ + public String decryptContent(String msgSignature, String timeStamp, String nonce, String encryptedContent) { // 验证安全签名 - String signature = SHA1.gen(this.token, timeStamp, nonce, cipherText); + String signature = SHA1.gen(this.token, timeStamp, nonce, encryptedContent); if (!signature.equals(msgSignature)) { - throw new RuntimeException("加密消息签名校验失败"); + throw new WxRuntimeException("加密消息签名校验失败"); } - // 解密 - return decrypt(cipherText); + return decrypt(encryptedContent); } /** @@ -250,7 +324,7 @@ public String decrypt(String cipherText) { // 解密 original = cipher.doFinal(encrypted); } catch (Exception e) { - throw new RuntimeException(e); + throw new WxRuntimeException(e); } String xmlContent; @@ -260,23 +334,45 @@ 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 RuntimeException(e); + if (e instanceof WxRuntimeException) { + throw (WxRuntimeException) e; + } else { + throw new WxRuntimeException(e); + } } // appid不相同的情况 暂时忽略这段判断 -// if (!fromAppid.equals(this.appidOrCorpid)) { -// throw new RuntimeException("AppID不正确,请核实!"); -// } + // if (!fromAppid.equals(this.appidOrCorpid)) { + // throw new WxRuntimeException("AppID不正确,请核实!"); + // } return xmlContent; } + @Data + @AllArgsConstructor + public static class EncryptContext { + private String encrypt; + private String signature; + private String timeStamp; + private String nonce; + } } diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/fs/FileUtils.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/fs/FileUtils.java index 1b051a4718..4a267153c6 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/fs/FileUtils.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/fs/FileUtils.java @@ -1,10 +1,19 @@ package me.chanjar.weixin.common.util.fs; +import org.apache.commons.io.IOUtils; + import java.io.File; import java.io.IOException; import java.io.InputStream; +import java.io.OutputStream; import java.nio.file.Files; +import java.util.Base64; + +import static org.apache.commons.io.FileUtils.openOutputStream; +/** + * @author Daniel Qian + */ public class FileUtils { /** @@ -15,14 +24,21 @@ public class FileUtils { * @param ext 扩展名 * @param tmpDirFile 临时文件夹目录 */ - public static File createTmpFile(InputStream inputStream, String name, String ext, File tmpDirFile) throws IOException { + public static File createTmpFile(InputStream inputStream, String name, String ext, File tmpDirFile) + throws IOException { File resultFile = File.createTempFile(name, '.' + ext, tmpDirFile); resultFile.deleteOnExit(); - org.apache.commons.io.FileUtils.copyToFile(inputStream, resultFile); + copyToFile(inputStream, resultFile); return resultFile; } + private static void copyToFile(final InputStream source, final File destination) throws IOException { + try (InputStream in = source; OutputStream out = openOutputStream(destination)) { + IOUtils.copy(in, out); + } + } + /** * 创建临时文件. * @@ -31,7 +47,35 @@ public static File createTmpFile(InputStream inputStream, String name, String ex * @param ext 扩展名 */ public static File createTmpFile(InputStream inputStream, String name, String ext) throws IOException { - return createTmpFile(inputStream, name, ext, Files.createTempDirectory("weixin-java-tools-temp").toFile()); + return createTmpFile(inputStream, name, ext, Files.createTempDirectory("wxjava-temp").toFile()); + } + + /** + * 文件流生成base64 + * + * @param in 文件流 + * @return base64编码 + */ + public static String imageToBase64ByStream(InputStream in) { + byte[] data = null; + // 读取图片字节数组 + try { + data = new byte[in.available()]; + in.read(data); + // 返回Base64编码过的字节数组字符串 + return Base64.getEncoder().encodeToString(data); + } catch (IOException e) { + e.printStackTrace(); + } finally { + if (in != null) { + try { + in.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + return null; } } 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 5c49c0f78d..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 me.chanjar.weixin.common.WxType; +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/HttpClientType.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/HttpClientType.java new file mode 100644 index 0000000000..a4e22be9b4 --- /dev/null +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/HttpClientType.java @@ -0,0 +1,23 @@ +package me.chanjar.weixin.common.util.http; + +/** + * Created by ecoolper on 2017/4/28. + */ +public enum HttpClientType { + /** + * jodd-http. + */ + JODD_HTTP, + /** + * apache httpclient 4.x. + */ + APACHE_HTTP, + /** + * okhttp. + */ + 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 0d68518849..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,90 +1,71 @@ package me.chanjar.weixin.common.util.http; -import jodd.http.HttpResponse; -import me.chanjar.weixin.common.error.WxError; 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; +import java.nio.charset.StandardCharsets; import java.util.regex.Matcher; import java.util.regex.Pattern; /** *
- * 三种http框架的response代理类,方便提取公共方法
+ * http 框架的 response 代理类,方便提取公共方法
  * Created by Binary Wang on 2017-8-3.
  * 
* * @author Binary Wang */ -public class HttpResponseProxy { - private static final Pattern PATTERN = Pattern.compile(".*filename=\"(.*)\""); - - private CloseableHttpResponse apacheHttpResponse; - private HttpResponse joddHttpResponse; - private Response okHttpResponse; +public interface HttpResponseProxy { - public HttpResponseProxy(CloseableHttpResponse apacheHttpResponse) { - this.apacheHttpResponse = apacheHttpResponse; + static ApacheHttpResponseProxy from(org.apache.http.client.methods.CloseableHttpResponse response) { + return new ApacheHttpResponseProxy(response); } - public HttpResponseProxy(HttpResponse joddHttpResponse) { - this.joddHttpResponse = joddHttpResponse; + static HttpComponentsResponseProxy from(org.apache.hc.client5.http.impl.classic.CloseableHttpResponse response) { + return new HttpComponentsResponseProxy(response); } - public HttpResponseProxy(Response okHttpResponse) { - this.okHttpResponse = okHttpResponse; + static JoddHttpResponseProxy from(jodd.http.HttpResponse response) { + return new JoddHttpResponseProxy(response); } - 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 OkHttpResponseProxy from(okhttp3.Response response) { + return new OkHttpResponseProxy(response); } - private String getFileName(CloseableHttpResponse response) throws WxErrorException { - Header[] contentDispositionHeader = response.getHeaders("Content-disposition"); - if (contentDispositionHeader == null || contentDispositionHeader.length == 0) { - throw new WxErrorException(WxError.builder().errorMsg("无法获取到文件名").errorCode(99999).build()); - } - - return this.extractFileNameFromContentString(contentDispositionHeader[0].getValue()); - } + String getFileName() throws WxErrorException; - private String getFileName(HttpResponse response) throws WxErrorException { - String content = response.header("Content-disposition"); - return this.extractFileNameFromContentString(content); - } - - private String getFileName(Response response) throws WxErrorException { - String content = response.header("Content-disposition"); - return this.extractFileNameFromContentString(content); - } + static String extractFileNameFromContentString(String content) throws WxErrorException { + if (content == null || content.isEmpty()) { + throw new WxErrorException("无法获取到文件名,content为空"); + } - private String extractFileNameFromContentString(String content) throws WxErrorException { - if (content == null || content.length() == 0) { - throw new WxErrorException(WxError.builder().errorMsg("无法获取到文件名").errorCode(99999).build()); + // 查找filename*=utf-8''开头的部分 + Pattern pattern = Pattern.compile("filename\\*=utf-8''(.*?)($|;|\\s|,)"); + Matcher matcher = pattern.matcher(content); + if (matcher.find()) { + String encodedFileName = matcher.group(1); + // 解码URL编码的文件名 + try { + return URLDecoder.decode(encodedFileName, StandardCharsets.UTF_8.name()); + } catch (UnsupportedEncodingException e) { + throw new RuntimeException(e); + } } - Matcher m = PATTERN.matcher(content); - if (m.matches()) { - return m.group(1); + // 查找普通filename="..."部分 + pattern = Pattern.compile("filename=\"(.*?)\""); + matcher = pattern.matcher(content); + if (matcher.find()) { + return new String(matcher.group(1).getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8); } - throw new WxErrorException(WxError.builder().errorMsg("无法获取到文件名").errorCode(99999).build()); + throw new WxErrorException("无法获取到文件名,header信息有问题"); } } 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/HttpType.java deleted file mode 100644 index eff5907f7a..0000000000 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/HttpType.java +++ /dev/null @@ -1,19 +0,0 @@ -package me.chanjar.weixin.common.util.http; - -/** - * Created by ecoolper on 2017/4/28. - */ -public enum HttpType { - /** - * jodd-http. - */ - JODD_HTTP, - /** - * apache httpclient. - */ - APACHE_HTTP, - /** - * okhttp. - */ - OK_HTTP -} diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/InputStreamData.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/InputStreamData.java new file mode 100644 index 0000000000..f03932984f --- /dev/null +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/InputStreamData.java @@ -0,0 +1,28 @@ +package me.chanjar.weixin.common.util.http; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.Accessors; + +import java.io.InputStream; +import java.io.Serializable; + +/** + * 输入流数据. + *

+ * InputStreamData + *

+ * + * @author zichuan.zhou91@gmail.com + * created on 2022/2/15 + */ +@Data +@Accessors(chain = true) +@NoArgsConstructor +@AllArgsConstructor +public class InputStreamData implements Serializable { + private static final long serialVersionUID = -4627006604779378520L; + private InputStream inputStream; + private String filename; +} 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 new file mode 100644 index 0000000000..22c426ca54 --- /dev/null +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/MediaInputStreamUploadRequestExecutor.java @@ -0,0 +1,53 @@ +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; + +/** + * 上传媒体文件请求执行器. + * 请求的参数是File, 返回的结果是String + * + * @author Daniel Qian + */ +public abstract class MediaInputStreamUploadRequestExecutor implements RequestExecutor { + protected RequestHttp requestHttp; + + public MediaInputStreamUploadRequestExecutor(RequestHttp requestHttp) { + this.requestHttp = requestHttp; + } + + @Override + public void execute(String uri, InputStreamData data, ResponseHandler handler, WxType wxType) throws WxErrorException, IOException { + handler.handle(this.execute(uri, data, wxType)); + } + + @SuppressWarnings("unchecked") + public static RequestExecutor create(RequestHttp requestHttp) { + switch (requestHttp.getRequestType()) { + case APACHE_HTTP: + return new ApacheMediaInputStreamUploadRequestExecutor( + (RequestHttp) requestHttp); + case JODD_HTTP: + return new JoddHttpMediaInputStreamUploadRequestExecutor((RequestHttp) requestHttp); + case OK_HTTP: + return new OkHttpMediaInputStreamUploadRequestExecutor((RequestHttp) requestHttp); + case HTTP_COMPONENTS: + return new HttpComponentsMediaInputStreamUploadRequestExecutor( + (RequestHttp) requestHttp); + default: + 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 df1444cc75..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,25 +1,36 @@ package me.chanjar.weixin.common.util.http; -import java.io.File; -import java.io.IOException; - -import me.chanjar.weixin.common.WxType; +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; /** * 上传媒体文件请求执行器. * 请求的参数是File, 返回的结果是String * * @author Daniel Qian + * @see WxService#upload(String, CommonUploadParam) 通用的上传,封装接口是推荐调用此方法 + * @see CommonUploadParam 通用的上传参数 + * @deprecated 不应该继续使用执行器的方式上传文件,封装上传接口时应调用通用的文件上传,而旧代码也应该逐步迁移为新的上传方式 */ +@Deprecated public abstract class MediaUploadRequestExecutor implements RequestExecutor { protected RequestHttp requestHttp; - public MediaUploadRequestExecutor(RequestHttp requestHttp) { + public MediaUploadRequestExecutor(RequestHttp requestHttp) { this.requestHttp = requestHttp; } @@ -28,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 new file mode 100644 index 0000000000..0e8684a1db --- /dev/null +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/MinishopUploadRequestCustomizeExecutor.java @@ -0,0 +1,57 @@ +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; + +public abstract class MinishopUploadRequestCustomizeExecutor implements RequestExecutor { + protected RequestHttp requestHttp; + protected String respType; + protected String uploadType; + protected 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 { + this.uploadType = "1"; + this.imgUrl = imgUrl; + } + } + + @Override + public void execute(String uri, File data, ResponseHandler handler, WxType wxType) throws WxErrorException, IOException { + handler.handle(this.execute(uri, data, wxType)); + } + + @SuppressWarnings("unchecked") + public static RequestExecutor create(RequestHttp requestHttp, String respType, String imgUrl) { + switch (requestHttp.getRequestType()) { + case APACHE_HTTP: + return new ApacheMinishopMediaUploadRequestCustomizeExecutor( + (RequestHttp) requestHttp, respType, imgUrl); + case JODD_HTTP: + return new JoddHttpMinishopMediaUploadRequestCustomizeExecutor((RequestHttp) requestHttp, respType, imgUrl); + case OK_HTTP: + return new OkHttpMinishopMediaUploadRequestCustomizeExecutor((RequestHttp) requestHttp, respType, imgUrl); + case HTTP_COMPONENTS: + return new HttpComponentsMinishopMediaUploadRequestCustomizeExecutor( + (RequestHttp) requestHttp, respType, imgUrl); + default: + 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 new file mode 100644 index 0000000000..e6018a7791 --- /dev/null +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/MinishopUploadRequestExecutor.java @@ -0,0 +1,47 @@ +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; + +public abstract class MinishopUploadRequestExecutor implements RequestExecutor { + protected RequestHttp requestHttp; + + public MinishopUploadRequestExecutor(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)); + } + + @SuppressWarnings("unchecked") + public static RequestExecutor create(RequestHttp requestHttp) { + switch (requestHttp.getRequestType()) { + case APACHE_HTTP: + return new ApacheMinishopMediaUploadRequestExecutor( + (RequestHttp) requestHttp); + case JODD_HTTP: + return new JoddHttpMinishopMediaUploadRequestExecutor((RequestHttp) requestHttp); + case OK_HTTP: + return new OkHttpMinishopMediaUploadRequestExecutor((RequestHttp) requestHttp); + case HTTP_COMPONENTS: + return new HttpComponentsMinishopMediaUploadRequestExecutor( + (RequestHttp) requestHttp); + default: + throw new IllegalArgumentException("不支持的http执行器类型:" + requestHttp.getRequestType()); + } + } +} diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/RequestExecutor.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/RequestExecutor.java index 3d0b772f94..b5e394756e 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/RequestExecutor.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/RequestExecutor.java @@ -1,6 +1,6 @@ package me.chanjar.weixin.common.util.http; -import me.chanjar.weixin.common.WxType; +import me.chanjar.weixin.common.enums.WxType; import me.chanjar.weixin.common.error.WxErrorException; import java.io.IOException; @@ -37,4 +37,6 @@ public interface RequestExecutor { * @throws IOException io异常 */ void execute(String uri, E data, ResponseHandler handler, WxType wxType) throws WxErrorException, IOException; + + } 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 05c396e029..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,12 +1,18 @@ package me.chanjar.weixin.common.util.http; -import java.io.IOException; - -import me.chanjar.weixin.common.WxType; +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.ApacheHttpClientSimpleGetRequestExecutor; +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; /** * 简单的GET请求执行器. @@ -26,17 +32,30 @@ 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 ApacheHttpClientSimpleGetRequestExecutor(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()); } } + protected String handleResponse(WxType wxType, String responseContent) throws WxErrorException { + 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/util/http/SimplePostRequestExecutor.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/SimplePostRequestExecutor.java index 692387f9f2..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,15 +1,21 @@ package me.chanjar.weixin.common.util.http; -import java.io.IOException; - -import me.chanjar.weixin.common.WxType; +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; /** - * 用装饰模式实现 * 简单的POST请求执行器,请求的参数是String, 返回的结果也是String * * @author Daniel Qian @@ -17,7 +23,7 @@ public abstract class SimplePostRequestExecutor implements RequestExecutor { protected RequestHttp requestHttp; - public SimplePostRequestExecutor(RequestHttp requestHttp) { + public SimplePostRequestExecutor(RequestHttp requestHttp) { this.requestHttp = requestHttp; } @@ -27,17 +33,39 @@ 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()); } } + @NotNull + public String handleResponse(WxType wxType, String responseContent) throws WxErrorException { + if (responseContent.isEmpty()) { + throw new WxErrorException("无响应内容"); + } + + if (responseContent.startsWith("")) { + //xml格式输出直接返回 + return responseContent; + } + + 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/util/http/WxDnsResolver.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/WxDnsResolver.java index 6c6137089a..ff0977e8d8 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/WxDnsResolver.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/WxDnsResolver.java @@ -18,8 +18,8 @@ */ public class WxDnsResolver implements DnsResolver { - private final static String WECHAT_API_URL = "api.weixin.qq.com"; - private static Map MAPPINGS = new HashMap(); + private static final String WECHAT_API_URL = "api.weixin.qq.com"; + private static Map MAPPINGS = new HashMap<>(); protected final Logger log = LoggerFactory.getLogger(WxDnsResolver.class); private String wxApiIp; @@ -38,7 +38,7 @@ private void init() { } catch (UnknownHostException e) { //如果初始化DNS配置失败则使用默认配置,不影响服务的启动 log.error("init WxDnsResolver error", e); - MAPPINGS = new HashMap(); + MAPPINGS = new HashMap<>(); } } 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 fcd56c48a7..5b13e7cc17 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/ApacheHttpClientBuilder.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/ApacheHttpClientBuilder.java @@ -1,5 +1,7 @@ package me.chanjar.weixin.common.util.http.apache; +import org.apache.http.client.HttpRequestRetryHandler; +import org.apache.http.conn.ConnectionKeepAliveStrategy; import org.apache.http.conn.ssl.SSLConnectionSocketFactory; import org.apache.http.impl.client.CloseableHttpClient; @@ -19,26 +21,66 @@ public interface ApacheHttpClientBuilder { /** * 代理服务器地址. + * + * @param httpProxyHost 代理服务器地址 + * @return ApacheHttpClientBuilder */ ApacheHttpClientBuilder httpProxyHost(String httpProxyHost); /** * 代理服务器端口. + * + * @param httpProxyPort 代理服务器端口 + * @return ApacheHttpClientBuilder */ ApacheHttpClientBuilder httpProxyPort(int httpProxyPort); /** * 代理服务器用户名. + * + * @param httpProxyUsername 代理服务器用户名 + * @return ApacheHttpClientBuilder */ ApacheHttpClientBuilder httpProxyUsername(String httpProxyUsername); /** * 代理服务器密码. + * + * @param httpProxyPassword 代理服务器密码 + * @return ApacheHttpClientBuilder */ ApacheHttpClientBuilder httpProxyPassword(String httpProxyPassword); + /** + * 重试策略. + * + * @param httpRequestRetryHandler 重试处理器 + * @return ApacheHttpClientBuilder + */ + ApacheHttpClientBuilder httpRequestRetryHandler(HttpRequestRetryHandler httpRequestRetryHandler); + + /** + * 超时时间. + * + * @param keepAliveStrategy 保持连接策略 + * @return ApacheHttpClientBuilder + */ + ApacheHttpClientBuilder keepAliveStrategy(ConnectionKeepAliveStrategy keepAliveStrategy); + /** * ssl连接socket工厂. + * + * @param sslConnectionSocketFactory SSL连接Socket工厂 + * @return ApacheHttpClientBuilder */ ApacheHttpClientBuilder sslConnectionSocketFactory(SSLConnectionSocketFactory sslConnectionSocketFactory); + + /** + * 支持的TLS协议版本. + * Supported TLS protocol versions. + * + * @param supportedProtocols 支持的协议版本数组 + * @return ApacheHttpClientBuilder + */ + ApacheHttpClientBuilder supportedProtocols(String[] supportedProtocols); } diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/ApacheHttpClientSimpleGetRequestExecutor.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/ApacheHttpClientSimpleGetRequestExecutor.java deleted file mode 100644 index cc830013d2..0000000000 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/ApacheHttpClientSimpleGetRequestExecutor.java +++ /dev/null @@ -1,53 +0,0 @@ -package me.chanjar.weixin.common.util.http.apache; - -import me.chanjar.weixin.common.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.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; - -import java.io.IOException; - -/** - * . - * - * @author ecoolper - * @date 2017/5/4 - */ -public class ApacheHttpClientSimpleGetRequestExecutor extends SimpleGetRequestExecutor { - public ApacheHttpClientSimpleGetRequestExecutor(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); - } - - try (CloseableHttpResponse response = requestHttp.getRequestHttpClient().execute(httpGet)) { - String responseContent = Utf8ResponseHandler.INSTANCE.handleResponse(response); - WxError error = WxError.fromJson(responseContent, wxType); - if (error.getErrorCode() != 0) { - throw new WxErrorException(error); - } - return responseContent; - } finally { - httpGet.releaseConnection(); - } - } - -} 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 fe5472f3c0..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 @@ -1,12 +1,7 @@ package me.chanjar.weixin.common.util.http.apache; -import java.io.IOException; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; - import org.apache.commons.lang3.StringUtils; import org.apache.http.HttpHost; -import org.apache.http.annotation.NotThreadSafe; import org.apache.http.auth.AuthScope; import org.apache.http.auth.UsernamePasswordCredentials; import org.apache.http.client.CredentialsProvider; @@ -15,6 +10,7 @@ import org.apache.http.config.Registry; import org.apache.http.config.RegistryBuilder; import org.apache.http.config.SocketConfig; +import org.apache.http.conn.ConnectionKeepAliveStrategy; import org.apache.http.conn.DnsResolver; import org.apache.http.conn.HttpClientConnectionManager; import org.apache.http.conn.socket.ConnectionSocketFactory; @@ -29,6 +25,11 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import javax.annotation.concurrent.NotThreadSafe; +import java.io.IOException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; + /** * httpclient 连接管理器 自带DNS解析. *

大部分代码拷贝自:DefaultApacheHttpClientBuilder

@@ -48,21 +49,19 @@ public class ApacheHttpDnsClientBuilder implements ApacheHttpClientBuilder { private int maxTotalConn = 50; private String userAgent; - private DnsResolver dnsResover; + private DnsResolver dnsResolver; - private HttpRequestRetryHandler httpRequestRetryHandler = new HttpRequestRetryHandler() { - @Override - public boolean retryRequest(IOException exception, int executionCount, HttpContext context) { - return false; - } - }; + private HttpRequestRetryHandler httpRequestRetryHandler = (IOException exception, int executionCount, HttpContext context) -> false; private SSLConnectionSocketFactory sslConnectionSocketFactory = SSLConnectionSocketFactory.getSocketFactory(); private PlainConnectionSocketFactory plainConnectionSocketFactory = PlainConnectionSocketFactory.getSocketFactory(); private String httpProxyHost; + private ConnectionKeepAliveStrategy keepAliveStrategy; + private int httpProxyPort; private String httpProxyUsername; private String httpProxyPassword; + /** * 闲置连接监控线程. */ @@ -100,12 +99,31 @@ public ApacheHttpClientBuilder httpProxyPassword(String httpProxyPassword) { return this; } + @Override + public ApacheHttpClientBuilder httpRequestRetryHandler(HttpRequestRetryHandler httpRequestRetryHandler) { + this.httpRequestRetryHandler = httpRequestRetryHandler; + return this; + } + + @Override + public ApacheHttpClientBuilder keepAliveStrategy(ConnectionKeepAliveStrategy keepAliveStrategy) { + this.keepAliveStrategy = keepAliveStrategy; + return this; + } + @Override public ApacheHttpClientBuilder sslConnectionSocketFactory(SSLConnectionSocketFactory sslConnectionSocketFactory) { this.sslConnectionSocketFactory = sslConnectionSocketFactory; 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 *

@@ -205,11 +223,11 @@ private synchronized void prepare() { @SuppressWarnings("resource") PoolingHttpClientConnectionManager connectionManager; - if (dnsResover != null) { + if (dnsResolver != null) { if (log.isDebugEnabled()) { log.debug("specified dns resolver."); } - connectionManager = new PoolingHttpClientConnectionManager(registry, dnsResover); + connectionManager = new PoolingHttpClientConnectionManager(registry, dnsResolver); } else { if (log.isDebugEnabled()) { log.debug("Not specified dns resolver."); @@ -257,12 +275,12 @@ public CloseableHttpClient build() { return this.httpClientBuilder.build(); } - public DnsResolver getDnsResover() { - return dnsResover; + public DnsResolver getDnsResolver() { + return dnsResolver; } - public void setDnsResover(DnsResolver dnsResover) { - this.dnsResover = dnsResover; + public void setDnsResolver(DnsResolver dnsResolver) { + this.dnsResolver = dnsResolver; } public static class IdleConnectionMonitorThread extends Thread { 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 51c37f59a5..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 @@ -1,6 +1,6 @@ package me.chanjar.weixin.common.util.http.apache; -import me.chanjar.weixin.common.WxType; +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; @@ -25,10 +25,10 @@ * . * * @author ecoolper - * @date 2017/5/5 + * 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,16 +58,17 @@ 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()); } - return FileUtils.createTmpFile(inputStream, FilenameUtils.getBaseName(fileName), FilenameUtils.getExtension(fileName), - super.tmpDirFile); + String baseName = FilenameUtils.getBaseName(fileName); + if (StringUtils.isBlank(fileName) || baseName.length() < 3) { + baseName = String.valueOf(System.currentTimeMillis()); + } - } 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 new file mode 100644 index 0000000000..43a5d604b0 --- /dev/null +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/ApacheMediaInputStreamUploadRequestExecutor.java @@ -0,0 +1,54 @@ +package me.chanjar.weixin.common.util.http.apache; + +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.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.ContentType; +import org.apache.http.entity.mime.HttpMultipartMode; +import org.apache.http.entity.mime.MultipartEntityBuilder; +import org.apache.http.impl.client.CloseableHttpClient; + +import java.io.IOException; + +/** + * 文件输入流上传. + * + * @author meiqin.zhou91@gmail.com + * created on 2022/02/15 + */ +public class ApacheMediaInputStreamUploadRequestExecutor extends MediaInputStreamUploadRequestExecutor { + public ApacheMediaInputStreamUploadRequestExecutor(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.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 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 3c1b15dc9a..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.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 new file mode 100644 index 0000000000..48fafc3401 --- /dev/null +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/ApacheMinishopMediaUploadRequestCustomizeExecutor.java @@ -0,0 +1,68 @@ +package me.chanjar.weixin.common.util.http.apache; + +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.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; + +/** + * Created by liming1019 on 2021/8/10. + */ +@Slf4j +public class ApacheMinishopMediaUploadRequestCustomizeExecutor extends MinishopUploadRequestCustomizeExecutor { + public ApacheMinishopMediaUploadRequestCustomizeExecutor(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.RFC6532) + .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(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); + } + 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 new file mode 100644 index 0000000000..f76d4e8642 --- /dev/null +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/ApacheMinishopMediaUploadRequestExecutor.java @@ -0,0 +1,53 @@ +package me.chanjar.weixin.common.util.http.apache; + +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.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; + +/** + * Created by ecoolper on 2017/5/5. + */ +@Slf4j +public class ApacheMinishopMediaUploadRequestExecutor extends MinishopUploadRequestExecutor { + public ApacheMinishopMediaUploadRequestExecutor(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.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); + } + 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 new file mode 100644 index 0000000000..08ccf1b252 --- /dev/null +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/ApacheSimpleGetRequestExecutor.java @@ -0,0 +1,43 @@ +package me.chanjar.weixin.common.util.http.apache; + +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.http.HttpHost; +import org.apache.http.client.config.RequestConfig; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.impl.client.CloseableHttpClient; + +import java.io.IOException; + +/** + * . + * + * @author ecoolper + * created on 2017/5/4 + */ +public class ApacheSimpleGetRequestExecutor extends SimpleGetRequestExecutor { + public ApacheSimpleGetRequestExecutor(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/apache/ApacheSimplePostRequestExecutor.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/ApacheSimplePostRequestExecutor.java index 3405aaab78..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 @@ -1,28 +1,27 @@ package me.chanjar.weixin.common.util.http.apache; -import me.chanjar.weixin.common.WxType; -import me.chanjar.weixin.common.error.WxError; +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.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; /** * . * * @author ecoolper - * @date 2017/5/4 + * created on 2017/5/4 */ public class ApacheSimplePostRequestExecutor extends SimplePostRequestExecutor { - public ApacheSimplePostRequestExecutor(RequestHttp requestHttp) { + public ApacheSimplePostRequestExecutor(RequestHttp requestHttp) { super(requestHttp); } @@ -35,28 +34,12 @@ public String execute(String uri, String postEntity, WxType wxType) throws WxErr } if (postEntity != null) { - StringEntity entity = new StringEntity(postEntity, Consts.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); - if (responseContent.isEmpty()) { - throw new WxErrorException(WxError.builder().errorCode(9999).errorMsg("无响应内容").build()); - } - - if (responseContent.startsWith("")) { - //xml格式输出直接返回 - return responseContent; - } - - 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); + 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 dfca21a7b2..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 @@ -1,8 +1,11 @@ package me.chanjar.weixin.common.util.http.apache; +import lombok.Data; +import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.apache.http.HttpHost; -import org.apache.http.annotation.NotThreadSafe; +import org.apache.http.HttpRequestInterceptor; +import org.apache.http.HttpResponseInterceptor; import org.apache.http.auth.AuthScope; import org.apache.http.auth.UsernamePasswordCredentials; import org.apache.http.client.CredentialsProvider; @@ -11,6 +14,7 @@ import org.apache.http.config.Registry; import org.apache.http.config.RegistryBuilder; import org.apache.http.config.SocketConfig; +import org.apache.http.conn.ConnectionKeepAliveStrategy; import org.apache.http.conn.HttpClientConnectionManager; import org.apache.http.conn.socket.ConnectionSocketFactory; import org.apache.http.conn.socket.PlainConnectionSocketFactory; @@ -21,18 +25,15 @@ 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 org.slf4j.Logger; -import org.slf4j.LoggerFactory; +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; import java.util.concurrent.atomic.AtomicBoolean; @@ -41,26 +42,87 @@ * * @author kakotor */ +@Slf4j +@Data @NotThreadSafe public class DefaultApacheHttpClientBuilder implements ApacheHttpClientBuilder { - protected final Logger log = LoggerFactory.getLogger(DefaultApacheHttpClientBuilder.class); 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 HttpRequestRetryHandler httpRequestRetryHandler = new HttpRequestRetryHandler() { - @Override - public boolean retryRequest(IOException exception, int executionCount, HttpContext context) { - return false; - } - }; + + /** + * 支持的TLS协议版本,默认支持现代TLS版本 + * Supported TLS protocol versions, defaults to modern TLS versions + */ + private String[] supportedProtocols = {"TLSv1.2", "TLSv1.3", "TLSv1.1", "TLSv1"}; + + /** + * 自定义请求拦截器 + */ + private List requestInterceptors = new ArrayList<>(); + + /** + * 自定义响应拦截器 + */ + private List responseInterceptors = new ArrayList<>(); + + /** + * 自定义重试策略 + */ + private HttpRequestRetryHandler httpRequestRetryHandler; + + /** + * 自定义KeepAlive策略 + */ + private ConnectionKeepAliveStrategy connectionKeepAliveStrategy; + + private final HttpRequestRetryHandler defaultHttpRequestRetryHandler = (exception, executionCount, context) -> false; + private SSLConnectionSocketFactory sslConnectionSocketFactory = SSLConnectionSocketFactory.getSocketFactory(); - private PlainConnectionSocketFactory plainConnectionSocketFactory = PlainConnectionSocketFactory.getSocketFactory(); + private final PlainConnectionSocketFactory plainConnectionSocketFactory = PlainConnectionSocketFactory.getSocketFactory(); private String httpProxyHost; private int httpProxyPort; private String httpProxyUsername; @@ -106,93 +168,27 @@ public ApacheHttpClientBuilder httpProxyPassword(String httpProxyPassword) { } @Override - public ApacheHttpClientBuilder sslConnectionSocketFactory(SSLConnectionSocketFactory sslConnectionSocketFactory) { - this.sslConnectionSocketFactory = sslConnectionSocketFactory; + public ApacheHttpClientBuilder httpRequestRetryHandler(HttpRequestRetryHandler httpRequestRetryHandler) { + this.httpRequestRetryHandler = httpRequestRetryHandler; return this; } - /** - * 获取链接的超时时间设置,默认3000ms - *

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

- * - * @param connectionRequestTimeout 获取链接的超时时间设置(单位毫秒),默认3000ms - */ - public void setConnectionRequestTimeout(int connectionRequestTimeout) { - this.connectionRequestTimeout = connectionRequestTimeout; - } - - /** - * 建立链接的超时时间,默认为5000ms.由于是在链接池获取链接,此设置应该并不起什么作用 - *

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

- * - * @param connectionTimeout 建立链接的超时时间设置(单位毫秒),默认5000ms - */ - public void setConnectionTimeout(int connectionTimeout) { - this.connectionTimeout = connectionTimeout; - } - - /** - * 默认NIO的socket超时设置,默认5000ms. - * - * @param soTimeout 默认NIO的socket超时设置,默认5000ms. - * @see java.net.SocketOptions#SO_TIMEOUT - */ - public void setSoTimeout(int soTimeout) { - this.soTimeout = soTimeout; - } - - /** - * 空闲链接的超时时间,默认60000ms. - *

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

- * - * @param idleConnTimeout 空闲链接的超时时间,默认60000ms. - */ - public void setIdleConnTimeout(int idleConnTimeout) { - this.idleConnTimeout = idleConnTimeout; - } - - /** - * 检查空间链接的间隔周期,默认60000ms. - * - * @param checkWaitTime 检查空间链接的间隔周期,默认60000ms. - */ - public void setCheckWaitTime(int checkWaitTime) { - this.checkWaitTime = checkWaitTime; - } - - /** - * 每路的最大链接数,默认10 - * - * @param maxConnPerHost 每路的最大链接数,默认10 - */ - public void setMaxConnPerHost(int maxConnPerHost) { - this.maxConnPerHost = maxConnPerHost; + @Override + public ApacheHttpClientBuilder keepAliveStrategy(ConnectionKeepAliveStrategy keepAliveStrategy) { + this.connectionKeepAliveStrategy = keepAliveStrategy; + return this; } - /** - * 最大总连接数,默认50 - * - * @param maxTotalConn 最大总连接数,默认50 - */ - public void setMaxTotalConn(int maxTotalConn) { - this.maxTotalConn = maxTotalConn; + @Override + public ApacheHttpClientBuilder sslConnectionSocketFactory(SSLConnectionSocketFactory sslConnectionSocketFactory) { + this.sslConnectionSocketFactory = sslConnectionSocketFactory; + return this; } - /** - * 自定义httpclient的User Agent - * - * @param userAgent User Agent - */ - public void setUserAgent(String userAgent) { - this.userAgent = userAgent; + @Override + public ApacheHttpClientBuilder supportedProtocols(String[] supportedProtocols) { + this.supportedProtocols = supportedProtocols; + return this; } public IdleConnectionMonitorThread getIdleConnectionMonitorThread() { @@ -208,7 +204,6 @@ private synchronized void prepare() { .register("https", this.sslConnectionSocketFactory) .build(); - @SuppressWarnings("resource") PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(registry); connectionManager.setMaxTotal(this.maxTotalConn); connectionManager.setDefaultMaxPerRoute(this.maxConnPerHost); @@ -232,7 +227,16 @@ private synchronized void prepare() { .setConnectTimeout(this.connectionTimeout) .setConnectionRequestTimeout(this.connectionRequestTimeout) .build() - ).setRetryHandler(this.httpRequestRetryHandler); + ); + + // 设置重试策略,没有则使用默认 + httpRequestRetryHandler = httpRequestRetryHandler == null ? defaultHttpRequestRetryHandler : httpRequestRetryHandler; + httpClientBuilder.setRetryHandler(httpRequestRetryHandler); + + // 设置KeepAliveStrategy,没有使用默认 + if (connectionKeepAliveStrategy != null) { + httpClientBuilder.setKeepAliveStrategy(connectionKeepAliveStrategy); + } if (StringUtils.isNotBlank(this.httpProxyHost) && StringUtils.isNotBlank(this.httpProxyUsername)) { // 使用代理服务器 需要用户认证的代理服务器 @@ -247,6 +251,12 @@ private synchronized void prepare() { httpClientBuilder.setUserAgent(this.userAgent); } + //添加自定义的请求拦截器 + requestInterceptors.forEach(httpClientBuilder::addInterceptorFirst); + + //添加自定义的响应拦截器 + responseInterceptors.forEach(httpClientBuilder::addInterceptorLast); + this.closeableHttpClient = httpClientBuilder.build(); prepared.set(true); } @@ -255,20 +265,15 @@ private SSLConnectionSocketFactory buildSSLConnectionSocketFactory() { try { SSLContext sslcontext = SSLContexts.custom() //忽略掉对服务器端证书的校验 - .loadTrustMaterial(new TrustStrategy() { - @Override - public boolean isTrusted(X509Certificate[] chain, String authType) throws CertificateException { - return true; - } - }).build(); + .loadTrustMaterial((TrustStrategy) (chain, authType) -> true).build(); return new SSLConnectionSocketFactory( sslcontext, - new String[]{"TLSv1"}, + this.supportedProtocols, null, SSLConnectionSocketFactory.getDefaultHostnameVerifier()); } catch (NoSuchAlgorithmException | KeyManagementException | KeyStoreException e) { - this.log.error(e.getMessage(), e); + log.error("构建SSL连接工厂时发生异常!", e); } return null; 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 697d4695e2..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.getReasonPhrase()); - } - 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 4f310274df..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,8 +4,7 @@ import jodd.http.HttpRequest; import jodd.http.HttpResponse; import jodd.http.ProxyInfo; -import jodd.util.StringPool; -import me.chanjar.weixin.common.WxType; +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; @@ -19,15 +18,16 @@ import java.io.File; import java.io.IOException; import java.io.InputStream; +import java.nio.charset.StandardCharsets; /** * . * * @author ecoolper - * @date 2017/5/5 + * 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); } @@ -47,7 +47,7 @@ public File execute(String uri, String queryParam, WxType wxType) throws WxError request.withConnectionProvider(requestHttp.getRequestHttpClient()); HttpResponse response = request.send(); - response.charset(StringPool.UTF_8); + response.charset(StandardCharsets.UTF_8.name()); String contentType = response.header("Content-Type"); if (contentType != null && contentType.startsWith("application/json")) { @@ -55,14 +55,19 @@ 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; } + String baseName = FilenameUtils.getBaseName(fileName); + if (StringUtils.isBlank(fileName) || baseName.length() < 3) { + baseName = String.valueOf(System.currentTimeMillis()); + } + try (InputStream inputStream = new ByteArrayInputStream(response.bodyBytes())) { return FileUtils.createTmpFile(inputStream, - FilenameUtils.getBaseName(fileName), + baseName, FilenameUtils.getExtension(fileName), super.tmpDirFile); } 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 new file mode 100644 index 0000000000..915db21c65 --- /dev/null +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/jodd/JoddHttpMediaInputStreamUploadRequestExecutor.java @@ -0,0 +1,61 @@ +package me.chanjar.weixin.common.util.http.jodd; + +import jodd.http.HttpConnectionProvider; +import jodd.http.HttpRequest; +import jodd.http.HttpResponse; +import jodd.http.ProxyInfo; +import jodd.http.upload.ByteArrayUploadable; +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 java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; + +/** + * 文件输入流上传. + * + * @author meiqin.zhou91@gmail.com + * created on 2022/02/15 + */ +public class JoddHttpMediaInputStreamUploadRequestExecutor extends MediaInputStreamUploadRequestExecutor { + public JoddHttpMediaInputStreamUploadRequestExecutor(RequestHttp requestHttp) { + super(requestHttp); + } + + @Override + public WxMediaUploadResult execute(String uri, InputStreamData data, WxType wxType) throws WxErrorException, IOException { + HttpRequest request = HttpRequest.post(uri); + if (requestHttp.getRequestHttpProxy() != null) { + requestHttp.getRequestHttpClient().useProxy(requestHttp.getRequestHttpProxy()); + } + request.withConnectionProvider(requestHttp.getRequestHttpClient()); + request.form("media", new ByteArrayUploadable(this.toByteArray(data.getInputStream()), data.getFilename())); + HttpResponse response = request.send(); + response.charset(StandardCharsets.UTF_8.name()); + + String responseContent = response.bodyText(); + WxError error = WxError.fromJson(responseContent, wxType); + if (error.getErrorCode() != 0) { + throw new WxErrorException(error); + } + return WxMediaUploadResult.fromJson(responseContent); + } + + public byte[] toByteArray(InputStream input) throws IOException { + try (ByteArrayOutputStream output = new ByteArrayOutputStream()) { + byte[] buffer = new byte[4096]; + int n = 0; + while (-1 != (n = input.read(buffer))) { + output.write(buffer, 0, n); + } + return output.toByteArray(); + } + } +} 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 3c7122a1f8..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,8 +4,7 @@ import jodd.http.HttpRequest; import jodd.http.HttpResponse; import jodd.http.ProxyInfo; -import jodd.util.StringPool; -import me.chanjar.weixin.common.WxType; +import me.chanjar.weixin.common.enums.WxType; import me.chanjar.weixin.common.bean.result.WxMediaUploadResult; import me.chanjar.weixin.common.error.WxError; import me.chanjar.weixin.common.error.WxErrorException; @@ -14,15 +13,16 @@ import java.io.File; import java.io.IOException; +import java.nio.charset.StandardCharsets; /** * . * * @author ecoolper - * @date 2017/5/5 + * created on 2017/5/5 */ public class JoddHttpMediaUploadRequestExecutor extends MediaUploadRequestExecutor { - public JoddHttpMediaUploadRequestExecutor(RequestHttp requestHttp) { + public JoddHttpMediaUploadRequestExecutor(RequestHttp requestHttp) { super(requestHttp); } @@ -35,7 +35,7 @@ public WxMediaUploadResult execute(String uri, File file, WxType wxType) throws request.withConnectionProvider(requestHttp.getRequestHttpClient()); request.form("media", file); HttpResponse response = request.send(); - response.charset(StringPool.UTF_8); + response.charset(StandardCharsets.UTF_8.name()); String responseContent = response.bodyText(); WxError error = WxError.fromJson(responseContent, wxType); 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 new file mode 100644 index 0000000000..66074d8103 --- /dev/null +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/jodd/JoddHttpMinishopMediaUploadRequestCustomizeExecutor.java @@ -0,0 +1,58 @@ +package me.chanjar.weixin.common.util.http.jodd; + +import jodd.http.HttpConnectionProvider; +import jodd.http.HttpRequest; +import jodd.http.HttpResponse; +import jodd.http.ProxyInfo; +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 java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; + +/** + * @author liming1019 + * created on 2021/8/10 + */ +@Slf4j +public class JoddHttpMinishopMediaUploadRequestCustomizeExecutor extends MinishopUploadRequestCustomizeExecutor { + public JoddHttpMinishopMediaUploadRequestCustomizeExecutor(RequestHttp requestHttp, String respType, String imgUrl) { + super(requestHttp, respType, imgUrl); + } + + @Override + public WxMinishopImageUploadCustomizeResult execute(String uri, File file, WxType wxType) throws WxErrorException, IOException { + HttpRequest request = HttpRequest.post(uri); + if (requestHttp.getRequestHttpProxy() != null) { + requestHttp.getRequestHttpClient().useProxy(requestHttp.getRequestHttpProxy()); + } + request.withConnectionProvider(requestHttp.getRequestHttpClient()); + if (this.uploadType.equals("0")) { + request.form("resp_type", this.respType, + "upload_type", this.uploadType, + "media", file); + } + else { + request.form("resp_type", this.respType, + "upload_type", this.uploadType, + "img_url", this.imgUrl); + } + HttpResponse response = request.send(); + response.charset(StandardCharsets.UTF_8.name()); + + String responseContent = response.bodyText(); + 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/jodd/JoddHttpMinishopMediaUploadRequestExecutor.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/jodd/JoddHttpMinishopMediaUploadRequestExecutor.java new file mode 100644 index 0000000000..c7c35dd798 --- /dev/null +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/jodd/JoddHttpMinishopMediaUploadRequestExecutor.java @@ -0,0 +1,51 @@ +package me.chanjar.weixin.common.util.http.jodd; + +import jodd.http.HttpConnectionProvider; +import jodd.http.HttpRequest; +import jodd.http.HttpResponse; +import jodd.http.ProxyInfo; +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 java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; + +/** + * . + * + * @author ecoolper + * created on 2017/5/5 + */ +@Slf4j +public class JoddHttpMinishopMediaUploadRequestExecutor extends MinishopUploadRequestExecutor { + public JoddHttpMinishopMediaUploadRequestExecutor(RequestHttp requestHttp) { + super(requestHttp); + } + + @Override + public WxMinishopImageUploadResult execute(String uri, File file, WxType wxType) throws WxErrorException, IOException { + HttpRequest request = HttpRequest.post(uri); + if (requestHttp.getRequestHttpProxy() != null) { + requestHttp.getRequestHttpClient().useProxy(requestHttp.getRequestHttpProxy()); + } + request.withConnectionProvider(requestHttp.getRequestHttpClient()); + request.form("media", file); + HttpResponse response = request.send(); + response.charset(StandardCharsets.UTF_8.name()); + + String responseContent = response.bodyText(); + 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/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 c93bd4b180..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,23 +4,22 @@ import jodd.http.HttpRequest; import jodd.http.HttpResponse; import jodd.http.ProxyInfo; -import jodd.util.StringPool; -import me.chanjar.weixin.common.WxType; -import me.chanjar.weixin.common.error.WxError; +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 java.io.IOException; +import java.nio.charset.StandardCharsets; /** * . * * @author ecoolper - * @date 2017/5/4 + * created on 2017/5/4 */ public class JoddHttpSimpleGetRequestExecutor extends SimpleGetRequestExecutor { - public JoddHttpSimpleGetRequestExecutor(RequestHttp requestHttp) { + public JoddHttpSimpleGetRequestExecutor(RequestHttp requestHttp) { super(requestHttp); } @@ -39,15 +38,9 @@ public String execute(String uri, String queryParam, WxType wxType) throws WxErr } request.withConnectionProvider(requestHttp.getRequestHttpClient()); HttpResponse response = request.send(); - response.charset(StringPool.UTF_8); + response.charset(StandardCharsets.UTF_8.name()); - String responseContent = response.bodyText(); - - WxError error = WxError.fromJson(responseContent, wxType); - if (error.getErrorCode() != 0) { - throw new WxErrorException(error); - } - return responseContent; + return handleResponse(wxType, response.bodyText()); } } 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 75114d6a7d..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 @@ -4,23 +4,22 @@ import jodd.http.HttpRequest; import jodd.http.HttpResponse; import jodd.http.ProxyInfo; -import jodd.util.StringPool; -import me.chanjar.weixin.common.WxType; -import me.chanjar.weixin.common.error.WxError; +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 java.io.IOException; +import java.nio.charset.StandardCharsets; /** * . * * @author ecoolper - * @date 2017/5/4 + * created on 2017/5/4 */ public class JoddHttpSimplePostRequestExecutor extends SimplePostRequestExecutor { - public JoddHttpSimplePostRequestExecutor(RequestHttp requestHttp) { + public JoddHttpSimplePostRequestExecutor(RequestHttp requestHttp) { super(requestHttp); } @@ -35,26 +34,13 @@ public String execute(String uri, String postEntity, WxType wxType) throws WxErr } request.withConnectionProvider(provider); if (postEntity != null) { + request.contentType("application/json", "utf-8"); request.bodyText(postEntity); } HttpResponse response = request.send(); - response.charset(StringPool.UTF_8); + response.charset(StandardCharsets.UTF_8.name()); - String responseContent = response.bodyText(); - if (responseContent.isEmpty()) { - throw new WxErrorException(WxError.builder().errorCode(9999).errorMsg("无响应内容").build()); - } - - if (responseContent.startsWith("")) { - //xml格式输出直接返回 - return responseContent; - } - - WxError error = WxError.fromJson(responseContent, wxType); - if (error.getErrorCode() != 0) { - throw new WxErrorException(error); - } - return responseContent; + return this.handleResponse(wxType, response.bodyText()); } } diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/okhttp/DefaultOkHttpClientBuilder.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/okhttp/DefaultOkHttpClientBuilder.java new file mode 100644 index 0000000000..5ebe0ff1fa --- /dev/null +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/okhttp/DefaultOkHttpClientBuilder.java @@ -0,0 +1,263 @@ +package me.chanjar.weixin.common.util.http.okhttp; + +import lombok.Data; +import lombok.extern.slf4j.Slf4j; +import okhttp3.*; + +import javax.annotation.concurrent.NotThreadSafe; +import java.net.Proxy; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * @author wulang + **/ +@Slf4j +@Data +@NotThreadSafe +public class DefaultOkHttpClientBuilder implements OkHttpClientBuilder { + + private final AtomicBoolean prepared = new AtomicBoolean(false); + + /** + * 代理 + */ + private Proxy proxy; + + /** + * 授权 + */ + private Authenticator authenticator; + + /** + * 拦截器 + */ + private final List interceptorList = new ArrayList<>(); + + /** + * 请求调度管理 + */ + private Dispatcher dispatcher; + + /** + * 连接池 + */ + private ConnectionPool connectionPool; + + /** + * 监听网络请求过程 + */ + private EventListener.Factory eventListenerFactory; + + /** + * 是否支持失败重连 + */ + private Boolean retryOnConnectionFailure; + + /** + * 是否允许重定向操作 + */ + private Boolean followRedirects; + + /** + * 连接建立的超时时长 + */ + private Long connectTimeout; + + /** + * 连接建立的超时时间单位 + */ + private TimeUnit connectTimeUnit; + + /** + * 完整的请求过程超时时长 + */ + private Long callTimeout; + + /** + * 完整的请求过程超时时间单位 + */ + private TimeUnit callTimeUnit; + + /** + * 连接的IO读操作超时时长 + */ + private Long readTimeout; + + /** + * 连接的IO读操作超时时间单位 + */ + private TimeUnit readTimeUnit; + + /** + * 连接的IO写操作超时时长 + */ + private Long writeTimeout; + + /** + * 连接的IO写操作超时时间单位 + */ + private TimeUnit writeTimeUnit; + + /** + * ping的时间间隔 + */ + private Integer pingInterval; + + /** + * 持有client对象,仅初始化一次,避免多service实例的时候造成重复初始化的问题 + */ + private OkHttpClient okHttpClient; + + private DefaultOkHttpClientBuilder() { + + } + + public static DefaultOkHttpClientBuilder get() { + return DefaultOkHttpClientBuilder.SingletonHolder.INSTANCE; + } + + @Override + public OkHttpClient build() { + if (!prepared.get()) { + prepare(); + } + return this.okHttpClient; + } + + @Override + public OkHttpClientBuilder proxy(Proxy proxy) { + this.proxy = proxy; + return this; + } + + @Override + public OkHttpClientBuilder authenticator(Authenticator authenticator) { + this.authenticator = authenticator; + return this; + } + + @Override + public OkHttpClientBuilder addInterceptor(Interceptor interceptor) { + this.interceptorList.add(interceptor); + return this; + } + + @Override + public OkHttpClientBuilder setDispatcher(Dispatcher dispatcher) { + this.dispatcher = dispatcher; + return this; + } + + @Override + public OkHttpClientBuilder setConnectionPool(ConnectionPool connectionPool) { + this.connectionPool = connectionPool; + return this; + } + + @Override + public OkHttpClientBuilder setEventListenerFactory(EventListener.Factory eventListenerFactory) { + this.eventListenerFactory = eventListenerFactory; + return this; + } + + @Override + public OkHttpClientBuilder setRetryOnConnectionFailure(Boolean retryOnConnectionFailure) { + this.retryOnConnectionFailure = retryOnConnectionFailure; + return this; + } + + @Override + public OkHttpClientBuilder setFollowRedirects(Boolean followRedirects) { + this.followRedirects = followRedirects; + return this; + } + + @Override + public OkHttpClientBuilder connectTimeout(Long timeout, TimeUnit unit) { + this.connectTimeout = timeout; + this.connectTimeUnit = unit; + return this; + } + + @Override + public OkHttpClientBuilder callTimeout(Long timeout, TimeUnit unit) { + this.callTimeout = timeout; + this.callTimeUnit = unit; + return this; + } + + @Override + public OkHttpClientBuilder readTimeout(Long timeout, TimeUnit unit) { + this.readTimeout = timeout; + this.readTimeUnit = unit; + return this; + } + + @Override + public OkHttpClientBuilder writeTimeout(Long timeout, TimeUnit unit) { + this.writeTimeout = timeout; + this.writeTimeUnit = unit; + return this; + } + + @Override + public OkHttpClientBuilder setPingInterval(Integer pingInterval) { + this.pingInterval = pingInterval; + return this; + } + + private synchronized void prepare() { + if (prepared.get()) { + return; + } + OkHttpClient.Builder builder = new OkHttpClient.Builder(); + if (this.authenticator != null) { + builder.authenticator(this.authenticator); + } + if (this.proxy != null) { + builder.proxy(this.proxy); + } + for (Interceptor interceptor : this.interceptorList) { + builder.addInterceptor(interceptor); + } + if (this.dispatcher != null) { + builder.dispatcher(dispatcher); + } + if (this.connectionPool != null) { + builder.connectionPool(connectionPool); + } + if (this.eventListenerFactory != null) { + builder.eventListenerFactory(this.eventListenerFactory); + } + if (this.retryOnConnectionFailure != null) { + builder.setRetryOnConnectionFailure$okhttp(this.retryOnConnectionFailure); + } + if (this.followRedirects != null) { + builder.followRedirects(this.followRedirects); + } + if (this.connectTimeout != null && this.connectTimeUnit != null) { + builder.connectTimeout(this.connectTimeout, this.connectTimeUnit); + } + if (this.callTimeout != null && this.callTimeUnit != null) { + builder.callTimeout(this.callTimeout, this.callTimeUnit); + } + if (this.readTimeout != null && this.readTimeUnit != null) { + builder.readTimeout(this.readTimeout, this.readTimeUnit); + } + if (this.writeTimeout != null && this.writeTimeUnit != null) { + builder.writeTimeout(this.writeTimeout, this.writeTimeUnit); + } + if (this.pingInterval != null) { + builder.setPingInterval$okhttp(this.pingInterval); + } + this.okHttpClient = builder.build(); + prepared.set(true); + } + + private static class SingletonHolder { + private static final DefaultOkHttpClientBuilder INSTANCE = new DefaultOkHttpClientBuilder(); + } +} diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/okhttp/OkHttpClientBuilder.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/okhttp/OkHttpClientBuilder.java new file mode 100644 index 0000000000..0a2586a4bd --- /dev/null +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/okhttp/OkHttpClientBuilder.java @@ -0,0 +1,126 @@ +package me.chanjar.weixin.common.util.http.okhttp; + +import okhttp3.*; + +import java.net.Proxy; +import java.util.concurrent.TimeUnit; + +/** + * @author wulang + **/ +public interface OkHttpClientBuilder { + /** + * 构建OkHttpClient实例. + * + * @return OkHttpClient + */ + OkHttpClient build(); + + /** + * 代理 + * + * @param proxy Proxy + * @return OkHttpClientBuilder + */ + OkHttpClientBuilder proxy(Proxy proxy); + + /** + * 授权 + * + * @param authenticator Authenticator + * @return OkHttpClientBuilder + */ + OkHttpClientBuilder authenticator(Authenticator authenticator); + + /** + * 拦截器 + * + * @param interceptor Interceptor + * @return OkHttpClientBuilder + */ + OkHttpClientBuilder addInterceptor(Interceptor interceptor); + + /** + * 请求调度管理 + * + * @param dispatcher Dispatcher + * @return OkHttpClientBuilder + */ + OkHttpClientBuilder setDispatcher(Dispatcher dispatcher); + + /** + * 连接池 + * + * @param connectionPool ConnectionPool + * @return OkHttpClientBuilder + */ + OkHttpClientBuilder setConnectionPool(ConnectionPool connectionPool); + + /** + * 监听网络请求过程 + * + * @param eventListenerFactory EventListener + * @return OkHttpClientBuilder + */ + OkHttpClientBuilder setEventListenerFactory(EventListener.Factory eventListenerFactory); + + /** + * 是否支持失败重连 + * + * @param retryOnConnectionFailure retryOnConnectionFailure + * @return OkHttpClientBuilder + */ + OkHttpClientBuilder setRetryOnConnectionFailure(Boolean retryOnConnectionFailure); + + /** + * 是否允许重定向操作 + * + * @param followRedirects followRedirects + * @return OkHttpClientBuilder + */ + OkHttpClientBuilder setFollowRedirects(Boolean followRedirects); + + /** + * 连接建立的超时时间 + * + * @param timeout 时长 + * @param unit 时间单位 + * @return OkHttpClientBuilder + */ + OkHttpClientBuilder connectTimeout(Long timeout, TimeUnit unit); + + /** + * 完整的请求过程超时时间 + * + * @param timeout 时长 + * @param unit 时间单位 + * @return OkHttpClientBuilder + */ + OkHttpClientBuilder callTimeout(Long timeout, TimeUnit unit); + + /** + * 连接的IO读操作超时时间 + * + * @param timeout 时长 + * @param unit 时间单位 + * @return OkHttpClientBuilder + */ + OkHttpClientBuilder readTimeout(Long timeout, TimeUnit unit); + + /** + * 连接的IO写操作超时时间 + * + * @param timeout 时长 + * @param unit 时间单位 + * @return OkHttpClientBuilder + */ + OkHttpClientBuilder writeTimeout(Long timeout, TimeUnit unit); + + /** + * ping的时间间隔 + * + * @param pingInterval ping的时间间隔 + * @return OkHttpClientBuilder + */ + OkHttpClientBuilder setPingInterval(Integer pingInterval); +} diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/okhttp/OkHttpDnsClientBuilder.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/okhttp/OkHttpDnsClientBuilder.java new file mode 100644 index 0000000000..c346747527 --- /dev/null +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/okhttp/OkHttpDnsClientBuilder.java @@ -0,0 +1,257 @@ +package me.chanjar.weixin.common.util.http.okhttp; + +import okhttp3.*; + +import java.net.Proxy; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.TimeUnit; + +/** + * OkHttpClient 连接管理器 多一个DNS解析. + *

大部分代码拷贝自:DefaultOkHttpClientBuilder

+ * + * @author wulang + **/ +public class OkHttpDnsClientBuilder implements OkHttpClientBuilder { + + /** + * 代理 + */ + private Proxy proxy; + + /** + * 授权 + */ + private Authenticator authenticator; + + /** + * 拦截器 + */ + private final List interceptorList = new ArrayList<>(); + + /** + * 请求调度管理 + */ + private Dispatcher dispatcher; + + /** + * 连接池 + */ + private ConnectionPool connectionPool; + + /** + * 监听网络请求过程 + */ + private EventListener.Factory eventListenerFactory; + + /** + * 是否支持失败重连 + */ + private Boolean retryOnConnectionFailure; + + /** + * 是否允许重定向操作 + */ + private Boolean followRedirects; + + /** + * 连接建立的超时时长 + */ + private Long connectTimeout; + + /** + * 连接建立的超时时间单位 + */ + private TimeUnit connectTimeUnit; + + /** + * 完整的请求过程超时时长 + */ + private Long callTimeout; + + /** + * 完整的请求过程超时时间单位 + */ + private TimeUnit callTimeUnit; + + /** + * 连接的IO读操作超时时长 + */ + private Long readTimeout; + + /** + * 连接的IO读操作超时时间单位 + */ + private TimeUnit readTimeUnit; + + /** + * 连接的IO写操作超时时长 + */ + private Long writeTimeout; + + /** + * 连接的IO写操作超时时间单位 + */ + private TimeUnit writeTimeUnit; + + /** + * ping的时间间隔 + */ + private Integer pingInterval; + + private Dns dns; + + private OkHttpClient okHttpClient; + + private OkHttpDnsClientBuilder() { + + } + + public static OkHttpDnsClientBuilder get() { + return new OkHttpDnsClientBuilder(); + } + + public Dns getDns() { + return dns; + } + + public void setDns(Dns dns) { + this.dns = dns; + } + + @Override + public OkHttpClient build() { + prepare(); + return this.okHttpClient; + } + + @Override + public OkHttpClientBuilder proxy(Proxy proxy) { + this.proxy = proxy; + return this; + } + + @Override + public OkHttpClientBuilder authenticator(Authenticator authenticator) { + this.authenticator = authenticator; + return this; + } + + @Override + public OkHttpClientBuilder addInterceptor(Interceptor interceptor) { + this.interceptorList.add(interceptor); + return this; + } + + @Override + public OkHttpClientBuilder setDispatcher(Dispatcher dispatcher) { + this.dispatcher = dispatcher; + return this; + } + + @Override + public OkHttpClientBuilder setConnectionPool(ConnectionPool connectionPool) { + this.connectionPool = connectionPool; + return this; + } + + @Override + public OkHttpClientBuilder setEventListenerFactory(EventListener.Factory eventListenerFactory) { + this.eventListenerFactory = eventListenerFactory; + return this; + } + + @Override + public OkHttpClientBuilder setRetryOnConnectionFailure(Boolean retryOnConnectionFailure) { + this.retryOnConnectionFailure = retryOnConnectionFailure; + return this; + } + + @Override + public OkHttpClientBuilder setFollowRedirects(Boolean followRedirects) { + this.followRedirects = followRedirects; + return this; + } + + @Override + public OkHttpClientBuilder connectTimeout(Long timeout, TimeUnit unit) { + this.connectTimeout = timeout; + this.connectTimeUnit = unit; + return this; + } + + @Override + public OkHttpClientBuilder callTimeout(Long timeout, TimeUnit unit) { + this.callTimeout = timeout; + this.callTimeUnit = unit; + return this; + } + + @Override + public OkHttpClientBuilder readTimeout(Long timeout, TimeUnit unit) { + this.readTimeout = timeout; + this.readTimeUnit = unit; + return this; + } + + @Override + public OkHttpClientBuilder writeTimeout(Long timeout, TimeUnit unit) { + this.writeTimeout = timeout; + this.writeTimeUnit = unit; + return this; + } + + @Override + public OkHttpClientBuilder setPingInterval(Integer pingInterval) { + this.pingInterval = pingInterval; + return this; + } + + private synchronized void prepare() { + OkHttpClient.Builder builder = new OkHttpClient.Builder(); + if (this.authenticator != null) { + builder.authenticator(this.authenticator); + } + if (this.proxy != null) { + builder.proxy(this.proxy); + } + for (Interceptor interceptor : this.interceptorList) { + builder.addInterceptor(interceptor); + } + if (this.dispatcher != null) { + builder.dispatcher(dispatcher); + } + if (this.connectionPool != null) { + builder.connectionPool(connectionPool); + } + if (this.eventListenerFactory != null) { + builder.eventListenerFactory(this.eventListenerFactory); + } + if (this.retryOnConnectionFailure != null) { + builder.setRetryOnConnectionFailure$okhttp(this.retryOnConnectionFailure); + } + if (this.followRedirects != null) { + builder.followRedirects(this.followRedirects); + } + if (this.dns != null) { + builder.dns(this.dns); + } + if (this.connectTimeout != null && this.connectTimeUnit != null) { + builder.connectTimeout(this.connectTimeout, this.connectTimeUnit); + } + if (this.callTimeout != null && this.callTimeUnit != null) { + builder.callTimeout(this.callTimeout, this.callTimeUnit); + } + if (this.readTimeout != null && this.readTimeUnit != null) { + builder.readTimeout(this.readTimeout, this.readTimeUnit); + } + if (this.writeTimeout != null && this.writeTimeUnit != null) { + builder.writeTimeout(this.writeTimeout, this.writeTimeUnit); + } + if (this.pingInterval != null) { + builder.setPingInterval$okhttp(this.pingInterval); + } + this.okHttpClient = builder.build(); + } +} 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 729f1e186f..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 @@ -1,7 +1,7 @@ package me.chanjar.weixin.common.util.http.okhttp; import lombok.extern.slf4j.Slf4j; -import me.chanjar.weixin.common.WxType; +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.BaseMediaDownloadRequestExecutor; @@ -14,8 +14,6 @@ import okio.Okio; import org.apache.commons.io.FilenameUtils; import org.apache.commons.lang3.StringUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import java.io.File; import java.io.IOException; @@ -23,11 +21,11 @@ /** *. * @author ecoolper - * @date 2017/5/5 + * created on 2017/5/5 */ @Slf4j public class OkHttpMediaDownloadRequestExecutor extends BaseMediaDownloadRequestExecutor { - public OkHttpMediaDownloadRequestExecutor(RequestHttp requestHttp, File tmpDirFile) { + public OkHttpMediaDownloadRequestExecutor(RequestHttp requestHttp, File tmpDirFile) { super(requestHttp, tmpDirFile); } @@ -53,13 +51,18 @@ 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; } + String baseName = FilenameUtils.getBaseName(fileName); + if (StringUtils.isBlank(fileName) || baseName.length() < 3) { + baseName = String.valueOf(System.currentTimeMillis()); + } + File file = File.createTempFile( - FilenameUtils.getBaseName(fileName), "." + FilenameUtils.getExtension(fileName), super.tmpDirFile + baseName, "." + FilenameUtils.getExtension(fileName), super.tmpDirFile ); try (BufferedSink sink = Okio.buffer(Okio.sink(file))) { 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 new file mode 100644 index 0000000000..c30cc619aa --- /dev/null +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/okhttp/OkHttpMediaInputStreamUploadRequestExecutor.java @@ -0,0 +1,56 @@ +package me.chanjar.weixin.common.util.http.okhttp; + +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 okhttp3.*; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; + +/** + * 文件输入流上传. + * + * @author meiqin.zhou91@gmail.com + * created on 2022/02/15 + */ +public class OkHttpMediaInputStreamUploadRequestExecutor extends MediaInputStreamUploadRequestExecutor { + public OkHttpMediaInputStreamUploadRequestExecutor(RequestHttp requestHttp) { + super(requestHttp); + } + + @Override + public WxMediaUploadResult execute(String uri, InputStreamData data, WxType wxType) throws WxErrorException, IOException { + + RequestBody body = new MultipartBody.Builder() + .setType(MediaType.parse("multipart/form-data")) + .addFormDataPart("media", data.getFilename(), RequestBody.create(this.toByteArray(data.getInputStream()), MediaType.parse("application/octet-stream"))) + .build(); + Request request = new Request.Builder().url(uri).post(body).build(); + + Response response = requestHttp.getRequestHttpClient().newCall(request).execute(); + String responseContent = response.body().string(); + WxError error = WxError.fromJson(responseContent, wxType); + if (error.getErrorCode() != 0) { + throw new WxErrorException(error); + } + return WxMediaUploadResult.fromJson(responseContent); + } + + + public byte[] toByteArray(InputStream input) throws IOException { + try (ByteArrayOutputStream output = new ByteArrayOutputStream()) { + byte[] buffer = new byte[4096]; + int n = 0; + while (-1 != (n = input.read(buffer))) { + output.write(buffer, 0, n); + } + return output.toByteArray(); + } + } +} 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 2fef1f93ca..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 @@ -1,6 +1,6 @@ package me.chanjar.weixin.common.util.http.okhttp; -import me.chanjar.weixin.common.WxType; +import me.chanjar.weixin.common.enums.WxType; import me.chanjar.weixin.common.bean.result.WxMediaUploadResult; import me.chanjar.weixin.common.error.WxError; import me.chanjar.weixin.common.error.WxErrorException; @@ -15,10 +15,10 @@ * . * * @author ecoolper - * @date 2017/5/5 + * 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 new file mode 100644 index 0000000000..a2c78f423b --- /dev/null +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/okhttp/OkHttpMinishopMediaUploadRequestCustomizeExecutor.java @@ -0,0 +1,58 @@ +package me.chanjar.weixin.common.util.http.okhttp; + +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 okhttp3.*; + +import java.io.File; +import java.io.IOException; + +/** + * @author liming1019 + * created on 2021/8/10 + */ +@Slf4j +public class OkHttpMinishopMediaUploadRequestCustomizeExecutor extends MinishopUploadRequestCustomizeExecutor { + public OkHttpMinishopMediaUploadRequestCustomizeExecutor(RequestHttp requestHttp, String respType, String imgUrl) { + super(requestHttp, respType, imgUrl); + } + + @Override + public WxMinishopImageUploadCustomizeResult execute(String uri, File file, WxType wxType) throws WxErrorException, IOException { + + RequestBody body = null; + if (this.uploadType.equals("0")) { + body = new MultipartBody.Builder() + .setType(MediaType.parse("multipart/form-data")) + .addFormDataPart("resp_type", this.respType) + .addFormDataPart("upload_type", this.uploadType) + .addFormDataPart("media", file.getName(), RequestBody.create(MediaType.parse("application/octet-stream"), file)) + .build(); + } + else { + body = new MultipartBody.Builder() + .setType(MediaType.parse("multipart/form-data")) + .addFormDataPart("resp_type", this.respType) + .addFormDataPart("upload_type", this.uploadType) + .addFormDataPart("img_url", this.imgUrl) + .build(); + } + Request request = new Request.Builder().url(uri).post(body).build(); + + Response response = requestHttp.getRequestHttpClient().newCall(request).execute(); + String responseContent = response.body().string(); + 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/okhttp/OkHttpMinishopMediaUploadRequestExecutor.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/okhttp/OkHttpMinishopMediaUploadRequestExecutor.java new file mode 100644 index 0000000000..f2df3c7e73 --- /dev/null +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/okhttp/OkHttpMinishopMediaUploadRequestExecutor.java @@ -0,0 +1,49 @@ +package me.chanjar.weixin.common.util.http.okhttp; + +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 okhttp3.*; + +import java.io.File; +import java.io.IOException; + +/** + * . + * + * @author ecoolper + * created on 2017/5/5 + */ +@Slf4j +public class OkHttpMinishopMediaUploadRequestExecutor extends MinishopUploadRequestExecutor { + public OkHttpMinishopMediaUploadRequestExecutor(RequestHttp requestHttp) { + super(requestHttp); + } + + @Override + public WxMinishopImageUploadResult execute(String uri, File file, WxType wxType) throws WxErrorException, IOException { + + RequestBody body = new MultipartBody.Builder() + .setType(MediaType.parse("multipart/form-data")) + .addFormDataPart("media", + file.getName(), + RequestBody.create(MediaType.parse("application/octet-stream"), file)) + .build(); + Request request = new Request.Builder().url(uri).post(body).build(); + + Response response = requestHttp.getRequestHttpClient().newCall(request).execute(); + String responseContent = response.body().string(); + 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/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 dbaa27c544..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 @@ -1,7 +1,6 @@ package me.chanjar.weixin.common.util.http.okhttp; -import me.chanjar.weixin.common.WxType; -import me.chanjar.weixin.common.error.WxError; +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; @@ -12,13 +11,13 @@ import java.io.IOException; /** - * . + * * * @author ecoolper - * @date 2017/5/4 + * created on 2017/5/4 */ public class OkHttpSimpleGetRequestExecutor extends SimpleGetRequestExecutor { - public OkHttpSimpleGetRequestExecutor(RequestHttp requestHttp) { + public OkHttpSimpleGetRequestExecutor(RequestHttp requestHttp) { super(requestHttp); } @@ -35,12 +34,7 @@ public String execute(String uri, String queryParam, WxType wxType) throws WxErr OkHttpClient client = requestHttp.getRequestHttpClient(); Request request = new Request.Builder().url(uri).build(); Response response = client.newCall(request).execute(); - String responseContent = response.body().string(); - WxError error = WxError.fromJson(responseContent, wxType); - if (error.getErrorCode() != 0) { - throw new WxErrorException(error); - } - return responseContent; + return this.handleResponse(wxType, response.body().string()); } } 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 3be6152055..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 @@ -1,39 +1,33 @@ package me.chanjar.weixin.common.util.http.okhttp; import lombok.extern.slf4j.Slf4j; -import me.chanjar.weixin.common.WxType; -import me.chanjar.weixin.common.error.WxError; +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 okhttp3.*; import java.io.IOException; +import java.util.Objects; /** * . * * @author ecoolper - * @date 2017/5/4 + * created on 2017/5/4 */ @Slf4j public class OkHttpSimplePostRequestExecutor extends SimplePostRequestExecutor { - public OkHttpSimplePostRequestExecutor(RequestHttp requestHttp) { + public OkHttpSimplePostRequestExecutor(RequestHttp requestHttp) { super(requestHttp); } @Override public String execute(String uri, String postEntity, WxType wxType) throws WxErrorException, IOException { - RequestBody body = RequestBody.create(MediaType.parse("text/plain; charset=utf-8"), postEntity); + RequestBody body = RequestBody.Companion.create(postEntity, MediaType.parse("application/json; charset=utf-8")); Request request = new Request.Builder().url(uri).post(body).build(); Response response = requestHttp.getRequestHttpClient().newCall(request).execute(); - String responseContent = response.body().string(); - WxError error = WxError.fromJson(responseContent, wxType); - if (error.getErrorCode() != 0) { - throw new WxErrorException(error); - } - - return responseContent; + return this.handleResponse(wxType, Objects.requireNonNull(response.body()).string()); } } diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/json/GsonHelper.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/json/GsonHelper.java index 882853945a..0d807402ac 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/json/GsonHelper.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/json/GsonHelper.java @@ -4,6 +4,7 @@ import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonObject; +import me.chanjar.weixin.common.error.WxRuntimeException; import java.util.List; @@ -151,4 +152,54 @@ public static Long[] getLongArray(JsonObject o, String string) { public static JsonArray getAsJsonArray(JsonElement element) { return element == null ? null : element.getAsJsonArray(); } + + /** + * 快速构建JsonObject对象,批量添加一堆属性 + * + * @param keyOrValue 包含key或value的数组 + * @return JsonObject对象. + */ + public static JsonObject buildJsonObject(Object... keyOrValue) { + JsonObject result = new JsonObject(); + put(result, keyOrValue); + return result; + } + + /** + * 批量向JsonObject对象中添加属性 + * + * @param jsonObject 原始JsonObject对象 + * @param keyOrValue 包含key或value的数组 + */ + public static void put(JsonObject jsonObject, Object... keyOrValue) { + if (keyOrValue.length % 2 == 1) { + throw new WxRuntimeException("参数个数必须为偶数"); + } + + for (int i = 0; i < keyOrValue.length / 2; i++) { + final Object key = keyOrValue[2 * i]; + final Object value = keyOrValue[2 * i + 1]; + if (value == null) { + jsonObject.add(key.toString(), null); + continue; + } + + if (value instanceof Boolean) { + jsonObject.addProperty(key.toString(), (Boolean) value); + } else if (value instanceof Character) { + jsonObject.addProperty(key.toString(), (Character) value); + } else if (value instanceof Number) { + jsonObject.addProperty(key.toString(), (Number) value); + } else if (value instanceof JsonElement) { + jsonObject.add(key.toString(), (JsonElement) value); + } else if (value instanceof List) { + JsonArray array = new JsonArray(); + ((List) value).forEach(a -> array.add(a.toString())); + jsonObject.add(key.toString(), array); + } else { + jsonObject.add(key.toString(), WxGsonBuilder.create().toJsonTree(value)); + } + } + + } } 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 new file mode 100644 index 0000000000..caa07d0eaf --- /dev/null +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/json/GsonParser.java @@ -0,0 +1,25 @@ +package me.chanjar.weixin.common.util.json; + +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import com.google.gson.stream.JsonReader; + +import java.io.Reader; + +/** + * @author niefy + */ +public class GsonParser { + + public static JsonObject parse(String json) { + return new JsonParser().parse(json).getAsJsonObject(); + } + + public static JsonObject parse(Reader json) { + return new JsonParser().parse(json).getAsJsonObject(); + } + + public static JsonObject parse(JsonReader json) { + return new JsonParser().parse(json).getAsJsonObject(); + } +} diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/json/WxErrorAdapter.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/json/WxErrorAdapter.java index 0ea52b9a86..c9301a7750 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/json/WxErrorAdapter.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/json/WxErrorAdapter.java @@ -1,6 +1,7 @@ package me.chanjar.weixin.common.util.json; import com.google.gson.*; +import me.chanjar.weixin.common.api.WxConsts; import me.chanjar.weixin.common.error.WxError; import java.lang.reflect.Type; @@ -16,8 +17,8 @@ public WxError deserialize(JsonElement json, Type typeOfT, JsonDeserializationCo WxError.WxErrorBuilder errorBuilder = WxError.builder(); JsonObject wxErrorJsonObject = json.getAsJsonObject(); - if (wxErrorJsonObject.get("errcode") != null && !wxErrorJsonObject.get("errcode").isJsonNull()) { - errorBuilder.errorCode(GsonHelper.getAsPrimitiveInt(wxErrorJsonObject.get("errcode"))); + if (wxErrorJsonObject.get(WxConsts.ERR_CODE) != null && !wxErrorJsonObject.get(WxConsts.ERR_CODE).isJsonNull()) { + errorBuilder.errorCode(GsonHelper.getAsPrimitiveInt(wxErrorJsonObject.get(WxConsts.ERR_CODE))); } if (wxErrorJsonObject.get("errmsg") != null && !wxErrorJsonObject.get("errmsg").isJsonNull()) { errorBuilder.errorMsg(GsonHelper.getAsString(wxErrorJsonObject.get("errmsg"))); 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 b52bad9531..8f3dafe48a 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/json/WxGsonBuilder.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/json/WxGsonBuilder.java @@ -1,5 +1,7 @@ package me.chanjar.weixin.common.util.json; +import com.google.gson.ExclusionStrategy; +import com.google.gson.FieldAttributes; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import me.chanjar.weixin.common.bean.WxAccessToken; @@ -7,14 +9,18 @@ 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; /** * . * @author chanjarster */ public class WxGsonBuilder { - private static final GsonBuilder INSTANCE = new GsonBuilder(); + private static volatile Gson GSON_INSTANCE; static { INSTANCE.disableHtmlEscaping(); @@ -24,10 +30,33 @@ public class WxGsonBuilder { INSTANCE.registerTypeAdapter(WxMediaUploadResult.class, new WxMediaUploadResultAdapter()); INSTANCE.registerTypeAdapter(WxNetCheckResult.class, new WxNetCheckResultGsonAdapter()); + INSTANCE.setExclusionStrategies(new ExclusionStrategy() { + @Override + public boolean shouldSkipField(FieldAttributes fieldAttributes) { + return false; + } + + @Override + public boolean shouldSkipClass(Class aClass) { + return aClass == File.class || aClass == ApacheHttpClientBuilder.class; + } + }); } + /** + * 创建Gson实例 + * + * @return Gson实例 + */ public static Gson create() { - return INSTANCE.create(); + if (Objects.isNull(GSON_INSTANCE)) { + synchronized (INSTANCE) { + if (Objects.isNull(GSON_INSTANCE)) { + GSON_INSTANCE = INSTANCE.create(); + } + } + } + return GSON_INSTANCE; } } diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/json/WxMenuGsonAdapter.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/json/WxMenuGsonAdapter.java index 31c3c0204c..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,95 +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("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; @@ -117,13 +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.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/JedisDistributedLock.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/locks/JedisDistributedLock.java new file mode 100644 index 0000000000..115777d1c9 --- /dev/null +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/locks/JedisDistributedLock.java @@ -0,0 +1,76 @@ +package me.chanjar.weixin.common.util.locks; + +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.Lock; + +import com.github.jedis.lock.JedisLock; +import me.chanjar.weixin.common.error.WxRuntimeException; +import redis.clients.jedis.Jedis; +import redis.clients.jedis.util.Pool; + +/** + * JedisPool 分布式锁 + * @deprecated 不建议使用jedis-lock这个过期组件,不可靠 + * + * @author 007 + */ +@Deprecated +public class JedisDistributedLock implements Lock { + private final Pool jedisPool; + private final JedisLock lock; + + public JedisDistributedLock(Pool jedisPool, String key){ + this.jedisPool = jedisPool; + this.lock = new JedisLock(key); + } + + @Override + public void lock() { + try (Jedis jedis = jedisPool.getResource()) { + if (!lock.acquire(jedis)) { + throw new WxRuntimeException("acquire timeouted"); + } + } catch (InterruptedException e) { + throw new WxRuntimeException("lock failed", e); + } + } + + @Override + public void lockInterruptibly() throws InterruptedException { + try (Jedis jedis = jedisPool.getResource()) { + if (!lock.acquire(jedis)) { + throw new WxRuntimeException("acquire timeouted"); + } + } + } + + @Override + public boolean tryLock() { + try (Jedis jedis = jedisPool.getResource()) { + return lock.acquire(jedis); + } catch (InterruptedException e) { + throw new WxRuntimeException("lock failed", e); + } + } + + @Override + public boolean tryLock(long time, TimeUnit unit) throws InterruptedException { + try (Jedis jedis = jedisPool.getResource()) { + return lock.acquire(jedis); + } + } + + @Override + public void unlock() { + try (Jedis jedis = jedisPool.getResource()) { + lock.release(jedis); + } + } + + @Override + public Condition newCondition() { + throw new WxRuntimeException("unsupported method"); + } + +} 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 new file mode 100644 index 0000000000..3f5ce4d692 --- /dev/null +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/locks/RedisTemplateSimpleDistributedLock.java @@ -0,0 +1,115 @@ +package me.chanjar.weixin.common.util.locks; + +import lombok.Getter; +import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.data.redis.core.script.DefaultRedisScript; +import org.springframework.data.redis.core.script.RedisScript; + +import java.util.Collections; +import java.util.UUID; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.Lock; + +/** + * 实现简单的redis分布式锁, 支持重入, 不是红锁 + * + * @see reids distlock + */ +public class RedisTemplateSimpleDistributedLock implements Lock { + + @Getter + private final StringRedisTemplate redisTemplate; + @Getter + private final String key; + @Getter + private final int leaseMilliseconds; + + private final ThreadLocal valueThreadLocal = new ThreadLocal<>(); + + public RedisTemplateSimpleDistributedLock( StringRedisTemplate redisTemplate, int leaseMilliseconds) { + this(redisTemplate, "lock:" + UUID.randomUUID().toString(), leaseMilliseconds); + } + + public RedisTemplateSimpleDistributedLock( StringRedisTemplate redisTemplate, String key, int leaseMilliseconds) { + if (leaseMilliseconds <= 0) { + throw new IllegalArgumentException("Parameter 'leaseMilliseconds' must grate then 0: " + leaseMilliseconds); + } + this.redisTemplate = redisTemplate; + this.key = key; + this.leaseMilliseconds = leaseMilliseconds; + } + + @Override + public void lock() { + while (!tryLock()) { + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + // Ignore + } + } + } + + @Override + public void lockInterruptibly() throws InterruptedException { + while (!tryLock()) { + Thread.sleep(1000); + } + } + + @Override + public boolean tryLock() { + String value = valueThreadLocal.get(); + if (value == null || value.isEmpty()) { + value = UUID.randomUUID().toString(); + valueThreadLocal.set(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 + public boolean tryLock(long time, TimeUnit unit) throws InterruptedException { + long waitMs = unit.toMillis(time); + boolean locked = tryLock(); + while (!locked && waitMs > 0) { + long sleep = waitMs < 1000 ? waitMs : 1000; + Thread.sleep(sleep); + waitMs -= sleep; + locked = tryLock(); + } + return locked; + } + + @Override + 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, Collections.singletonList(key), valueThreadLocal.get()); + valueThreadLocal.remove(); + } + } + + @Override + public Condition newCondition() { + throw new UnsupportedOperationException(); + } + + /** + * 获取当前锁的值 + * return 返回null意味着没有加锁, 但是返回非null值并不以为着当前加锁成功(redis中key可能自动过期) + */ + public String getLockSecretValue() { + return valueThreadLocal.get(); + } +} 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 3a82b213ca..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 @@ -9,7 +9,7 @@ * Integer型数组转换器. * * @author Binary Wang - * @date 2019-08-22 + * created on 2019-08-22 */ public class IntegerArrayConverter extends StringConverter { @Override @@ -24,6 +24,11 @@ public String toString(Object obj) { @Override public Object fromString(String str) { + + if (str == null || str.isEmpty()) { + return null; + } + final Iterable iterable = Splitter.on(",").split(str); final String[] strings = Iterables.toArray(iterable, String.class); Integer[] result = new Integer[strings.length]; diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/xml/LongArrayConverter.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/xml/LongArrayConverter.java index a383c59674..ca5f8ac9a4 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/xml/LongArrayConverter.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/xml/LongArrayConverter.java @@ -9,7 +9,7 @@ * Long型数组转换器. * * @author Binary Wang - * @date 2019-08-22 + * created on 2019-08-22 */ public class LongArrayConverter extends StringConverter { @Override diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/xml/StringArrayConverter.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/xml/StringArrayConverter.java new file mode 100644 index 0000000000..44d2926f42 --- /dev/null +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/xml/StringArrayConverter.java @@ -0,0 +1,30 @@ +package me.chanjar.weixin.common.util.xml; + +import com.google.common.base.Joiner; +import com.google.common.base.Splitter; +import com.google.common.collect.Iterables; +import com.thoughtworks.xstream.converters.basic.StringConverter; + + +/** + * String 数组转换 + * @author chily.lin + */ +public class StringArrayConverter extends StringConverter { + @Override + public boolean canConvert(Class type) { + return type == String[].class; + } + + @Override + public String toString(Object obj) { + return ""; + } + + @Override + public Object fromString(String str) { + final Iterable iterable = Splitter.on(",").split(str); + String[] results = Iterables.toArray(iterable, String.class); + return results; + } +} diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/xml/XStreamInitializer.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/xml/XStreamInitializer.java index 639fcf08d1..51cd1e980c 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/xml/XStreamInitializer.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/xml/XStreamInitializer.java @@ -1,15 +1,36 @@ package me.chanjar.weixin.common.util.xml; -import java.io.Writer; - import com.thoughtworks.xstream.XStream; +import com.thoughtworks.xstream.converters.basic.*; +import com.thoughtworks.xstream.converters.collections.CollectionConverter; import com.thoughtworks.xstream.converters.reflection.PureJavaReflectionProvider; +import com.thoughtworks.xstream.converters.reflection.ReflectionConverter; import com.thoughtworks.xstream.core.util.QuickWriter; import com.thoughtworks.xstream.io.HierarchicalStreamWriter; import com.thoughtworks.xstream.io.xml.PrettyPrintWriter; import com.thoughtworks.xstream.io.xml.XppDriver; +import com.thoughtworks.xstream.security.NoTypePermission; +import com.thoughtworks.xstream.security.WildcardTypePermission; +import java.io.Writer; +/** + * The type X stream initializer. + * + * @author Daniel Qian + */ public class XStreamInitializer { + + public static ClassLoader classLoader; + + /** + * 设置类加载器 + * + * @param classLoaderInfo 类加载器 + */ + public static void setClassLoader(ClassLoader classLoaderInfo) { + classLoader = classLoaderInfo; + } + private static final XppDriver XPP_DRIVER = new XppDriver() { @Override public HierarchicalStreamWriter createWriter(Writer out) { @@ -18,6 +39,8 @@ public HierarchicalStreamWriter createWriter(Writer out) { private static final String SUFFIX_CDATA = "]]>"; private static final String PREFIX_MEDIA_ID = ""; private static final String SUFFIX_MEDIA_ID = ""; + private static final String PREFIX_REPLACE_NAME = ""; + private static final String SUFFIX_REPLACE_NAME = ""; @Override protected void writeText(QuickWriter writer, String text) { @@ -25,6 +48,8 @@ protected void writeText(QuickWriter writer, String text) { writer.write(text); } else if (text.startsWith(PREFIX_MEDIA_ID) && text.endsWith(SUFFIX_MEDIA_ID)) { writer.write(text); + } else if (text.startsWith(PREFIX_REPLACE_NAME) && text.endsWith(SUFFIX_REPLACE_NAME)){ + writer.write(text); } else { super.writeText(writer, text); } @@ -40,16 +65,43 @@ public String encodeNode(String name) { } }; + /** + * Gets instance. + * + * @return the instance + */ public static XStream getInstance() { - XStream xstream = new XStream(new PureJavaReflectionProvider(), XPP_DRIVER); + XStream xstream = new XStream(new PureJavaReflectionProvider(), XPP_DRIVER) { + // only register the converters we need; other converters generate a private access warning in the console on Java9+... + @Override + protected void setupConverters() { + registerConverter(new NullConverter(), PRIORITY_VERY_HIGH); + registerConverter(new IntConverter(), PRIORITY_NORMAL); + registerConverter(new FloatConverter(), PRIORITY_NORMAL); + registerConverter(new DoubleConverter(), PRIORITY_NORMAL); + registerConverter(new LongConverter(), PRIORITY_NORMAL); + registerConverter(new ShortConverter(), PRIORITY_NORMAL); + registerConverter(new BooleanConverter(), PRIORITY_NORMAL); + registerConverter(new ByteConverter(), PRIORITY_NORMAL); + registerConverter(new StringConverter(), PRIORITY_NORMAL); + registerConverter(new DateConverter(), PRIORITY_NORMAL); + registerConverter(new CollectionConverter(getMapper()), PRIORITY_NORMAL); + registerConverter(new ReflectionConverter(getMapper(), getReflectionProvider()), PRIORITY_VERY_LOW); + } + }; xstream.ignoreUnknownElements(); xstream.setMode(XStream.NO_REFERENCES); - XStream.setupDefaultSecurity(xstream); - xstream.allowTypesByWildcard(new String[]{ - "me.chanjar.weixin.**", "cn.binarywang.wx.**", "com.github.binarywang.**" - }); + xstream.autodetectAnnotations(true); - xstream.setClassLoader(Thread.currentThread().getContextClassLoader()); + // setup proper security by limiting which classes can be loaded by XStream + xstream.addPermission(NoTypePermission.NONE); + xstream.addPermission(new WildcardTypePermission(new String[]{ + "me.chanjar.weixin.**", "cn.binarywang.wx.**", "com.github.binarywang.**" + })); + if (null == classLoader) { + classLoader = Thread.currentThread().getContextClassLoader(); + } + xstream.setClassLoader(classLoader); return xstream; } diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/xml/XStreamReplaceNameConverter.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/xml/XStreamReplaceNameConverter.java new file mode 100644 index 0000000000..a136934383 --- /dev/null +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/xml/XStreamReplaceNameConverter.java @@ -0,0 +1,8 @@ +package me.chanjar.weixin.common.util.xml; + +public class XStreamReplaceNameConverter extends XStreamCDataConverter { + @Override + public String toString(Object obj) { + return "" + super.toString(obj) + ""; + } +} 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/api/WxMessageInMemoryDuplicateCheckerSingletonTest.java b/weixin-java-common/src/test/java/me/chanjar/weixin/common/api/WxMessageInMemoryDuplicateCheckerSingletonTest.java new file mode 100644 index 0000000000..df1ee7e2bc --- /dev/null +++ b/weixin-java-common/src/test/java/me/chanjar/weixin/common/api/WxMessageInMemoryDuplicateCheckerSingletonTest.java @@ -0,0 +1,44 @@ +package me.chanjar.weixin.common.api; + +import org.testng.annotations.Test; + +import java.util.concurrent.TimeUnit; + +import static org.testng.Assert.assertFalse; +import static org.testng.Assert.assertTrue; + +/** + * @author jiangby + * @version 1.0 + * created on 2022/5/26 1:46 + */ +@Test +public class WxMessageInMemoryDuplicateCheckerSingletonTest { + + private static WxMessageInMemoryDuplicateCheckerSingleton checkerSingleton = WxMessageInMemoryDuplicateCheckerSingleton.getInstance(); + + public void test() throws InterruptedException { + Long[] msgIds = new Long[]{1L, 2L, 3L, 4L, 5L, 6L, 7L, 8L}; + + // 第一次检查 + for (Long msgId : msgIds) { + boolean result = checkerSingleton.isDuplicate(String.valueOf(msgId)); + assertFalse(result); + } + + // 初始化后1S进行检查 每五秒检查一次,过期时间为15秒,过15秒再检查 + TimeUnit.SECONDS.sleep(15); + for (Long msgId : msgIds) { + boolean result = checkerSingleton.isDuplicate(String.valueOf(msgId)); + assertTrue(result); + } + + // 过6秒再检查 + TimeUnit.SECONDS.sleep(6); + for (Long msgId : msgIds) { + boolean result = checkerSingleton.isDuplicate(String.valueOf(msgId)); + assertFalse(result); + } + + } +} diff --git a/weixin-java-common/src/test/java/me/chanjar/weixin/common/api/WxMessageInMemoryDuplicateCheckerTest.java b/weixin-java-common/src/test/java/me/chanjar/weixin/common/api/WxMessageInMemoryDuplicateCheckerTest.java index 6f98b3d986..fd8819272c 100644 --- a/weixin-java-common/src/test/java/me/chanjar/weixin/common/api/WxMessageInMemoryDuplicateCheckerTest.java +++ b/weixin-java-common/src/test/java/me/chanjar/weixin/common/api/WxMessageInMemoryDuplicateCheckerTest.java @@ -1,6 +1,5 @@ package me.chanjar.weixin.common.api; -import org.testng.Assert; import org.testng.annotations.Test; import java.util.concurrent.TimeUnit; diff --git a/weixin-java-common/src/test/java/me/chanjar/weixin/common/api/WxMessageInRedisDuplicateCheckerTest.java b/weixin-java-common/src/test/java/me/chanjar/weixin/common/api/WxMessageInRedisDuplicateCheckerTest.java new file mode 100644 index 0000000000..382618862a --- /dev/null +++ b/weixin-java-common/src/test/java/me/chanjar/weixin/common/api/WxMessageInRedisDuplicateCheckerTest.java @@ -0,0 +1,57 @@ +package me.chanjar.weixin.common.api; + +import org.redisson.Redisson; +import org.redisson.api.RedissonClient; +import org.redisson.config.Config; +import org.redisson.config.TransportMode; +import org.testng.annotations.BeforeTest; +import org.testng.annotations.Test; + +import java.util.concurrent.TimeUnit; + +import static org.testng.Assert.assertFalse; +import static org.testng.Assert.assertTrue; + +@Test +public class WxMessageInRedisDuplicateCheckerTest { + + private RedissonClient redissonClient; + + @BeforeTest + public void init() { + Config config = new Config(); + config.useSingleServer().setAddress("redis://127.0.0.1:6379"); + config.setTransportMode(TransportMode.NIO); + this.redissonClient = Redisson.create(config); + checker = new WxMessageInRedisDuplicateChecker(redissonClient); + checker.setExpire(2); + } + + private WxMessageInRedisDuplicateChecker checker; + + public void test() throws InterruptedException { + Long[] msgIds = new Long[]{1L, 2L, 3L, 4L, 5L, 6L, 7L, 8L}; + + // 第一次检查 + for (Long msgId : msgIds) { + boolean result = checker.isDuplicate(String.valueOf(msgId)); + assertFalse(result); + } + + // 过1秒再检查 + TimeUnit.SECONDS.sleep(1); + for (Long msgId : msgIds) { + boolean result = checker.isDuplicate(String.valueOf(msgId)); + assertTrue(result); + } + + // 过1.5秒再检查 + TimeUnit.MILLISECONDS.sleep(1500L); + for (Long msgId : msgIds) { + boolean result = checker.isDuplicate(String.valueOf(msgId)); + assertFalse(result); + } + + } + +} diff --git a/weixin-java-common/src/test/java/me/chanjar/weixin/common/bean/WxNetCheckResultTest.java b/weixin-java-common/src/test/java/me/chanjar/weixin/common/bean/WxNetCheckResultTest.java new file mode 100644 index 0000000000..049b28227f --- /dev/null +++ b/weixin-java-common/src/test/java/me/chanjar/weixin/common/bean/WxNetCheckResultTest.java @@ -0,0 +1,58 @@ +package me.chanjar.weixin.common.bean; + +import org.testng.Assert; +import org.testng.annotations.Test; + +/** + * + * @author Binary Wang + * created on 2020-06-06 + */ +public class WxNetCheckResultTest { + + @Test + public void testFromJson() { + String json = "{\n" + + " \"dns\": [\n" + + " {\n" + + " \"ip\": \"111.161.64.40\", \n" + + " \"real_operator\": \"UNICOM\"\n" + + " }, \n" + + " {\n" + + " \"ip\": \"111.161.64.48\", \n" + + " \"real_operator\": \"UNICOM\"\n" + + " }\n" + + " ], \n" + + " \"ping\": [\n" + + " {\n" + + " \"ip\": \"111.161.64.40\", \n" + + " \"from_operator\": \"UNICOM\"," + + " \"package_loss\": \"0%\", \n" + + " \"time\": \"23.079ms\"\n" + + " }, \n" + + " {\n" + + " \"ip\": \"111.161.64.48\", \n" + + " \"from_operator\": \"UNICOM\", \n" + + " \"package_loss\": \"0%\", \n" + + " \"time\": \"21.434ms\"\n" + + " }\n" + + " ]\n" + + "}"; + WxNetCheckResult result = WxNetCheckResult.fromJson(json); + Assert.assertNotNull(result); + Assert.assertNotNull(result.getDnsInfos()); + + WxNetCheckResult.WxNetCheckDnsInfo dnsInfo = new WxNetCheckResult.WxNetCheckDnsInfo(); + dnsInfo.setIp("111.161.64.40"); + dnsInfo.setRealOperator("UNICOM"); + Assert.assertEquals(result.getDnsInfos().get(0), dnsInfo); + + WxNetCheckResult.WxNetCheckPingInfo pingInfo = new WxNetCheckResult.WxNetCheckPingInfo(); + pingInfo.setTime("21.434ms"); + pingInfo.setFromOperator("UNICOM"); + pingInfo.setIp("111.161.64.48"); + pingInfo.setPackageLoss("0%"); + Assert.assertEquals(result.getPingInfos().get(1), pingInfo); + + } +} diff --git a/weixin-java-common/src/test/java/me/chanjar/weixin/common/error/WxErrorTest.java b/weixin-java-common/src/test/java/me/chanjar/weixin/common/error/WxErrorTest.java index 79b80e63aa..456a58ad76 100644 --- a/weixin-java-common/src/test/java/me/chanjar/weixin/common/error/WxErrorTest.java +++ b/weixin-java-common/src/test/java/me/chanjar/weixin/common/error/WxErrorTest.java @@ -1,6 +1,6 @@ package me.chanjar.weixin.common.error; -import me.chanjar.weixin.common.WxType; +import me.chanjar.weixin.common.enums.WxType; import org.testng.annotations.Test; import static org.testng.Assert.assertEquals; diff --git a/weixin-java-common/src/test/java/me/chanjar/weixin/common/redis/CommonWxRedisOpsTest.java b/weixin-java-common/src/test/java/me/chanjar/weixin/common/redis/CommonWxRedisOpsTest.java new file mode 100644 index 0000000000..fb53c8c4b6 --- /dev/null +++ b/weixin-java-common/src/test/java/me/chanjar/weixin/common/redis/CommonWxRedisOpsTest.java @@ -0,0 +1,62 @@ +package me.chanjar.weixin.common.redis; + +import org.testng.Assert; +import org.testng.annotations.Test; + +import java.util.concurrent.TimeUnit; + +public class CommonWxRedisOpsTest { + + protected WxRedisOps wxRedisOps; + private String key = "access_token"; + private String value = String.valueOf(System.currentTimeMillis()); + + @Test + public void testGetValue() { + wxRedisOps.setValue(key, value, 3, TimeUnit.SECONDS); + Assert.assertEquals(wxRedisOps.getValue(key), value); + } + + @Test + public void testSetValue() { + String key = "access_token", value = String.valueOf(System.currentTimeMillis()); + wxRedisOps.setValue(key, value, -1, TimeUnit.SECONDS); + wxRedisOps.setValue(key, value, 0, TimeUnit.SECONDS); + wxRedisOps.setValue(key, value, 1, TimeUnit.SECONDS); + } + + @Test + public void testGetExpire() { + String key = "access_token", value = String.valueOf(System.currentTimeMillis()); + wxRedisOps.setValue(key, value, -1, TimeUnit.SECONDS); + Assert.assertTrue(wxRedisOps.getExpire(key) < 0); + wxRedisOps.setValue(key, value, 4, TimeUnit.SECONDS); + Long expireSeconds = wxRedisOps.getExpire(key); + Assert.assertTrue(expireSeconds <= 4 && expireSeconds >= 0); + } + + @Test + public void testGetExpireForNonExistentKey() { + String nonExistentKey = "non_existent_key_" + System.currentTimeMillis(); + Long expire = wxRedisOps.getExpire(nonExistentKey); + // 对于不存在的 key,底层使用 getExpire(key, TimeUnit.SECONDS) 时应返回负值 + // Spring Data Redis 2.x 和 3.x 约定:-2 表示 key 不存在,-1 表示 key 没有过期时间 + // 因此这里不应返回 null,而应返回一个小于 0 的值 + Assert.assertNotNull(expire, "Non-existent key should not have null expiration"); + Assert.assertTrue(expire < 0, "Non-existent key should have negative expiration"); + } + + @Test + public void testExpire() { + String key = "access_token", value = String.valueOf(System.currentTimeMillis()); + wxRedisOps.setValue(key, value, -1, TimeUnit.SECONDS); + wxRedisOps.expire(key, 4, TimeUnit.SECONDS); + Long expireSeconds = wxRedisOps.getExpire(key); + Assert.assertTrue(expireSeconds <= 4 && expireSeconds >= 0); + } + + @Test + public void testGetLock() { + Assert.assertNotNull(wxRedisOps.getLock("access_token_lock")); + } +} diff --git a/weixin-java-common/src/test/java/me/chanjar/weixin/common/redis/JedisWxRedisOpsTest.java b/weixin-java-common/src/test/java/me/chanjar/weixin/common/redis/JedisWxRedisOpsTest.java new file mode 100644 index 0000000000..2ff2c37b81 --- /dev/null +++ b/weixin-java-common/src/test/java/me/chanjar/weixin/common/redis/JedisWxRedisOpsTest.java @@ -0,0 +1,21 @@ +package me.chanjar.weixin.common.redis; + +import org.testng.annotations.AfterTest; +import org.testng.annotations.BeforeTest; +import redis.clients.jedis.JedisPool; + +public class JedisWxRedisOpsTest extends CommonWxRedisOpsTest { + + JedisPool jedisPool; + + @BeforeTest + public void init() { + this.jedisPool = new JedisPool("127.0.0.1", 6379); + this.wxRedisOps = new JedisWxRedisOps(jedisPool); + } + + @AfterTest + public void destroy() { + this.jedisPool.close(); + } +} diff --git a/weixin-java-common/src/test/java/me/chanjar/weixin/common/redis/RedisTemplateWxRedisOpsTest.java b/weixin-java-common/src/test/java/me/chanjar/weixin/common/redis/RedisTemplateWxRedisOpsTest.java new file mode 100644 index 0000000000..bf3b35a7cc --- /dev/null +++ b/weixin-java-common/src/test/java/me/chanjar/weixin/common/redis/RedisTemplateWxRedisOpsTest.java @@ -0,0 +1,26 @@ +package me.chanjar.weixin.common.redis; + +import org.springframework.data.redis.connection.jedis.JedisConnectionFactory; +import org.springframework.data.redis.core.StringRedisTemplate; +import org.testng.annotations.AfterTest; +import org.testng.annotations.BeforeTest; + +public class RedisTemplateWxRedisOpsTest extends CommonWxRedisOpsTest { + + StringRedisTemplate redisTemplate; + + @BeforeTest + public void init() { + JedisConnectionFactory connectionFactory = new JedisConnectionFactory(); + connectionFactory.setHostName("127.0.0.1"); + connectionFactory.setPort(6379); + connectionFactory.afterPropertiesSet(); + StringRedisTemplate redisTemplate = new StringRedisTemplate(connectionFactory); + this.redisTemplate = redisTemplate; + this.wxRedisOps = new RedisTemplateWxRedisOps(this.redisTemplate); + } + + @AfterTest + public void destroy() { + } +} diff --git a/weixin-java-common/src/test/java/me/chanjar/weixin/common/redis/RedissonWxRedisOpsTest.java b/weixin-java-common/src/test/java/me/chanjar/weixin/common/redis/RedissonWxRedisOpsTest.java new file mode 100644 index 0000000000..48cf7b29be --- /dev/null +++ b/weixin-java-common/src/test/java/me/chanjar/weixin/common/redis/RedissonWxRedisOpsTest.java @@ -0,0 +1,27 @@ +package me.chanjar.weixin.common.redis; + +import org.redisson.Redisson; +import org.redisson.api.RedissonClient; +import org.redisson.config.Config; +import org.redisson.config.TransportMode; +import org.testng.annotations.AfterTest; +import org.testng.annotations.BeforeTest; + +public class RedissonWxRedisOpsTest extends CommonWxRedisOpsTest { + + RedissonClient redissonClient; + + @BeforeTest + public void init() { + Config config = new Config(); + config.useSingleServer().setAddress("redis://127.0.0.1:6379"); + config.setTransportMode(TransportMode.NIO); + this.redissonClient = Redisson.create(config); + this.wxRedisOps = new RedissonWxRedisOps(this.redissonClient); + } + + @AfterTest + public void destroy() { + this.redissonClient.shutdown(); + } +} diff --git a/weixin-java-common/src/test/java/me/chanjar/weixin/common/util/XmlUtilsTest.java b/weixin-java-common/src/test/java/me/chanjar/weixin/common/util/XmlUtilsTest.java index 1afd1c1d9c..ff34475ef2 100644 --- a/weixin-java-common/src/test/java/me/chanjar/weixin/common/util/XmlUtilsTest.java +++ b/weixin-java-common/src/test/java/me/chanjar/weixin/common/util/XmlUtilsTest.java @@ -1,10 +1,10 @@ package me.chanjar.weixin.common.util; +import org.testng.annotations.Test; + import java.util.List; import java.util.Map; -import org.testng.annotations.*; - import static org.assertj.core.api.Assertions.assertThat; /** @@ -17,6 +17,17 @@ */ public class XmlUtilsTest { + @Test(expectedExceptions = {RuntimeException.class}) + public void testXml2Map_xxe() { + String xml = "\n" + + "\n" + + "\n" + + "]>\n" + + ""; + XmlUtils.xml2Map(xml); + } + @Test public void testXml2Map() { String xml = "\n" + @@ -52,27 +63,54 @@ public void testXml2Map() { assertThat(map).isNotNull(); final Map copyrightCheckResult = (Map) map.get("CopyrightCheckResult"); List> resultList = (List>) ((Map) copyrightCheckResult.get("ResultList")).get("item"); - assertThat(copyrightCheckResult).isNotNull(); - assertThat(copyrightCheckResult.get("Count")).isEqualTo("2"); - assertThat(copyrightCheckResult.get("CheckState")).isEqualTo("2"); + assertThat(copyrightCheckResult) + .isNotNull() + .containsEntry("Count", "2") + .containsEntry("CheckState", "2"); + + assertThat(resultList.get(0)).containsEntry("ArticleIdx", "1") + .containsEntry("UserDeclareState", "0") + .containsEntry("AuditState", "2") + .containsEntry("OriginalArticleUrl", "Url_1") + .containsEntry("OriginalArticleType", "1") + .containsEntry("CanReprint", "1") + .containsEntry("NeedReplaceContent", "1") + .containsEntry("NeedShowReprintSource", "1"); + + assertThat(resultList.get(1)).containsEntry("ArticleIdx", "2") + .containsEntry("UserDeclareState", "0") + .containsEntry("AuditState", "2") + .containsEntry("OriginalArticleUrl", "Url_2") + .containsEntry("OriginalArticleType", "1") + .containsEntry("CanReprint", "1") + .containsEntry("NeedReplaceContent", "1") + .containsEntry("NeedShowReprintSource", "1"); + } + + @Test + public void testXml2Map_another() { + String xml = " 1481013459 2247503051 0 1 1 2 "; + + final Map map = XmlUtils.xml2Map(xml); + assertThat(map).isNotNull() + .containsEntry("ToUserName", "gh_4d00ed8d6399") + .containsEntry("FromUserName", "oV5CrjpxgaGXNHIQigzNlgLTnwic") + .containsEntry("CreateTime", "1481013459") + .containsEntry("MsgType", "event"); + + Map publishEventInfo = (Map) map.get("PublishEventInfo"); + assertThat(publishEventInfo).containsEntry("publish_id", "2247503051") + .containsEntry("publish_status", "0") + .containsEntry("article_id", "b5O2OUs25HBxRceL7hfReg-U9QGeq9zQjiDvy WP4Hq4"); - assertThat(resultList.get(0).get("ArticleIdx")).isEqualTo("1"); - assertThat(resultList.get(0).get("UserDeclareState")).isEqualTo("0"); - assertThat(resultList.get(0).get("AuditState")).isEqualTo("2"); - assertThat(resultList.get(0).get("OriginalArticleUrl")).isEqualTo("Url_1"); - assertThat(resultList.get(0).get("OriginalArticleType")).isEqualTo("1"); - assertThat(resultList.get(0).get("CanReprint")).isEqualTo("1"); - assertThat(resultList.get(0).get("NeedReplaceContent")).isEqualTo("1"); - assertThat(resultList.get(0).get("NeedShowReprintSource")).isEqualTo("1"); + Map articleDetail = (Map) publishEventInfo.get("article_detail"); + assertThat(articleDetail).containsEntry("count", "1"); + List< Map> item = (List>) articleDetail.get("item"); + assertThat(item.get(0)).containsEntry("idx", "1") + .containsEntry("article_url", "ARTICLE_URL"); - assertThat(resultList.get(1).get("ArticleIdx")).isEqualTo("2"); - assertThat(resultList.get(1).get("UserDeclareState")).isEqualTo("0"); - assertThat(resultList.get(1).get("AuditState")).isEqualTo("2"); - assertThat(resultList.get(1).get("OriginalArticleUrl")).isEqualTo("Url_2"); - assertThat(resultList.get(1).get("OriginalArticleType")).isEqualTo("1"); - assertThat(resultList.get(1).get("CanReprint")).isEqualTo("1"); - assertThat(resultList.get(1).get("NeedReplaceContent")).isEqualTo("1"); - assertThat(resultList.get(1).get("NeedShowReprintSource")).isEqualTo("1"); + assertThat(item.get(1)).containsEntry("idx", "2") + .containsEntry("article_url", "ARTICLE_URL_2"); } } diff --git a/weixin-java-common/src/test/java/me/chanjar/weixin/common/util/crypto/WxCryptUtilTest.java b/weixin-java-common/src/test/java/me/chanjar/weixin/common/util/crypto/WxCryptUtilTest.java index 82cfa9d2d6..b61696c1ea 100755 --- a/weixin-java-common/src/test/java/me/chanjar/weixin/common/util/crypto/WxCryptUtilTest.java +++ b/weixin-java-common/src/test/java/me/chanjar/weixin/common/util/crypto/WxCryptUtilTest.java @@ -96,7 +96,7 @@ public void testValidateSignatureError() throws ParserConfigurationException, SA String encrypt = nodelist1.item(0).getTextContent(); String fromXML = String.format(this.xmlFormat, encrypt); - pc.decrypt("12345", this.timestamp, this.nonce, fromXML); // 这里签名错误 + pc.decryptXml("12345", this.timestamp, this.nonce, fromXML); // 这里签名错误 } catch (RuntimeException e) { assertEquals(e.getMessage(), "加密消息签名校验失败"); return; diff --git a/weixin-java-common/src/test/java/me/chanjar/weixin/common/util/fs/FileUtilsTest.java b/weixin-java-common/src/test/java/me/chanjar/weixin/common/util/fs/FileUtilsTest.java new file mode 100644 index 0000000000..5a25fb1493 --- /dev/null +++ b/weixin-java-common/src/test/java/me/chanjar/weixin/common/util/fs/FileUtilsTest.java @@ -0,0 +1,34 @@ +package me.chanjar.weixin.common.util.fs; + +import org.apache.commons.io.IOUtils; +import org.testng.annotations.Test; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.IOException; +import java.nio.charset.Charset; +import java.nio.file.Files; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +public class FileUtilsTest { + + @Test + public void testCreateTmpFile() throws IOException { + String strings = "abc"; + File tmpFile = FileUtils.createTmpFile(new ByteArrayInputStream(strings.getBytes()), "name", "txt"); + System.out.println(tmpFile); + List lines = IOUtils.readLines(Files.newInputStream(tmpFile.toPath()), Charset.defaultCharset()); + assertThat(lines).hasSize(1); + assertThat(lines.get(0)).isEqualTo(strings); + } + + @Test + public void testTestCreateTmpFile() { + } + + @Test + public void testImageToBase64ByStream() { + } +} 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 new file mode 100644 index 0000000000..1b20b98d74 --- /dev/null +++ b/weixin-java-common/src/test/java/me/chanjar/weixin/common/util/http/HttpResponseProxyTest.java @@ -0,0 +1,27 @@ +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.*; + +public class HttpResponseProxyTest { + + @Test + public void testExtractFileNameFromContentString() throws WxErrorException { + String content = "attachment; filename*=utf-8''%E6%B5%8B%E8%AF%95.xlsx; filename=\"æµ�è¯�.xlsx\""; + String filename = HttpResponseProxy.extractFileNameFromContentString(content); + assertNotNull(filename); + assertEquals(filename, "测试.xlsx"); + } + + @Test + public void testExtractFileNameFromContentString_another() throws WxErrorException { + String content = "attachment; filename*=utf-8''%E8%90%A5%E4%B8%9A%E6%89%A7%E7%85%A7.jpg; filename=\"è�¥ä¸�æ�§ç�§.jpg\""; +// String content = "attachment; filename=\"è�¥ä¸�æ�§ç�§.jpg\""; + String filename = HttpResponseProxy.extractFileNameFromContentString(content); + assertNotNull(filename); + assertEquals(filename, "营业执照.jpg"); + } +} 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 24a45eea09..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 @@ -1,14 +1,24 @@ package me.chanjar.weixin.common.util.http.apache; +import org.apache.http.HttpRequestInterceptor; +import org.apache.http.HttpResponseInterceptor; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.methods.HttpUriRequest; +import org.apache.http.client.protocol.HttpClientContext; import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.protocol.BasicHttpContext; +import org.apache.http.protocol.HttpContext; +import org.apache.http.protocol.HttpCoreContext; import org.testng.Assert; import org.testng.annotations.Test; import java.io.IOException; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; public class DefaultApacheHttpClientBuilderTest { @Test @@ -24,7 +34,7 @@ public void testBuild() throws Exception { } for (TestThread testThread : threadList) { testThread.join(); - Assert.assertNotEquals(-1,testThread.getRespState(),"请求响应code不应为-1"); + Assert.assertNotEquals(-1, testThread.getRespState(), "请求响应code不应为-1"); } for (int i = 1; i < threadList.size(); i++) { @@ -38,6 +48,47 @@ public void testBuild() throws Exception { } } + @Test + void testHttpClientWithInterceptor() throws Exception { + DefaultApacheHttpClientBuilder builder = DefaultApacheHttpClientBuilder.get(); + + + List interceptorOrder = new ArrayList<>(); + + HttpRequestInterceptor requestInterceptor1 = (request, context) -> { + context.setAttribute("interceptor_called", "requestInterceptor1"); + interceptorOrder.add("requestInterceptor1"); + }; + + HttpRequestInterceptor requestInterceptor2 = (request, context) -> { + interceptorOrder.add("requestInterceptor2"); + }; + + HttpResponseInterceptor responseInterceptor1 = (response, context) -> { + interceptorOrder.add("responseInterceptor1"); + }; + + HttpResponseInterceptor responseInterceptor2 = (response, context) -> { + interceptorOrder.add("responseInterceptor2"); + }; + + builder.setRequestInterceptors(Stream.of(requestInterceptor1, requestInterceptor2).collect(Collectors.toList())); + builder.setResponseInterceptors(Stream.of(responseInterceptor1, responseInterceptor2).collect(Collectors.toList())); + + try (CloseableHttpClient client = builder.build()) { + HttpUriRequest request = new HttpGet("http://localhost:8080"); + HttpContext context = HttpClientContext.create(); + try (CloseableHttpResponse resp = client.execute(request, context)) { + Assert.assertEquals(context.getAttribute("interceptor_called"), "requestInterceptor1", "成功调用 requestInterceptor1 并向 content 中写入了数据"); + + // 测试拦截器执行顺序 + Assert.assertEquals(interceptorOrder.get(0), "requestInterceptor1"); + Assert.assertEquals(interceptorOrder.get(1), "requestInterceptor2"); + Assert.assertEquals(interceptorOrder.get(2), "responseInterceptor1"); + Assert.assertEquals(interceptorOrder.get(3), "responseInterceptor2"); + } + } + } public static class TestThread extends Thread { private CloseableHttpClient client; @@ -47,7 +98,7 @@ public static class TestThread extends Thread { public void run() { client = DefaultApacheHttpClientBuilder.get().build(); HttpGet httpGet = new HttpGet("http://www.sina.com.cn/"); - try (CloseableHttpResponse resp = client.execute(httpGet)){ + try (CloseableHttpResponse resp = client.execute(httpGet)) { respState = resp.getStatusLine().getStatusCode(); } catch (IOException ignored) { } 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/http/okhttp/DefaultOkHttpClientBuilderTest.java b/weixin-java-common/src/test/java/me/chanjar/weixin/common/util/http/okhttp/DefaultOkHttpClientBuilderTest.java new file mode 100644 index 0000000000..d742845b6c --- /dev/null +++ b/weixin-java-common/src/test/java/me/chanjar/weixin/common/util/http/okhttp/DefaultOkHttpClientBuilderTest.java @@ -0,0 +1,66 @@ +package me.chanjar.weixin.common.util.http.okhttp; + +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; +import org.testng.Assert; +import org.testng.annotations.Test; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +public class DefaultOkHttpClientBuilderTest { + @Test + public void testBuild() throws Exception { + DefaultOkHttpClientBuilder builder1 = DefaultOkHttpClientBuilder.get(); + DefaultOkHttpClientBuilder builder2 = DefaultOkHttpClientBuilder.get(); + Assert.assertSame(builder1, builder2, "DefaultOkHttpClientBuilder为单例,获取到的对象应该相同"); + List threadList = new ArrayList<>(10); + for (int i = 0; i < 10; i++) { + DefaultOkHttpClientBuilderTest.TestThread thread = new DefaultOkHttpClientBuilderTest.TestThread(); + thread.start(); + threadList.add(thread); + } + for (DefaultOkHttpClientBuilderTest.TestThread testThread : threadList) { + testThread.join(); + Assert.assertNotEquals(-1, testThread.getRespState(), "请求响应code不应为-1"); + } + + for (int i = 1; i < threadList.size(); i++) { + DefaultOkHttpClientBuilderTest.TestThread thread1 = threadList.get(i - 1); + DefaultOkHttpClientBuilderTest.TestThread thread2 = threadList.get(i); + Assert.assertSame( + thread1.getClient(), + thread2.getClient(), + "DefaultOkHttpClientBuilderTest为单例,并持有了相同的OkHttpClient" + ); + } + } + + public static class TestThread extends Thread { + private OkHttpClient client; + private int respState = -1; + + @Override + public void run() { + client = DefaultOkHttpClientBuilder.get().build(); + Request request = new Request.Builder() + .url("http://www.sina.com.cn/") + .build(); + try (Response response = client.newCall(request).execute()) { + respState = response.code(); + } catch (IOException e) { + // ignore + } + } + + public OkHttpClient getClient() { + return client; + } + + public int getRespState() { + return respState; + } + } +} diff --git a/weixin-java-common/src/test/java/me/chanjar/weixin/common/util/json/GsonHelperTest.java b/weixin-java-common/src/test/java/me/chanjar/weixin/common/util/json/GsonHelperTest.java new file mode 100644 index 0000000000..bafe3c30d1 --- /dev/null +++ b/weixin-java-common/src/test/java/me/chanjar/weixin/common/util/json/GsonHelperTest.java @@ -0,0 +1,139 @@ +package me.chanjar.weixin.common.util.json; + +import com.google.gson.JsonObject; +import org.testng.annotations.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * GsonHelper 的单元测试. + * + * @author Binary Wang + * created on 2020-09-04 + */ +public class GsonHelperTest { + + @Test + public void testIsNull() { + } + + @Test + public void testIsNotNull() { + } + + @Test + public void testGetLong() { + } + + @Test + public void testGetPrimitiveLong() { + } + + @Test + public void testGetInteger() { + } + + @Test + public void testGetPrimitiveInteger() { + } + + @Test + public void testGetDouble() { + } + + @Test + public void testGetPrimitiveDouble() { + } + + @Test + public void testGetFloat() { + } + + @Test + public void testGetPrimitiveFloat() { + } + + @Test + public void testGetBoolean() { + } + + @Test + public void testGetString() { + } + + @Test + public void testGetAsString() { + } + + @Test + public void testGetAsLong() { + } + + @Test + public void testGetAsPrimitiveLong() { + } + + @Test + public void testGetAsInteger() { + } + + @Test + public void testGetAsPrimitiveInt() { + } + + @Test + public void testGetAsBoolean() { + } + + @Test + public void testGetAsPrimitiveBool() { + } + + @Test + public void testGetAsDouble() { + } + + @Test + public void testGetAsPrimitiveDouble() { + } + + @Test + public void testGetAsFloat() { + } + + @Test + public void testGetAsPrimitiveFloat() { + } + + @Test + public void testGetIntArray() { + } + + @Test + public void testGetStringArray() { + } + + @Test + public void testGetLongArray() { + } + + @Test + public void testGetAsJsonArray() { + } + + @Test + public void testBuildSimpleJsonObject() { + try { + GsonHelper.buildJsonObject(1, 2, 3); + } catch (RuntimeException e) { + assertThat(e.getMessage()).isEqualTo("参数个数必须为偶数"); + } + + System.out.println(GsonHelper.buildJsonObject(1, 2)); + System.out.println(GsonHelper.buildJsonObject(1, null)); + System.out.println(GsonHelper.buildJsonObject("int", 1, "float", 2.1f, "double", 2.5)); + System.out.println(GsonHelper.buildJsonObject("boolean", true, "string", "1av")); + System.out.println(GsonHelper.buildJsonObject(1, true, "jsonElement", new JsonObject())); + System.out.println(GsonHelper.buildJsonObject("num", 2, "string", "cde", "char", 'a', "bool", true)); + } +} 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 new file mode 100644 index 0000000000..b278eeafa0 --- /dev/null +++ b/weixin-java-common/src/test/java/me/chanjar/weixin/common/util/locks/RedisTemplateSimpleDistributedLockTest.java @@ -0,0 +1,105 @@ +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; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +import static org.testng.Assert.*; + +@Slf4j +@Test(enabled = true) +public class RedisTemplateSimpleDistributedLockTest { + + private static final String KEY_PREFIX = "System:"; + RedisTemplateSimpleDistributedLock redisLock; + + StringRedisTemplate redisTemplate; + + AtomicInteger lockCurrentExecuteCounter; + + @BeforeTest + public void init() { + JedisConnectionFactory connectionFactory = new JedisConnectionFactory(); + connectionFactory.setHostName("127.0.0.1"); + 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); + } + + @Test(description = "多线程测试锁排他性") + public void testLockExclusive() throws InterruptedException { + int threadSize = 100; + final CountDownLatch startLatch = new CountDownLatch(threadSize); + final CountDownLatch endLatch = new CountDownLatch(threadSize); + + for (int i = 0; i < threadSize; i++) { + new Thread(() -> { + try { + startLatch.await(); + } catch (InterruptedException e) { + log.error("unexpected exception", e); + } + + redisLock.lock(); + assertEquals(lockCurrentExecuteCounter.incrementAndGet(), 1, "临界区同时只能有一个线程执行"); + lockCurrentExecuteCounter.decrementAndGet(); + redisLock.unlock(); + + endLatch.countDown(); + }).start(); + startLatch.countDown(); + } + endLatch.await(); + } + + @Test + public void testTryLock() throws InterruptedException { + assertTrue(redisLock.tryLock(3, TimeUnit.SECONDS), "第一次加锁应该成功"); + assertNotNull(redisLock.getLockSecretValue()); + String redisValue = this.redisTemplate.opsForValue().get(redisLock.getKey()); + assertEquals(redisValue, redisLock.getLockSecretValue()); + + redisLock.unlock(); + assertNull(redisLock.getLockSecretValue()); + redisValue = this.redisTemplate.opsForValue().get(redisLock.getKey()); + assertNull(redisValue, "释放锁后key会被删除"); + + redisLock.unlock(); + } + + +} + 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..dcd90e1a1a --- /dev/null +++ b/weixin-java-cp/INTELLIGENT_ROBOT.md @@ -0,0 +1,149 @@ +# 企业微信智能机器人接口 + +本模块提供企业微信智能机器人相关的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 +WxCpIntelligentRobotSendMessageRequest request = new WxCpIntelligentRobotSendMessageRequest(); +request.setRobotId("robot_id_here"); +request.setUserid("user123"); +request.setMessage("您好,这是来自智能机器人的主动消息"); +request.setSessionId("session123"); // 可选,用于保持会话连续性 + +WxCpIntelligentRobotSendMessageResponse response = robotService.sendMessage(request); +String msgId = response.getMsgId(); +String sessionId = response.getSessionId(); +``` + +### 接收用户消息 + +当用户向智能机器人发送消息时,企业微信会通过回调接口推送消息。可以使用 `WxCpXmlMessage` 接收和解析这些消息: + +```java +// 在接收回调消息的接口中 +WxCpXmlMessage message = WxCpXmlMessage.fromEncryptedXml( + requestBody, wxCpConfigStorage, timestamp, nonce, msgSignature +); + +// 获取智能机器人相关字段 +String robotId = message.getRobotId(); // 机器人ID +String sessionId = message.getSessionId(); // 会话ID +String content = message.getContent(); // 消息内容 +String fromUser = message.getFromUserName(); // 发送用户 + +// 处理消息并回复 +// ... +``` + +### 删除智能机器人 + +```java +String robotId = "robot_id_here"; +robotService.deleteRobot(robotId); +``` + +## 主要类说明 + +### 请求类 + +- `WxCpIntelligentRobotCreateRequest`: 创建机器人请求 +- `WxCpIntelligentRobotUpdateRequest`: 更新机器人请求 +- `WxCpIntelligentRobotChatRequest`: 智能对话请求 +- `WxCpIntelligentRobotSendMessageRequest`: 主动发送消息请求 + +### 响应类 + +- `WxCpIntelligentRobotCreateResponse`: 创建机器人响应 +- `WxCpIntelligentRobotChatResponse`: 智能对话响应 +- `WxCpIntelligentRobotSendMessageResponse`: 主动发送消息响应 +- `WxCpIntelligentRobot`: 机器人信息实体 + +### 消息接收 + +- `WxCpXmlMessage`: 支持接收智能机器人回调消息,包含 `robotId` 和 `sessionId` 字段 + +### 服务接口 + +- `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 2dc09480d4..9294b62d20 100644 --- a/weixin-java-cp/pom.xml +++ b/weixin-java-cp/pom.xml @@ -7,7 +7,7 @@ com.github.binarywang wx-java - 3.6.0 + 4.8.0 weixin-java-cp @@ -30,6 +30,16 @@ okhttp provided + + org.apache.httpcomponents + httpclient + provided + + + org.apache.httpcomponents.client5 + httpclient5 + provided + redis.clients jedis @@ -38,6 +48,15 @@ org.slf4j slf4j-api + + + org.redisson + redisson + + + org.springframework.data + spring-data-redis + org.testng @@ -46,9 +65,10 @@ org.mockito - mockito-all + mockito-core test + com.google.inject guice @@ -73,12 +93,28 @@ org.projectlombok lombok + + org.bouncycastle + bcprov-jdk18on + 1.80 + org.assertj assertj-guava test + + com.github.dreamhead + moco-runner + test + + + + com.fasterxml.jackson.core + jackson-core + test + @@ -95,4 +131,36 @@ + + + native-image + + false + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.5.1 + + + com.github.binarywang.wx.graal.GraalProcessor,lombok.launch.AnnotationProcessorHider$AnnotationProcessor,lombok.launch.AnnotationProcessorHider$ClaimingProcessor + + + + com.github.binarywang + weixin-graal + ${project.version} + + + + + + + + + + diff --git a/weixin-java-cp/src/main/java/com/tencent/wework/Finance.java b/weixin-java-cp/src/main/java/com/tencent/wework/Finance.java new file mode 100644 index 0000000000..4d6406bd18 --- /dev/null +++ b/weixin-java-cp/src/main/java/com/tencent/wework/Finance.java @@ -0,0 +1,252 @@ +package com.tencent.wework; + +import lombok.extern.slf4j.Slf4j; + +import java.util.List; + +/** + * 企业微信会话内容存档Finance类 + * 注意: + * 此类必须配置在com.tencent.wework路径底下,否则会报错:java.lang.UnsatisfiedLinkError: com.xxx.Finance.NewSdk() + *

+ * Q:JAVA版本的sdk报错UnsatisfiedLinkError? + * A:请检查是否修改了sdk的包名。 + *

+ * 官方文档 + * + * @author Wang_Wong created on 2022-01-17 + */ +@Slf4j +public class Finance { + + private static volatile long sdk = -1L; + private static Finance finance = null; + private static final String SO_FILE = "so"; + private static final String DLL_FILE = "dll"; + + /** + * New sdk long. + * + * @return the long + */ + public static native long NewSdk(); + + /** + * 初始化函数 + * Return值=0表示该API调用成功 + * + * @param sdk the sdk + * @param corpid the corpid + * @param secret the secret + * @return 返回是否初始化成功 0 - 成功 !=0 - 失败 + */ + public static native int Init(long sdk, String corpid, String secret); + + /** + * 拉取聊天记录函数 + * Return值=0表示该API调用成功 + * + * @param sdk the sdk + * @param seq the seq + * @param limit the limit + * @param proxy the proxy + * @param passwd the passwd + * @param timeout the timeout + * @param chatData the chat data + * @return 返回是否调用成功 0 - 成功 !=0 - 失败 + */ + public static native int GetChatData(long sdk, long seq, long limit, String proxy, String passwd, long timeout, long chatData); + + /** + * 拉取媒体消息函数 + * Return值=0表示该API调用成功 + * + * @param sdk the sdk + * @param indexbuf the indexbuf + * @param sdkField the sdk field + * @param proxy the proxy + * @param passwd the passwd + * @param timeout the timeout + * @param mediaData the media data + * @return 返回是否调用成功 0 - 成功 !=0 - 失败 + */ + public static native int GetMediaData(long sdk, String indexbuf, String sdkField, String proxy, String passwd, long timeout, long mediaData); + + /** + * 解析密文 + * + * @param sdk the sdk + * @param encrypt_key the encrypt key + * @param encrypt_msg the encrypt msg + * @param msg the msg + * @return 返回是否调用成功 0 - 成功 !=0 - 失败 + */ + public static native int DecryptData(long sdk, String encrypt_key, String encrypt_msg, long msg); + + /** + * Destroy sdk. + * + * @param sdk the sdk + */ + public static native void DestroySdk(long sdk); + + /** + * New slice long. + * + * @return the long + */ + public static native long NewSlice(); + + /** + * 释放slice ,和NewSlice成对使用 + * + * @param slice the slice + */ + public static native void FreeSlice(long slice); + + /** + * 获取slice内容 + * + * @param slice the slice + * @return 内容 string + */ + public static native String GetContentFromSlice(long slice); + + /** + * 获取slice内容长度 + * + * @param slice the slice + * @return 内容 int + */ + public static native int GetSliceLen(long slice); + + /** + * New media data long. + * + * @return the long + */ + public static native long NewMediaData(); + + /** + * Free media data. + * + * @param mediaData the media data + */ + public static native void FreeMediaData(long mediaData); + + /** + * 获取 mediadata outindex + * + * @param mediaData the media data + * @return outindex string + */ + public static native String GetOutIndexBuf(long mediaData); + + /** + * 获取 mediadata data数据 + * + * @param mediaData the media data + * @return data byte [ ] + */ + public static native byte[] GetData(long mediaData); + + /** + * Get index len int. + * + * @param mediaData the media data + * @return the int + */ + public static native int GetIndexLen(long mediaData); + + /** + * Get data len int. + * + * @param mediaData the media data + * @return the int + */ + public static native int GetDataLen(long mediaData); + + /** + * Is media data finish int. + * + * @param mediaData the media data + * @return 1完成 、0未完成 + * 判断mediadata是否结束 + */ + public static native int IsMediaDataFinish(long mediaData); + + /** + * 判断Windows环境 + * + * @return boolean boolean + */ + public static boolean isWindows() { + String osName = System.getProperties().getProperty("os.name"); + log.info("Loading System Libraries, Current OS Version Is: {}", osName); + return osName.toUpperCase().contains("WINDOWS"); + } + + /** + * 加载系统类库 + * + * @param libFiles 类库配置文件 + * @param prefixPath 类库文件的前缀路径 + */ + public Finance(List libFiles, String prefixPath) { + boolean isWindows = Finance.isWindows(); + for (String file : libFiles) { + String suffix = file.substring(file.lastIndexOf(".") + 1); + if (isWindows) { + // 加载dll文件 + if (suffix.equalsIgnoreCase(DLL_FILE)) { + System.load(prefixPath + file); + } + } else { + // 加载so文件 + if (suffix.equalsIgnoreCase(SO_FILE)) { + System.load(prefixPath + file); + } + } + } + + } + + /** + * 初始化类库文件 + * + * @param libFiles the lib files + * @param prefixPath the prefix path + * @return finance finance + */ + public static synchronized Finance loadingLibraries(List libFiles, String prefixPath) { + if (finance != null) { + return finance; + } + finance = new Finance(libFiles, prefixPath); + return finance; + } + + /** + * 单例sdk + * + * @return long + */ + public static synchronized long SingletonSDK() { + if (sdk > 0) { + return sdk; + } + sdk = Finance.NewSdk(); + return sdk; + } + + /** + * 销毁sdk,保证线程可见性 + * + * @param destroySDK the destroy sdk + */ + public static synchronized void DestroySingletonSDK(long destroySDK) { + sdk = 0L; + Finance.DestroySdk(destroySDK); + } + +} diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpAgentService.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpAgentService.java index d57ca56c21..05f06f1da9 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpAgentService.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpAgentService.java @@ -2,13 +2,14 @@ import me.chanjar.weixin.common.error.WxErrorException; import me.chanjar.weixin.cp.bean.WxCpAgent; +import me.chanjar.weixin.cp.bean.WxCpTpAdmin; import java.util.List; /** *

  *  管理企业号应用
- *  文档地址:https://work.weixin.qq.com/api/doc#10087
+ *  文档地址:...
  *  Created by huansinho on 2018/4/13.
  * 
* @@ -18,12 +19,13 @@ public interface WxCpAgentService { /** *
    * 获取企业号应用信息
-   * 该API用于获取企业号某个应用的基本信息,包括头像、昵称、帐号类型、认证类型、可见范围等信息
-   * 详情请见: https://work.weixin.qq.com/api/doc#10087
+   * 该API用于获取企业号某个应用的基本信息,包括头像、昵称、账号类型、认证类型、可见范围等信息
+   * 详情请见: ...
    * 
* * @param agentId 企业应用的id - * @return 部门id + * @return wx cp agent + * @throws WxErrorException the wx error exception */ WxCpAgent get(Integer agentId) throws WxErrorException; @@ -31,10 +33,11 @@ public interface WxCpAgentService { *
    * 设置应用.
    * 仅企业可调用,可设置当前凭证对应的应用;第三方不可调用。
-   * 详情请见: https://work.weixin.qq.com/api/doc#10088
+   * 详情请见: ...
    * 
* * @param agentInfo 应用信息 + * @throws WxErrorException the wx error exception */ void set(WxCpAgent agentInfo) throws WxErrorException; @@ -42,10 +45,26 @@ public interface WxCpAgentService { *
    * 获取应用列表.
    * 企业仅可获取当前凭证对应的应用;第三方仅可获取被授权的应用。
-   * 详情请见: https://work.weixin.qq.com/api/doc#11214
+   * 详情请见: ...
    * 
* + * @return the list + * @throws WxErrorException the wx error exception */ List list() throws WxErrorException; + /** + *
+   * 获取应用管理员列表
+   * 第三方服务商可以用此接口获取授权企业中某个第三方应用或者代开发应用的管理员列表(不包括外部管理员),
+   * 以便服务商在用户进入应用主页之后根据是否管理员身份做权限的区分。
+   * 详情请见: 文档
+   * 
+ * + * @param agentId 应用id + * @return admin list + * @throws WxErrorException the wx error exception + */ + WxCpTpAdmin getAdminList(Integer agentId) throws WxErrorException; + } diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpAgentWorkBenchService.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpAgentWorkBenchService.java new file mode 100644 index 0000000000..67c57a8a88 --- /dev/null +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpAgentWorkBenchService.java @@ -0,0 +1,47 @@ +package me.chanjar.weixin.cp.api; + +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.cp.bean.WxCpAgentWorkBench; + +/** + * The interface Wx cp agent work bench service. + * 工作台自定义展示 + * + * @author songshiyu + * created on 16:16 2020/9/27 + */ +public interface WxCpAgentWorkBenchService { + + /** + * Sets work bench template. + * + * @param wxCpAgentWorkBench the wx cp agent work bench + * @throws WxErrorException the wx error exception + */ + void setWorkBenchTemplate(WxCpAgentWorkBench wxCpAgentWorkBench) throws WxErrorException; + + /** + * Gets work bench template. + * + * @param agentid the agentid + * @return the work bench template + * @throws WxErrorException the wx error exception + */ + String getWorkBenchTemplate(Long agentid) throws WxErrorException; + + /** + * Sets work bench data. + * + * @param wxCpAgentWorkBench the wx cp agent work bench + * @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/WxCpChatService.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpChatService.java index 741ee906d5..6cbe2ec8ac 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpChatService.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpChatService.java @@ -1,8 +1,8 @@ package me.chanjar.weixin.cp.api; import me.chanjar.weixin.common.error.WxErrorException; -import me.chanjar.weixin.cp.bean.WxCpAppChatMessage; import me.chanjar.weixin.cp.bean.WxCpChat; +import me.chanjar.weixin.cp.bean.message.WxCpAppChatMessage; import java.util.List; @@ -12,10 +12,6 @@ * @author gaigeshen */ public interface WxCpChatService { - - @Deprecated - String chatCreate(String name, String owner, List users, String chatId) throws WxErrorException; - /** * 创建群聊会话,注意:刚创建的群,如果没有下发消息,在企业微信不会出现该群. * @@ -23,14 +19,11 @@ public interface WxCpChatService { * @param owner 指定群主的id。如果不指定,系统会随机从userlist中选一人作为群主 * @param users 群成员id列表。至少2人,至多500人 * @param chatId 群聊的唯一标志,不能与已有的群重复;字符串类型,最长32个字符。只允许字符0-9及字母a-zA-Z。如果不填,系统会随机生成群id - * @return 创建的群聊会话chatId + * @return 创建的群聊会话chatId string * @throws WxErrorException 异常 */ String create(String name, String owner, List users, String chatId) throws WxErrorException; - @Deprecated - void chatUpdate(String chatId, String name, String owner, List usersToAdd, List usersToDelete) throws WxErrorException; - /** * 修改群聊会话. * @@ -43,14 +36,11 @@ public interface WxCpChatService { */ void update(String chatId, String name, String owner, List usersToAdd, List usersToDelete) throws WxErrorException; - @Deprecated - WxCpChat chatGet(String chatId) throws WxErrorException; - /** * 获取群聊会话. * * @param chatId 群聊编号 - * @return 群聊会话 + * @return 群聊会话 wx cp chat * @throws WxErrorException 异常 */ WxCpChat get(String chatId) throws WxErrorException; @@ -59,7 +49,7 @@ public interface WxCpChatService { * 应用支持推送文本、图片、视频、文件、图文等类型. * 请求方式: POST(HTTPS) * 请求地址: https://qyapi.weixin.qq.com/cgi-bin/appchat/send?access_token=ACCESS_TOKEN - * 文档地址:https://work.weixin.qq.com/api/doc#90000/90135/90248 + * 文档地址:... * * @param message 要发送的消息内容对象 * @throws WxErrorException 异常 diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpCorpGroupService.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpCorpGroupService.java new file mode 100644 index 0000000000..69aea4bca7 --- /dev/null +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpCorpGroupService.java @@ -0,0 +1,27 @@ +package me.chanjar.weixin.cp.api; + +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.cp.bean.corpgroup.WxCpCorpGroupCorp; + +import java.util.List; + +/** + * 企业互联相关接口 + * + * @author libo <422423229@qq.com> + * @since 2023-02-27 9:57 PM + */ +public interface WxCpCorpGroupService { + /** + * List app share info list. + * + * @param agentId the agent id + * @param businessType the business type + * @param corpId the corp id + * @param limit the limit + * @param cursor the cursor + * @return the list + * @throws WxErrorException the wx error exception + */ + List listAppShareInfo(Integer agentId, Integer businessType, String corpId, Integer limit, String cursor) throws WxErrorException; +} diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpDepartmentService.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpDepartmentService.java index c86816b7f2..75bf02a64b 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpDepartmentService.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpDepartmentService.java @@ -19,31 +19,55 @@ public interface WxCpDepartmentService { *
    * 部门管理接口 - 创建部门.
    * 最多支持创建500个部门
-   * 详情请见: https://work.weixin.qq.com/api/doc#90000/90135/90205
+   * 详情请见: ...
    * 
* * @param depart 部门 - * @return 部门id + * @return 部门id long * @throws WxErrorException 异常 */ Long create(WxCpDepart depart) throws WxErrorException; + /** + *
+   * 部门管理接口 - 获取单个部门详情.
+   * 详情请见: ...
+   * 
+ * + * @param id 部门id + * @return 部门信息 wx cp depart + * @throws WxErrorException 异常 + */ + WxCpDepart get(Long id) throws WxErrorException; + /** *
    * 部门管理接口 - 获取部门列表.
-   * 详情请见: https://work.weixin.qq.com/api/doc#90000/90135/90208
+   * 详情请见: ...
    * 
* * @param id 部门id。获取指定部门及其下的子部门。非必需,可为null - * @return 获取的部门列表 + * @return 获取的部门列表 list * @throws WxErrorException 异常 */ List list(Long id) throws WxErrorException; + /** + *
+   * 部门管理接口 - 获取子部门ID列表.
+   * 详情请见: ...
+   * 
+ * + * @param id 部门id。获取指定部门及其下的子部门(以及子部门的子部门等等,递归)。 如果不填,默认获取全量组织架构 + * @return 子部门ID列表 list + * @throws WxErrorException 异常 + */ + List simpleList(Long id) throws WxErrorException; + /** *
    * 部门管理接口 - 更新部门.
-   * 详情请见: https://work.weixin.qq.com/api/doc#90000/90135/90206
+   * 详情请见: ...
    * 如果id为0(未部门),1(黑名单),2(星标组),或者不存在的id,微信会返回系统繁忙的错误
    * 
* @@ -55,7 +79,7 @@ public interface WxCpDepartmentService { /** *
    * 部门管理接口 - 删除部门.
-   * 详情请见: https://work.weixin.qq.com/api/doc#90000/90135/90207
+   * 详情请见: ...
    * 应用须拥有指定部门的管理权限
    * 
* diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpExportService.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpExportService.java new file mode 100644 index 0000000000..a2c7adabea --- /dev/null +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpExportService.java @@ -0,0 +1,99 @@ +package me.chanjar.weixin.cp.api; + +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.cp.bean.export.WxCpExportRequest; +import me.chanjar.weixin.cp.bean.export.WxCpExportResult; + +/** + * 异步导出接口 + * + * @author zhongjun created on 2022/4/21 + */ +public interface WxCpExportService { + + /** + *
+   *
+   * 导出成员
+   *
+   * 请求方式:POST(HTTPS)
+   * 请求地址:https://qyapi.weixin.qq.com/cgi-bin/export/simple_user?access_token=ACCESS_TOKEN
+   *
+   * 文档地址:https://developer.work.weixin.qq.com/document/path/94849
+   * 
+ * + * @param params 导出参数 + * @return jobId 异步任务id + * @throws WxErrorException . + */ + String simpleUser(WxCpExportRequest params) throws WxErrorException; + + /** + *
+   *
+   * 导出成员详情
+   *
+   * 请求方式:POST(HTTPS)
+   * 请求地址:https://qyapi.weixin.qq.com/cgi-bin/export/user?access_token=ACCESS_TOKEN
+   *
+   * 文档地址:https://developer.work.weixin.qq.com/document/path/94851
+   * 
+ * + * @param params 导出参数 + * @return jobId 异步任务id + * @throws WxErrorException . + */ + String user(WxCpExportRequest params) throws WxErrorException; + + /** + *
+   *
+   * 导出部门
+   *
+   * 请求方式:POST(HTTPS)
+   * 请求地址:https://qyapi.weixin.qq.com/cgi-bin/export/department?access_token=ACCESS_TOKEN
+   *
+   * 文档地址:https://developer.work.weixin.qq.com/document/path/94852
+   * 
+ * + * @param params 导出参数 + * @return jobId 异步任务id + * @throws WxErrorException . + */ + String department(WxCpExportRequest params) throws WxErrorException; + + /** + *
+   *
+   * 导出标签成员
+   *
+   * 请求方式:POST(HTTPS)
+   * 请求地址:https://qyapi.weixin.qq.com/cgi-bin/export/taguser?access_token=ACCESS_TOKEN
+   *
+   * 文档地址:https://developer.work.weixin.qq.com/document/path/94853
+   * 
+ * + * @param params 导出参数 + * @return jobId 异步任务id + * @throws WxErrorException . + */ + String tagUser(WxCpExportRequest params) throws WxErrorException; + + /** + *
+   *
+   * 获取导出结果
+   *
+   * 请求方式:GET(HTTPS)
+   * {@code 请求地址:https://qyapi.weixin.qq.com/cgi-bin/export/get_result?access_token=ACCESS_TOKEN&jobid=jobid_xxxxxxxxxxxxxxx}
+   *
+   * 文档地址:https://developer.work.weixin.qq.com/document/path/94854
+   * 返回的url文件下载解密可参考 CSDN
+   * 
+ * + * @param jobId 异步任务id + * @return 导出结果 result + * @throws WxErrorException . + */ + WxCpExportResult getResult(String jobId) 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 92f2258696..6de9f9226d 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpExternalContactService.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpExternalContactService.java @@ -1,9 +1,20 @@ package me.chanjar.weixin.cp.api; -import me.chanjar.weixin.common.error.WxErrorException; -import me.chanjar.weixin.cp.bean.WxCpUserExternalContactInfo; - +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.util.Date; import java.util.List; +import me.chanjar.weixin.common.bean.result.WxMediaUploadResult; +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.cp.bean.WxCpBaseResp; +import me.chanjar.weixin.cp.bean.external.*; +import me.chanjar.weixin.cp.bean.external.acquisition.*; +import me.chanjar.weixin.cp.bean.external.contact.*; +import me.chanjar.weixin.cp.bean.external.interceptrule.WxCpInterceptRule; +import me.chanjar.weixin.cp.bean.external.interceptrule.WxCpInterceptRuleAddRequest; +import me.chanjar.weixin.cp.bean.external.interceptrule.WxCpInterceptRuleInfo; +import me.chanjar.weixin.cp.bean.external.interceptrule.WxCpInterceptRuleList; /** *
@@ -14,6 +25,103 @@
  * @author JoeCao
  */
 public interface WxCpExternalContactService {
+
+  /**
+   * 配置客户联系「联系我」方式
+   * 
+   * 企业可以在管理后台-客户联系中配置成员的「联系我」的二维码或者小程序按钮,客户通过扫描二维码或点击小程序上的按钮,即可获取成员联系方式,主动联系到成员。
+   * 企业可通过此接口为具有客户联系功能的成员生成专属的「联系我」二维码或者「联系我」按钮。
+   * 如果配置的是「联系我」按钮,需要开发者的小程序接入小程序插件。
+   *
+   * 注意:
+   * 通过API添加的「联系我」不会在管理端进行展示,每个企业可通过API最多配置50万个「联系我」。
+   * 用户需要妥善存储返回的config_id,config_id丢失可能导致用户无法编辑或删除「联系我」。
+   * 临时会话模式不占用「联系我」数量,但每日最多添加10万个,并且仅支持单人。
+   * 临时会话模式的二维码,添加好友完成后该二维码即刻失效。
+   * 文档地址
+   * 
+ * + * @param info 客户联系「联系我」方式 + * @return wx cp contact way result + * @throws WxErrorException the wx error exception + */ + WxCpContactWayResult addContactWay(WxCpContactWayInfo info) throws WxErrorException; + + /** + * 获取企业已配置的「联系我」方式 + * + *
+   * 批量获取企业配置的「联系我」二维码和「联系我」小程序按钮。
+   * 
+ * + * @param configId 联系方式的配置id,必填 + * @return contact way + * @throws WxErrorException the wx error exception + */ + 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; + + /** + * 更新企业已配置的「联系我」方式 + * + *
+   * 更新企业配置的「联系我」二维码和「联系我」小程序按钮中的信息,如使用人员和备注等。
+   * 
+ * + * @param info 客户联系「联系我」方式 + * @return wx cp base resp + * @throws WxErrorException the wx error exception + */ + WxCpBaseResp updateContactWay(WxCpContactWayInfo info) throws WxErrorException; + + /** + * 删除企业已配置的「联系我」方式 + * + *
+   * 删除一个已配置的「联系我」二维码或者「联系我」小程序按钮。
+   * 
+ * + * @param configId 企业联系方式的配置id,必填 + * @return wx cp base resp + * @throws WxErrorException the wx error exception + */ + WxCpBaseResp deleteContactWay(String configId) throws WxErrorException; + + /** + * 结束临时会话 + * + *
+   * 将指定的企业成员和客户之前的临时会话断开,断开前会自动下发已配置的结束语。
+   *
+   * 注意:请保证传入的企业成员和客户之间有仍然有效的临时会话, 通过其他方式的添加外部联系人无法通过此接口关闭会话。
+   * 
+ * + * @param userId the user id + * @param externalUserId the external user id + * @return wx cp base resp + * @throws WxErrorException the wx error exception + */ + WxCpBaseResp closeTempChat(String userId, String externalUserId) throws WxErrorException; + + /** * 获取外部联系人详情. *
@@ -23,26 +131,329 @@ public interface WxCpExternalContactService {
    * 第三方应用调用时,返回的跟进人follow_user仅包含应用可见范围之内的成员。
    * 
* - * @param userId 外部联系人的userid + * @param externalUserId 外部联系人的userid + * @return . external contact + * @throws WxErrorException the wx error exception + * @deprecated 建议使用 {@link #getContactDetail(String, String)} + */ + @Deprecated + WxCpExternalContactInfo getExternalContact(String externalUserId) throws WxErrorException; + + /** + * 获取客户详情. + *
+   *
+   * 企业可通过此接口,根据外部联系人的userid(如何获取?),拉取客户详情。
+   *
+   * 请求方式:GET(HTTPS)
+   * {@code 请求地址:https://qyapi.weixin.qq.com/cgi-bin/externalcontact/get?access_token=ACCESS_TOKEN&external_userid=EXTERNAL_USERID}
+   *
+   * 权限说明:
+   *
+   * 企业需要使用“客户联系”secret或配置到“可调用应用”列表中的自建应用secret所获取的accesstoken来调用(accesstoken如何获取?);
+   * 第三方/自建应用调用时,返回的跟进人follow_user仅包含应用可见范围之内的成员。
+   * 
+ * + * @param externalUserId 外部联系人的userid,注意不是企业成员的帐号 + * @param cursor 用于分页查询的游标,字符串类型,由上一次调用返回,首次调用可不填 + * @return . contact detail + * @throws WxErrorException . + */ + WxCpExternalContactInfo getContactDetail(String externalUserId, String cursor) throws WxErrorException; + + /** + * 企业和服务商可通过此接口,将微信外部联系人的userid转为微信openid,用于调用支付相关接口。暂不支持企业微信外部联系人(ExternalUserid为wo开头)的userid转openid。 + * + * @param externalUserid 微信外部联系人的userid + * @return 该企业的外部联系人openid string + * @throws WxErrorException . + */ + String convertToOpenid(String externalUserid) throws WxErrorException; + + /** + * 服务商为企业代开发微信小程序的场景,服务商可通过此接口,将微信客户的unionid转为external_userid。 + *
+   *
+   * 文档地址:https://work.weixin.qq.com/api/doc/90001/90143/93274
+   *
+   * 服务商代开发小程序指企业使用的小程序为企业主体的,非服务商主体的小程序。
+   * 场景:企业客户在微信端从企业主体的小程序(非服务商应用)登录,同时企业在企业微信安装了服务商的第三方应用,服务商可以调用该接口将登录用户的unionid转换为服务商全局唯一的外部联系人id
+   *
+   * 权限说明:
+   *
+   * 仅认证企业可调用
+   * unionid必须是企业主体下的unionid。即unionid的主体(为绑定了该小程序的微信开放平台账号主体)需与当前企业的主体一致。
+   * unionid的主体(即微信开放平台账号主体)需认证
+   * 该客户的跟进人必须在应用的可见范围之内
+   * 
+ * + * @param unionid 微信客户的unionid + * @param openid the openid + * @return 该企业的外部联系人ID string + * @throws WxErrorException . + */ + String unionidToExternalUserid(String unionid, String openid) throws WxErrorException; + + /** + * 配置客户群进群方式 + * 企业可以在管理后台-客户联系中配置「加入群聊」的二维码或者小程序按钮,客户通过扫描二维码或点击小程序上的按钮,即可加入特定的客户群。 + * 企业可通过此接口为具有客户联系功能的成员生成专属的二维码或者小程序按钮。 + * 如果配置的是小程序按钮,需要开发者的小程序接入小程序插件。 + * 注意: + * 通过API添加的配置不会在管理端进行展示,每个企业可通过API最多配置50万个「加入群聊」(与「联系我」共用50万的额度)。 + * 文档地址:https://developer.work.weixin.qq.com/document/path/92229 + * + * @param wxCpGroupJoinWayInfo the wx cp group join way info + * @return {@link WxCpGroupJoinWayResult} + * @throws WxErrorException the wx error exception + */ + WxCpGroupJoinWayResult addJoinWay(WxCpGroupJoinWayInfo wxCpGroupJoinWayInfo) throws WxErrorException; + + /** + * 更新客户群进群方式配置 + * 更新进群方式配置信息。注意:使用覆盖的方式更新。 + * 文档地址:https://developer.work.weixin.qq.com/document/path/92229 + * + * @param wxCpGroupJoinWayInfo the wx cp group join way info + * @return wx cp base resp + * @throws WxErrorException the wx error exception + */ + WxCpBaseResp updateJoinWay(WxCpGroupJoinWayInfo wxCpGroupJoinWayInfo) throws WxErrorException; + + /** + * 获取客户群进群方式配置 + * 获取企业配置的群二维码或小程序按钮。 + * 文档地址:https://developer.work.weixin.qq.com/document/path/92229 + * + * @param configId the config id + * @return join way + * @throws WxErrorException the wx error exception + */ + WxCpGroupJoinWayInfo getJoinWay(String configId) throws WxErrorException; + + /** + * 删除客户群进群方式配置 + * 文档地址:https://developer.work.weixin.qq.com/document/path/92229 + * + * @param configId the config id + * @return wx cp base resp + * @throws WxErrorException the wx error exception + */ + WxCpBaseResp delJoinWay(String configId) throws WxErrorException; + + /** + * 代开发应用external_userid转换 + *
+   *
+   * 文档地址:https://work.weixin.qq.com/api/doc/90001/90143/95195
+   *
+   * 企业同时安装服务商第三方应用以及授权代开发自建应用的时,服务商可使用该接口将代开发应用获取到的外部联系人id跟第三方应用的id进行关联,
+   * 该接口可将代开发自建应用获取到的external_userid转换为服务商第三方应用的external_userid。
+   *
+   * 权限说明:
+   *
+   * {@code 该企业授权了该服务商第三方应用,且授权的第三方应用具备“企业客户权限->客户基础信息”权限}
+   * 该客户的跟进人必须在应用的可见范围之内
+   * {@code 应用需具备“企业客户权限->客户基础信息”权限}
+   * 
+ * + * @param externalUserid 代开发自建应用获取到的外部联系人ID + * @return 该服务商第三方应用下的企业的外部联系人ID string + * @throws WxErrorException . + */ + String toServiceExternalUserid(String externalUserid) throws WxErrorException; + + /** + * 将代开发应用或第三方应用获取的externaluserid转换成自建应用的externaluserid + *
+   * 文档地址:https://developer.work.weixin.qq.com/document/path/95884#external-userid%E8%BD%AC%E6%8D%A2
+   *
+   * 权限说明:
+   *
+   * 需要使用自建应用或基础应用的access_token
+   * 客户的跟进人,或者用户所在客户群的群主,需要同时在access_token和source_agentid所对应应用的可见范围内
+   * 
+ * + * @param externalUserid 服务商主体的external_userid,必须是source_agentid对应的应用所获取 + * @param sourceAgentId 企业授权的代开发自建应用或第三方应用的agentid + * @return 企业的external_userid + * @throws WxErrorException 微信错误异常 + */ + String fromServiceExternalUserid(String externalUserid, String sourceAgentId) throws WxErrorException; + + /** + * 企业客户微信unionid的升级 - unionid查询external_userid + *
+   *
+   * 文档地址:https://open.work.weixin.qq.com/api/doc/35863#4.2%20unionid%E6%9F%A5%E8%AF%A2external_userid
+   *
+   * 当微信用户在微信中使用第三方应用的小程序或公众号时,第三方可将获取到的unionid与openid,调用此接口转换为企业客户external_userid。
+   * 该接口调用频次有限,每个服务商每小时仅可调用1万次,仅用于微信用户主动使用第三方应用的场景来调用,服务商切不可滥用。
+   * 同时建议服务商的小程序路径或公众号页面链接带上corpid参数,如此可明确地转换出该企业对应的external_userid,以获得更好的性能。
+   *
+   * 权限说明:
+   *
+   * 该企业授权了该服务商第三方应用
+   * 调用频率最大为10000次/小时
+   * unionid和openid的主体需与服务商的主体一致
+   * openid与unionid必须是在同一个小程序或同一个公众号获取到的
+   * 
+ * + * @param unionid 微信客户的unionid + * @param openid 微信客户的openid + * @param corpid 需要换取的企业corpid,不填则拉取所有企业 + * @return 该服务商第三方应用下的企业的外部联系人ID wx cp external user id list + * @throws WxErrorException . + */ + WxCpExternalUserIdList unionidToExternalUserid3rd(String unionid, String openid, String corpid) throws WxErrorException; + + /** + * 转换external_userid + *
+   *
+   * 文档地址:https://open.work.weixin.qq.com/api/doc/35863#转换external_userid
+   *
+   * 对于历史已授权的企业,在2022年3月1号之前,所有接口与回调返回的external_userid仍然为旧的external_userid,
+   * 从2022年3月1号0点开始,所有输入与返回的external_userid字段,将启用升级后的external_userid。
+   * 所以服务商需要在此之前完成历史数据的迁移整改
+   *
+   * 权限说明:
+   *
+   * 该企业授权了该服务商第三方应用
+   * external_userid对应的跟进人需要在应用可见范围内
+   * 
+ * + * @param externalUserIdList 微信客户的unionid + * @return List 新外部联系人id + * @throws WxErrorException . + */ + WxCpNewExternalUserIdList getNewExternalUserId(String[] externalUserIdList) throws WxErrorException; + + /** + * 设置迁移完成 + *
+   *
+   * 文档地址:https://open.work.weixin.qq.com/api/doc/35863#转换external_userid
+   *
+   * 企业授权确认之后,且服务商完成了新旧external_userid的迁移,即可主动将该企业设置为“迁移完成”,
+   * 设置之后,从该企业获取到的将是新的external_userid。注意,该接口需要使用provider_access_token来调用,
+   * 对于有多个应用的服务商,可逐个应用进行external_userid的转换,最后再使用provider_access_token调用该接口完成设置。
+   *
+   * 权限说明:
+   *
+   * 该企业授权了该服务商第三方应用
+   * 
+ * + * @param corpid 企业corpid + * @return wx cp base resp + * @throws WxErrorException . + */ + WxCpBaseResp finishExternalUserIdMigration(String corpid) throws WxErrorException; + + /** + * 客户群opengid转换 + *
+   *
+   * 文档地址:https://open.work.weixin.qq.com/api/doc/90000/90135/94822
+   *
+   * 用户在微信里的客户群里打开小程序时,某些场景下可以获取到群的opengid,如果该群是企业微信的客户群,
+   * 则企业或第三方可以调用此接口将一个opengid转换为客户群chat_id
+   *
+   * 权限说明:
+   *
+   * 企业需要使用“客户联系”secret或配置到“可调用应用”列表中的自建应用secret所获取的accesstoken来调用(accesstoken如何获取?)
+   * {@code 第三方应用需具有“企业客户权限->客户基础信息”权限}
+   * 对于第三方/自建应用,群主必须在应用的可见范围
+   * 仅支持企业服务人员创建的客户群
+   * 仅可转换出自己企业下的客户群chat_id
+   * 
+ * + * @param opengid 小程序在微信获取到的群ID,参见wx.getGroupEnterInfo(https://developers.weixin.qq + * .com/miniprogram/dev/api/open-api/group/wx.getGroupEnterInfo.html) + * @return 客户群ID ,可以用来调用获取客户群详情 + * @throws WxErrorException . + */ + String opengidToChatid(String opengid) throws WxErrorException; + + /** + * 批量获取客户详情. + *
+   *
+   * 企业/第三方可通过此接口获取指定成员添加的客户信息列表。
+   *
+   * 请求方式:POST(HTTPS)
+   * 请求地址:https://qyapi.weixin.qq.com/cgi-bin/externalcontact/batch/get_by_user?access_token=ACCESS_TOKEN
+   *
+   * 权限说明:
+   *
+   * 企业需要使用“客户联系”secret或配置到“可调用应用”列表中的自建应用secret所获取的accesstoken来调用(accesstoken如何获取?);
+   * 第三方/自建应用调用时,返回的跟进人follow_user仅包含应用可见范围之内的成员。
+   * 
+ * + * @param userIdList 企业成员的userid列表,注意不是外部联系人的帐号 + * @param cursor the cursor + * @param limit the limit + * @return wx cp user external contact batch info + * @throws WxErrorException . + */ + WxCpExternalContactBatchInfo getContactDetailBatch(String[] userIdList, String cursor, + Integer limit) + throws WxErrorException; + + /** + * 获取已服务的外部联系人 + *
+   *  企业可通过此接口获取所有已服务的外部联系人,及其添加人和加入的群聊。
+   * 外部联系人分为客户和其他外部联系人,如果是客户,接口将返回外部联系人临时ID和externaluserid;如果是其他外部联系人,接口将只返回外部联系人临时ID。
+   * 请求方式:POST(HTTPS)
+   * 请求地址:https://qyapi.weixin.qq.com/cgi-bin/externalcontact/contact_list?access_token=ACCESS_TOKEN
+   * 文档地址: https://developer.work.weixin.qq.com/document/path/99434
+   * 
+ * + * 注意:企业可通过外部联系人临时ID排除重复数据,外部联系人临时ID有效期为4小时。 + * + * @param cursor the cursor + * @param limit the limit + * @return 已服务的外部联系人列表 + * @throws WxErrorException . + */ + WxCpExternalContactListInfo getContactList(String cursor, Integer limit) throws WxErrorException; + + /** + * 修改客户备注信息. + *
+   * 企业可通过此接口修改指定用户添加的客户的备注信息。
+   * 请求方式: POST(HTTP)
+   * 请求地址:https://qyapi.weixin.qq.com/cgi-bin/externalcontact/remark?access_token=ACCESS_TOKEN
+   * 文档地址:https://work.weixin.qq.com/api/doc/90000/90135/92115
+   * 
+ * + * @param request 备注信息请求 + * @throws WxErrorException . */ - WxCpUserExternalContactInfo getExternalContact(String userId) throws WxErrorException; + void updateRemark(WxCpUpdateRemarkRequest request) throws WxErrorException; /** - * 获取外部联系人列表. + * 获取客户列表. *
-   *   企业可通过此接口获取指定成员添加的客户列表。
-   *   客户是指配置了客户联系功能的成员所添加的外部联系人。
-   *   没有配置客户联系功能的成员,所添加的外部联系人将不会作为客户返回。
+   *   企业可通过此接口获取指定成员添加的客户列表。客户是指配置了客户联系功能的成员所添加的外部联系人。没有配置客户联系功能的成员,所添加的外部联系人将不会作为客户返回。
+   *
+   * 请求方式:GET(HTTPS)
+   * {@code 请求地址:https://qyapi.weixin.qq.com/cgi-bin/externalcontact/list?access_token=ACCESS_TOKEN&userid=USERID}
+   *
+   * 权限说明:
+   *
+   * 企业需要使用“客户联系”secret或配置到“可调用应用”列表中的自建应用secret所获取的accesstoken来调用(accesstoken如何获取?);
    * 第三方应用需拥有“企业客户”权限。
-   * 第三方应用调用时,返回的跟进人follow_user仅包含应用可见范围之内的成员。
+   * 第三方/自建应用只能获取到可见范围内的配置了客户联系功能的成员。
    * 
* - * @param userId 外部联系人的userid + * @param userId 企业成员的userid * @return List of External wx id + * @throws WxErrorException . */ List listExternalContacts(String userId) throws WxErrorException; - /** * 企业和第三方服务商可通过此接口获取配置了客户联系功能的成员(Customer Contact)列表。 *
@@ -52,7 +463,943 @@ public interface WxCpExternalContactService {
    * 
* * @return List of CpUser id + * @throws WxErrorException . + */ + List listFollowers() throws WxErrorException; + + /** + * 获取待分配的离职成员列表 + * 企业和第三方可通过此接口,获取所有离职成员的客户列表,并可进一步调用分配离职成员的客户接口将这些客户重新分配给其他企业成员。 + + * + * 请求方式:POST(HTTPS) + * 请求地址:https://qyapi.weixin.qq.com/cgi-bin/externalcontact/get_unassigned_list?access_token=ACCESS_TOKEN + * + * @param pageId 分页查询,要查询页号,从0开始 + * @param cursor 分页查询游标,字符串类型,适用于数据量较大的情况,如果使用该参数则无需填写page_id,该参数由上一次调用返回 + * @param pageSize 每次返回的最大记录数,默认为1000,最大值为1000 + * @return wx cp user external unassign list + * @throws WxErrorException the wx error exception + */ + WxCpUserExternalUnassignList listUnassignedList(Integer pageId, String cursor, Integer pageSize) throws WxErrorException; + + /** + * 企业可通过此接口,将已离职成员的外部联系人分配给另一个成员接替联系。 + * + * @param externalUserid the external userid + * @param handOverUserid the hand over userid + * @param takeOverUserid the take over userid + * @return wx cp base resp + * @throws WxErrorException the wx error exception + * @deprecated 此后续将不再更新维护, 建议使用 {@link #transferCustomer(WxCpUserTransferCustomerReq)} + */ + @Deprecated + WxCpBaseResp transferExternalContact(String externalUserid, String handOverUserid, String takeOverUserid) throws WxErrorException; + + /** + * 企业可通过此接口,转接在职成员的客户给其他成员。 + *
+   * external_userid必须是handover_userid的客户(即配置了客户联系功能的成员所添加的联系人)。
+   * 在职成员的每位客户最多被分配2次。客户被转接成功后,将有90个自然日的服务关系保护期,保护期内的客户无法再次被分配。
+   *
+   * 权限说明:
+   * 企业需要使用“客户联系”secret或配置到“可调用应用”列表中的自建应用secret所获取的accesstoken来调用(accesstoken如何获取?)。
+   * {@code 第三方应用需拥有“企业客户权限->客户联系->在职继承”权限}
+   * 接替成员必须在此第三方应用或自建应用的可见范围内。
+   * 接替成员需要配置了客户联系功能。
+   * 接替成员需要在企业微信激活且已经过实名认证。
+   * 
+ * + * @param req 转接在职成员的客户给其他成员请求实体 + * @return wx cp base resp + * @throws WxErrorException the wx error exception */ - List listFollowUser() throws WxErrorException; + WxCpUserTransferCustomerResp transferCustomer(WxCpUserTransferCustomerReq req) throws WxErrorException; + /** + * 企业和第三方可通过此接口查询在职成员的客户转接情况。 + *
+   * 权限说明:
+   *
+   * 企业需要使用“客户联系”secret或配置到“可调用应用”列表中的自建应用secret所获取的accesstoken来调用(accesstoken如何获取?)。
+   * {@code 第三方应用需拥有“企业客户权限->客户联系->在职继承”权限}
+   * 接替成员必须在此第三方应用或自建应用的可见范围内。
+   * 
+ * + * @param handOverUserid 原添加成员的userid + * @param takeOverUserid 接替成员的userid + * @param cursor 分页查询的cursor,每个分页返回的数据不会超过1000条;不填或为空表示获取第一个分页; + * @return 客户转接接口实体 wx cp user transfer result resp + * @throws WxErrorException the wx error exception + */ + WxCpUserTransferResultResp transferResult(String handOverUserid, String takeOverUserid, + String cursor) throws WxErrorException; + + /** + * 企业可通过此接口,分配离职成员的客户给其他成员。 + *
+   * handover_userid必须是已离职用户。
+   * external_userid必须是handover_userid的客户(即配置了客户联系功能的成员所添加的联系人)。
+   * 在职成员的每位客户最多被分配2次。客户被转接成功后,将有90个自然日的服务关系保护期,保护期内的客户无法再次被分配。
+
+   *
+   * 权限说明:
+
+   *
+   * 企业需要使用“客户联系”secret或配置到“可调用应用”列表中的自建应用secret所获取的accesstoken来调用(accesstoken如何获取?)。
+   * {@code 第三方应用需拥有“企业客户权限->客户联系->离职分配”权限}
+   * 接替成员必须在此第三方应用或自建应用的可见范围内。
+   * 接替成员需要配置了客户联系功能。
+   * 接替成员需要在企业微信激活且已经过实名认证。
+   * 
+ * + * @param req 转接在职成员的客户给其他成员请求实体 + * @return wx cp base resp + * @throws WxErrorException the wx error exception + */ + WxCpUserTransferCustomerResp resignedTransferCustomer(WxCpUserTransferCustomerReq req) throws WxErrorException; + + /** + * 企业和第三方可通过此接口查询离职成员的客户分配情况。 + *
+   * 权限说明:
+
+   *
+   * 企业需要使用“客户联系”secret或配置到“可调用应用”列表中的自建应用secret所获取的accesstoken来调用(accesstoken如何获取?)。
+   * {@code 第三方应用需拥有“企业客户权限->客户联系->在职继承”权限}
+   * 接替成员必须在此第三方应用或自建应用的可见范围内。
+   * 
+ * + * @param handOverUserid 原添加成员的userid + * @param takeOverUserid 接替成员的userid + * @param cursor 分页查询的cursor,每个分页返回的数据不会超过1000条;不填或为空表示获取第一个分页; + * @return 客户转接接口实体 wx cp user transfer result resp + * @throws WxErrorException the wx error exception + */ + WxCpUserTransferResultResp resignedTransferResult(String handOverUserid, String takeOverUserid, + String cursor) throws WxErrorException; + + /** + *
+   * 该接口用于获取配置过客户群管理的客户群列表。
+   * 企业需要使用“客户联系”secret或配置到“可调用应用”列表中的自建应用secret所获取的accesstoken来调用(accesstoken如何获取?)。
+   * 暂不支持第三方调用。
+   * 微信文档:https://work.weixin.qq.com/api/doc/90000/90135/92119
+   * 
+ * + * @param pageIndex the page index + * @param pageSize the page size + * @param status the status + * @param userIds the user ids + * @param partyIds the party ids + * @return the wx cp user external group chat list + * @throws WxErrorException the wx error exception + * @deprecated 请使用 {@link WxCpExternalContactService#listGroupChat(Integer, String, int, String[])} + */ + @Deprecated + WxCpUserExternalGroupChatList listGroupChat(Integer pageIndex, Integer pageSize, int status, String[] userIds, + String[] partyIds) throws WxErrorException; + + /** + *
+   * 该接口用于获取配置过客户群管理的客户群列表。
+   * 企业需要使用“客户联系”secret或配置到“可调用应用”列表中的自建应用secret所获取的accesstoken来调用(accesstoken如何获取?)。
+   * 暂不支持第三方调用。
+   * 微信文档:https://work.weixin.qq.com/api/doc/90000/90135/92119
+   * 
+ * + * @param limit 分页,预期请求的数据量,取值范围 1 ~ 1000 + * @param cursor 用于分页查询的游标,字符串类型,由上一次调用返回,首次调用不填 + * @param status 客户群跟进状态过滤。0 - 所有列表(即不过滤) 1 - 离职待继承 2 - 离职继承中 3 - 离职继承完成 默认为0 + * @param userIds 群主过滤。如果不填,表示获取应用可见范围内全部群主的数据(但是不建议这么用,如果可见范围人数超过1000人,为了防止数据包过大,会报错 81017);用户ID列表。最多100个 + * @return the wx cp user external group chat list + * @throws WxErrorException the wx error exception + */ + WxCpUserExternalGroupChatList listGroupChat(Integer limit, String cursor, int status, String[] userIds) throws WxErrorException; + + /** + *
+   * 通过客户群ID,获取详情。包括群名、群成员列表、群成员入群时间、入群方式。(客户群是由具有客户群使用权限的成员创建的外部群)
+   * 企业需要使用“客户联系”secret或配置到“可调用应用”列表中的自建应用secret所获取的accesstoken来调用(accesstoken如何获取?)。
+   * 暂不支持第三方调用。
+   * 微信文档:https://work.weixin.qq.com/api/doc/90000/90135/92122
+   * 
+ * + * @param chatId the chat id + * @param needName the need name + * @return group chat + * @throws WxErrorException the wx error exception + */ + WxCpUserExternalGroupChatInfo getGroupChat(String chatId, Integer needName) throws WxErrorException; + + /** + * 企业可通过此接口,将已离职成员为群主的群,分配给另一个客服成员。 + * + *
+   * 注意::
+
+   *
+   * 群主离职了的客户群,才可继承
+   * 继承给的新群主,必须是配置了客户联系功能的成员
+   * 继承给的新群主,必须有设置实名
+   * 继承给的新群主,必须有激活企业微信
+   * 同一个人的群,限制每天最多分配300个给新群主
+
+   *
+   * 权限说明:
+
+   *
+   * 企业需要使用“客户联系”secret或配置到“可调用应用”列表中的自建应用secret所获取的accesstoken来调用(accesstoken如何获取?)。
+   * {@code 第三方应用需拥有“企业客户权限->客户联系->分配离职成员的客户群”权限}
+   * 对于第三方/自建应用,群主必须在应用的可见范围。
+   * 
+ * + * @param chatIds 需要转群主的客户群ID列表。取值范围: 1 ~ 100 + * @param newOwner 新群主ID + * @return 分配结果 ,主要是分配失败的群列表 + * @throws WxErrorException the wx error exception + */ + WxCpUserExternalGroupChatTransferResp transferGroupChat(String[] chatIds, String newOwner) throws WxErrorException; + + + /** + * 企业可通过此接口,将在职成员为群主的群,分配给另一个客服成员。 + *
+   * 注意:
+   * 继承给的新群主,必须是配置了客户联系功能的成员
+   * 继承给的新群主,必须有设置实名
+   * 继承给的新群主,必须有激活企业微信
+   * 同一个人的群,限制每天最多分配300个给新群主
+   * 为保障客户服务体验,90个自然日内,在职成员的每个客户群仅可被转接2次。
+   * 
+ * + * @param chatIds 需要转群主的客户群ID列表。取值范围: 1 ~ 100 + * @param newOwner 新群主ID + * @return 分配结果 ,主要是分配失败的群列表 + * @throws WxErrorException the wx error exception + */ + WxCpUserExternalGroupChatTransferResp onjobTransferGroupChat(String[] chatIds, String newOwner) throws WxErrorException; + + /** + *
+   * 企业可通过此接口获取成员联系客户的数据,包括发起申请数、新增客户数、聊天数、发送消息数和删除/拉黑成员的客户数等指标。
+   * 企业需要使用“客户联系”secret或配置到“可调用应用”列表中的自建应用secret所获取的accesstoken来调用(accesstoken如何获取?)。
+   * 第三方应用需拥有“企业客户”权限。
+   * 第三方/自建应用调用时传入的userid和partyid要在应用的可见范围内;
+   * 
+ * + * @param startTime the start time + * @param endTime the end time + * @param userIds the user ids + * @param partyIds the party ids + * @return user behavior statistic + * @throws WxErrorException the wx error exception + */ + WxCpUserExternalUserBehaviorStatistic getUserBehaviorStatistic(Date startTime, Date endTime, String[] userIds, + String[] partyIds) throws WxErrorException; + + /** + *
+   * 获取指定日期全天的统计数据。注意,企业微信仅存储60天的数据。
+   * 企业需要使用“客户联系”secret或配置到“可调用应用”列表中的自建应用secret所获取的accesstoken来调用(accesstoken如何获取?)。
+   * 暂不支持第三方调用。
+   * 
+ * + * @param startTime the start time + * @param orderBy the order by + * @param orderAsc the order asc + * @param pageIndex the page index + * @param pageSize the page size + * @param userIds the user ids + * @param partyIds the party ids + * @return group chat statistic + * @throws WxErrorException the wx error exception + */ + WxCpUserExternalGroupChatStatistic getGroupChatStatistic(Date startTime, Integer orderBy, Integer orderAsc, + Integer pageIndex, Integer pageSize, String[] userIds, + String[] partyIds) throws WxErrorException; + + /** + * 添加企业群发消息任务 + * 企业可通过此接口添加企业群发消息的任务并通知客服人员发送给相关客户或客户群。(注:企业微信终端需升级到2.7.5版本及以上) + * 注意:调用该接口并不会直接发送消息给客户/客户群,需要相关的客服人员操作以后才会实际发送(客服人员的企业微信需要升级到2.7.5及以上版本) + * 同一个企业每个自然月内仅可针对一个客户/客户群发送4条消息,超过限制的用户将会被忽略。 + + * + * 请求方式: POST(HTTP) + + * + * 请求地址:https://qyapi.weixin.qq.com/cgi-bin/externalcontact/add_msg_template?access_token=ACCESS_TOKEN + + * + * 文档地址 + * + * @param wxCpMsgTemplate the wx cp msg template + * @return the wx cp msg template add result + * @throws WxErrorException the wx error exception + */ + WxCpMsgTemplateAddResult addMsgTemplate(WxCpMsgTemplate wxCpMsgTemplate) throws WxErrorException; + + + /** + * 提醒成员群发 + * 企业和第三方应用可调用此接口,重新触发群发通知,提醒成员完成群发任务,24小时内每个群发最多触发三次提醒。 + + * + * 请求方式: POST(HTTPS) + + * + * 请求地址:https://qyapi.weixin.qq.com/cgi-bin/externalcontact/remind_groupmsg_send?access_token=ACCESS_TOKEN + * + * 文档地址 + * + * @param msgId 群发消息的id,通过获取群发记录列表接口返回 + * @return the wx cp msg template add result + * @throws WxErrorException 微信错误异常 + */ + WxCpBaseResp remindGroupMsgSend(String msgId) throws WxErrorException; + + + /** + * 停止企业群发 + * 企业和第三方应用可调用此接口,停止无需成员继续发送的企业群发 + *

+ * 请求方式: POST(HTTPS) + *

+ * 请求地址:https://qyapi.weixin.qq.com/cgi-bin/externalcontact/cancel_groupmsg_send?access_token=ACCESS_TOKEN + * + * 文档地址 + * + * @param msgId 群发消息的id,通过获取群发记录列表接口返回 + * @return the wx cp msg template add result + * @throws WxErrorException 微信错误异常 + */ + WxCpBaseResp cancelGroupMsgSend(String msgId) throws WxErrorException; + + + /** + * 发送新客户欢迎语 + *

+   * 企业微信在向企业推送添加外部联系人事件时,会额外返回一个welcome_code,企业以此为凭据调用接口,即可通过成员向新添加的客户发送个性化的欢迎语。
+   * 为了保证用户体验以及避免滥用,企业仅可在收到相关事件后20秒内调用,且只可调用一次。
+   * 如果企业已经在管理端为相关成员配置了可用的欢迎语,则推送添加外部联系人事件时不会返回welcome_code。
+   * 每次添加新客户时可能有多个企业自建应用/第三方应用收到带有welcome_code的回调事件,但仅有最先调用的可以发送成功。后续调用将返回41051(externaluser has started chatting)错误,请用户根据实际使用需求,合理设置应用可见范围,避免冲突。
+   * 请求方式: POST(HTTP)
+   *
+   * 请求地址:https://qyapi.weixin.qq.com/cgi-bin/externalcontact/send_welcome_msg?access_token=ACCESS_TOKEN
+   *
+   * 文档地址
+   * 
+ * + * @param msg . + * @throws WxErrorException . + */ + void sendWelcomeMsg(WxCpWelcomeMsg msg) throws WxErrorException; + + /** + *
+   * 企业可通过此接口获取企业客户标签详情。
+   * 
+ * + * @param tagId the tag id + * @return corp tag list + * @throws WxErrorException the wx error exception + */ + WxCpUserExternalTagGroupList getCorpTagList(String[] tagId) throws WxErrorException; + + /** + *
+   * 企业可通过此接口获取企业客户标签详情。
+   * 若tag_id和group_id均为空,则返回所有标签。
+   * 同时传递tag_id和group_id时,忽略tag_id,仅以group_id作为过滤条件。
+   * 
+ * + * @param tagId the tag id + * @param groupId the tagGroup id + * @return corp tag list + * @throws WxErrorException the wx error exception + */ + WxCpUserExternalTagGroupList getCorpTagList(String[] tagId, String[] groupId) throws WxErrorException; + + /** + *
+   * 企业可通过此接口向客户标签库中添加新的标签组和标签,每个企业最多可配置3000个企业标签。
+   * 暂不支持第三方调用。
+   * 
+ * + * @param tagGroup the tag group + * @return wx cp user external tag group info + * @throws WxErrorException the wx error exception + */ + WxCpUserExternalTagGroupInfo addCorpTag(WxCpUserExternalTagGroupInfo tagGroup) throws WxErrorException; + + /** + *
+   * 企业可通过此接口编辑客户标签/标签组的名称或次序值。
+   * 暂不支持第三方调用。
+   * 
+ * + * @param id the id + * @param name the name + * @param order the order + * @return wx cp base resp + * @throws WxErrorException the wx error exception + */ + WxCpBaseResp editCorpTag(String id, String name, Integer order) throws WxErrorException; + + /** + *
+   * 企业可通过此接口删除客户标签库中的标签,或删除整个标签组。
+   * 暂不支持第三方调用。
+   * 
+ * + * @param tagId the tag id + * @param groupId the group id + * @return wx cp base resp + * @throws WxErrorException the wx error exception + */ + WxCpBaseResp delCorpTag(String[] tagId, String[] groupId) throws WxErrorException; + + /** + *
+   * 企业可通过此接口为指定成员的客户添加上由企业统一配置的标签。
+   * 文档地址
+   * 
+ * + * @param userid the userid + * @param externalUserid the external userid + * @param addTag the add tag + * @param removeTag the remove tag + * @return wx cp base resp + * @throws WxErrorException the wx error exception + */ + WxCpBaseResp markTag(String userid, String externalUserid, String[] addTag, String[] removeTag) throws WxErrorException; + + /** + *
+   *   企业和第三方应用可通过该接口创建客户朋友圈的发表任务。
+   *   文档地址
+   * 
+ * + * @param task the task + * @return wx cp add moment result + * @throws WxErrorException the wx error exception + */ + WxCpAddMomentResult addMomentTask(WxCpAddMomentTask task) throws WxErrorException; + + /** + *
+   * 由于发表任务的创建是异步执行的,应用需要再调用该接口以获取创建的结果。
+   * 文档地址
+   * 
+ * + * @param jobId 异步任务id,最大长度为64字节,由创建发表内容到客户朋友圈任务接口获取 + * @return moment task result + * @throws WxErrorException the wx error exception + */ + WxCpGetMomentTaskResult getMomentTaskResult(String jobId) throws WxErrorException; + + + /** + *
+   *   停止发表企业朋友圈。
+   *   文档地址
+   * 
+ * + * @param momentId 朋友圈id,可通过获取客户朋友圈企业发表的列表接口获取朋友圈企业发表的列表 + * @return wx cp add moment result + * @throws WxErrorException the wx error exception + */ + WxCpBaseResp cancelMomentTask(String momentId) throws WxErrorException; + + + /** + *
+   * 获取客户朋友圈全部的发表记录 获取企业全部的发表列表
+   * 文档地址
+   * 
+ * + * @param startTime 朋友圈记录开始时间。Unix时间戳 + * @param endTime 朋友圈记录结束时间。Unix时间戳 + * @param creator 朋友圈创建人的userid + * @param filterType 朋友圈类型。0:企业发表 1:个人发表 2:所有,包括个人创建以及企业创建,默认情况下为所有类型 + * @param cursor 用于分页查询的游标,字符串类型,由上一次调用返回,首次调用可不填 + * @param limit 返回的最大记录数,整型,最大值100,默认值100,超过最大值时取默认值 + * @return moment list + * @throws WxErrorException the wx error exception + */ + WxCpGetMomentList getMomentList(Long startTime, Long endTime, String creator, Integer filterType, + String cursor, Integer limit) throws WxErrorException; + + /** + *
+   * 获取客户朋友圈全部的发表记录 获取客户朋友圈企业发表的列表
+   * 文档地址
+   * 
+ * + * @param momentId 朋友圈id,仅支持企业发表的朋友圈id + * @param cursor 用于分页查询的游标,字符串类型,由上一次调用返回,首次调用可不填 + * @param limit 返回的最大记录数,整型,最大值1000,默认值500,超过最大值时取默认值 + * @return moment task + * @throws WxErrorException the wx error exception + */ + WxCpGetMomentTask getMomentTask(String momentId, String cursor, Integer limit) + throws WxErrorException; + + /** + *
+   * 获取客户朋友圈全部的发表记录 获取客户朋友圈发表时选择的可见范围
+   * 文档地址
+   * 
+ * + * @param momentId 朋友圈id + * @param userId 企业发表成员userid,如果是企业创建的朋友圈,可以通过获取客户朋友圈企业发表的 + * 列表获取已发表成员userid,如果是个人创建的朋友圈,创建人userid就是企业发表成员userid + * @param cursor 用于分页查询的游标,字符串类型,由上一次调用返回,首次调用可不填 + * @param limit 返回的最大记录数,整型,最大值1000,默认值500,超过最大值时取默认值 + * @return moment customer list + * @throws WxErrorException the wx error exception + */ + WxCpGetMomentCustomerList getMomentCustomerList(String momentId, String userId, + String cursor, Integer limit) throws WxErrorException; + + /** + *
+   * 获取客户朋友圈全部的发表记录 获取客户朋友圈发表后的可见客户列表
+   * 文档地址
+   * 
+ * + * @param momentId 朋友圈id + * @param userId 企业发表成员userid,如果是企业创建的朋友圈,可以通过获取客户朋友圈企业发表的列表获取已发表成员userid, + * 如果是个人创建的朋友圈,创建人userid就是企业发表成员userid + * @param cursor 用于分页查询的游标,字符串类型,由上一次调用返回,首次调用可不填 + * @param limit 返回的最大记录数,整型,最大值5000,默认值3000,超过最大值时取默认值 + * @return moment send result + * @throws WxErrorException the wx error exception + */ + WxCpGetMomentSendResult getMomentSendResult(String momentId, String userId, + String cursor, Integer limit) throws WxErrorException; + + /** + *
+   * 获取客户朋友圈全部的发表记录 获取客户朋友圈的互动数据
+   * 文档地址
+   * 
+ * + * @param momentId 朋友圈id + * @param userId 企业发表成员userid,如果是企业创建的朋友圈,可以通过获取客户朋友圈企业发表的列表获取已发表成员userid, + * 如果是个人创建的朋友圈,创建人userid就是企业发表成员userid + * @return moment comments + * @throws WxErrorException the wx error exception + */ + WxCpGetMomentComments getMomentComments(String momentId, String userId) + throws WxErrorException; + + /** + *
+   * 企业和第三方应用可通过此接口获取企业与成员的群发记录。
+   * 文档地址
+   * 
+ * + * @param chatType 群发任务的类型,默认为single,表示发送给客户,group表示发送给客户群 + * @param startTime 群发任务记录开始时间 + * @param endTime 群发任务记录结束时间 + * @param creator 群发任务创建人企业账号id + * @param filterType 创建人类型。0:企业发表 1:个人发表 2:所有,包括个人创建以及企业创建,默认情况下为所有类型 + * @param limit 返回的最大记录数,整型,最大值100,默认值50,超过最大值时取默认值 + * @param cursor 用于分页查询的游标,字符串类型,由上一次调用返回,首次调用可不填 + * @return wx cp base resp + * @throws WxErrorException the wx error exception + */ + WxCpGroupMsgListResult getGroupMsgListV2(String chatType, Date startTime, Date endTime, + String creator, Integer filterType, Integer limit, String cursor) throws WxErrorException; + + /** + *
+   * 企业和第三方应用可通过此接口获取企业与成员的群发记录。
+   * 文档地址:https://work.weixin.qq.com/api/doc/90000/90135/93338
+   * 
+ * + * @param msgid 群发消息的id,通过获取群发记录列表接口返回 + * @param userid 发送成员userid,通过获取群发成员发送任务列表接口返回 + * @param limit 返回的最大记录数,整型,最大值1000,默认值500,超过最大值时取默认值 + * @param cursor 用于分页查询的游标,字符串类型,由上一次调用返回,首次调用可不填 + * @return wx cp base resp + * @throws WxErrorException the wx error exception + */ + WxCpGroupMsgSendResult getGroupMsgSendResult(String msgid, String userid, Integer limit, String cursor) throws WxErrorException; + + /** + *
+   * 企业跟第三方应用可通过该接口获取到创建企业群发的群发发送结果。
+   * 文档
+   * 
+ * + * @param msgid 群发消息的id,通过创建企业群发接口返回 + * @param limit 返回的最大记录数,整型,最大值10000,默认值10000 + * @param cursor 用于分页查询的游标,字符串类型,由上一次调用返回,首次调用可不填 + * @return wx cp base resp + * @throws WxErrorException the wx error exception + */ + WxCpGroupMsgResult getGroupMsgResult(String msgid, Integer limit, String cursor) throws WxErrorException; + + /** + *
+   * 获取群发成员发送任务列表。
+   * 文档地址:https://work.weixin.qq.com/api/doc/90000/90135/93338
+   * 
+ * + * @param msgid 群发消息的id,通过获取群发记录列表接口返回 + * @param limit 返回的最大记录数,整型,最大值1000,默认值500,超过最大值时取默认值 + * @param cursor 用于分页查询的游标,字符串类型,由上一次调用返回,首次调用可不填 + * @return wx cp base resp + * @throws WxErrorException the wx error exception + */ + WxCpGroupMsgTaskResult getGroupMsgTask(String msgid, Integer limit, String cursor) throws WxErrorException; + + /** + *
+   * 添加入群欢迎语素材。
+   * 文档地址:https://open.work.weixin.qq.com/api/doc/90000/90135/92366
+   * 
+ * + * @param template 素材内容 + * @return template_id 欢迎语素材id + * @throws WxErrorException the wx error exception + */ + String addGroupWelcomeTemplate(WxCpGroupWelcomeTemplateResult template) throws WxErrorException; + + /** + *
+   * 编辑入群欢迎语素材。
+   * 文档地址:https://open.work.weixin.qq.com/api/doc/90000/90135/92366
+   * 
+ * + * @param template the template + * @return wx cp base resp + * @throws WxErrorException the wx error exception + */ + WxCpBaseResp editGroupWelcomeTemplate(WxCpGroupWelcomeTemplateResult template) throws WxErrorException; + + /** + *
+   * 获取入群欢迎语素材。
+   * 文档地址:https://open.work.weixin.qq.com/api/doc/90000/90135/92366
+   * 
+ * + * @param templateId 群欢迎语的素材id + * @return wx cp base resp + * @throws WxErrorException the wx error exception + */ + WxCpGroupWelcomeTemplateResult getGroupWelcomeTemplate(String templateId) throws WxErrorException; + + /** + *
+   * 删除入群欢迎语素材。
+   * 企业可通过此API删除入群欢迎语素材,且仅能删除调用方自己创建的入群欢迎语素材。
+   * 文档地址:https://open.work.weixin.qq.com/api/doc/90000/90135/92366
+   * 
+ * + * @param templateId 群欢迎语的素材id + * @param agentId the agent id + * @return wx cp base resp + * @throws WxErrorException the wx error exception + */ + WxCpBaseResp delGroupWelcomeTemplate(String templateId, String agentId) throws WxErrorException; + + /** + *
+   * 获取商品图册列表
+   * 文档地址:https://work.weixin.qq.com/api/doc/90000/90135/95096
+   * 
+ * + * @param limit 返回的最大记录数,整型,最大值100,默认值50,超过最大值时取默认值 + * @param cursor 用于分页查询的游标,字符串类型,由上一次调用返回,首次调用可不填 + * @return wx cp base resp + * @throws WxErrorException the wx error exception + */ + WxCpProductAlbumListResult getProductAlbumList(Integer limit, String cursor) throws WxErrorException; + + /** + *
+   * 获取商品图册
+   * 文档地址:https://work.weixin.qq.com/api/doc/90000/90135/95096
+   * 
+ * + * @param productId 商品id + * @return wx cp base resp + * @throws WxErrorException the wx error exception + */ + WxCpProductAlbumResult getProductAlbum(String productId) throws WxErrorException; + + /** + *
+   * 上传附件资源
+   * ...
+   * 
+ * + * @param mediaType the media type + * @param fileType the file type + * @param attachmentType the attachment type + * @param inputStream the input stream + * @return wx media upload result + * @throws WxErrorException the wx error exception + * @throws IOException the io exception + */ + WxMediaUploadResult uploadAttachment(String mediaType, String fileType, Integer attachmentType, + InputStream inputStream) throws WxErrorException, IOException; + + /** + *
+   * 上传附件资源
+   * ...
+   * 
+ * + * @param mediaType the media type + * @param attachmentType the attachment type + * @param file the file + * @return wx media upload result + * @throws WxErrorException the wx error exception + */ + WxMediaUploadResult uploadAttachment(String mediaType, Integer attachmentType, File file) + throws WxErrorException; + + /** + *
+   * 新建敏感词规则
+   * 企业和第三方应用可以通过此接口新建敏感词规则
+   * 请求方式:POST(HTTPS)
+   * 请求地址:https://qyapi.weixin.qq.com/cgi-bin/externalcontact/add_intercept_rule?access_token=ACCESS_TOKEN
+   * 
+ * @param ruleAddRequest the rule add request + * @return 规则id + * @throws WxErrorException the wx error exception + */ + String addInterceptRule(WxCpInterceptRuleAddRequest ruleAddRequest) throws WxErrorException; + + /** + *
+   * 修改敏感词规则
+   * 文档地址
+   * 企业和第三方应用可以通过此接口修改敏感词规则
+   * 请求方式:POST(HTTPS)
+   * 请求地址:https://qyapi.weixin.qq.com/cgi-bin/externalcontact/update_intercept_rule?access_token=ACCESS_TOKEN
+   * 
+ * @param interceptRule the rule + * @throws WxErrorException the wx error exception + */ + void updateInterceptRule(WxCpInterceptRule interceptRule) throws WxErrorException; + + /** + *
+   * 删除敏感词规则
+   * 企业和第三方应用可以通过此接口修改敏感词规则
+   * 请求方式:POST(HTTPS)
+   * 请求地址
+   * 
+ * @param ruleId 规则id + * @throws WxErrorException the wx error exception + */ + void delInterceptRule(String ruleId) throws WxErrorException; + + /** + * 获取敏感词规则列表 + * + * 企业和第三方应用可以通过此接口获取所有设置的敏感词规则列表。 + * 请求方式:GET(HTTPS) + * 文档地址:获取敏感词规则列表 + * + * @return WxCpInterceptRuleList 敏感词规则列表 + * @throws WxErrorException 微信API异常 + */ + WxCpInterceptRuleList getInterceptRuleList() throws WxErrorException; + + /** + * 获取敏感词详情 + * + * 企业和第三方应用可以通过此接口获取单个敏感词规则的详细信息。 + * 请求方式:GET(HTTPS) + * 文档地址:获取敏感词详情 + * + * @param ruleId 敏感词规则ID + * @return WxCpInterceptRuleInfo 敏感词规则详情 + * @throws WxErrorException 微信API异常 + */ + WxCpInterceptRuleInfo getInterceptRuleDetail(String ruleId) throws WxErrorException; + + /** + *
+   * 创建商品图册
+   * 企业和第三方应用可以通过此接口增加商品
+   * 请求方式:POST(HTTPS)
+   * 请求地址:
+   * https://qyapi.weixin.qq.com/cgi-bin/externalcontact/add_product_album?access_token=ACCESS_TOKEN
+   * 文档地址
+   * 
+ * @param wxCpProductAlbumInfo 商品图册信息 + * @return 商品id string + * @throws WxErrorException the wx error exception + */ + String addProductAlbum(WxCpProductAlbumInfo wxCpProductAlbumInfo) throws WxErrorException; + + /** + *
+   * 编辑商品图册
+   * 企业和第三方应用可以通过此接口修改商品信息
+   * 请求方式:POST(HTTPS)
+   * 请求地址:
+   * https://qyapi.weixin.qq.com/cgi-bin/externalcontact/update_product_album?access_token=ACCESS_TOKEN
+   * 文档地址
+   * 
+ * @param wxCpProductAlbumInfo 商品图册信息 + * @throws WxErrorException the wx error exception + */ + void updateProductAlbum(WxCpProductAlbumInfo wxCpProductAlbumInfo) throws WxErrorException; + + /** + *
+   * 删除商品图册
+   * 企业和第三方应用可以通过此接口删除商品信息
+   * 请求方式:POST(HTTPS)
+   * 请求地址:
+   * https://qyapi.weixin.qq.com/cgi-bin/externalcontact/delete_product_album?access_token=ACCESS_TOKEN
+   *
+   * 文档地址
+   * 
+ * @param productId 商品id + * @throws WxErrorException the wx error exception + */ + void deleteProductAlbum(String productId) throws WxErrorException; + + /** + *
+   * 获取获客链接列表
+   * 企业可通过此接口获取当前仍然有效的获客链接。
+   * 请求方式:POST(HTTPS)
+   * 请求地址:
+   * 接口地址
+   *
+   * 文档地址
+   * 
+ * @param limit 商品id + * @param cursor 商品id + * @return 获客链接列表 + * @throws WxErrorException the wx error exception + */ + WxCpCustomerAcquisitionList customerAcquisitionLinkList(Integer limit, String cursor) throws WxErrorException; + + /** + *
+   * 获取获客链接详情
+   * 企业可通过此接口根据获客链接id获取链接配置详情。。
+   * 请求方式:POST(HTTPS)
+   * 请求地址:
+   * 接口地址
+   *
+   * 文档地址
+   * 
+ * @param linkId 获客链接ID + * @return 获客链接详情 + * @throws WxErrorException the wx error exception + */ + WxCpCustomerAcquisitionInfo customerAcquisitionLinkGet(String linkId) throws WxErrorException; + + /** + *
+   * 创建获客链接
+   * 企业可通过此接口创建新的获客链接。
+   * 请求方式:POST(HTTPS)
+   * 请求地址:
+   * 接口地址
+   * 文档地址
+   * 
+ * + * @param wxCpCustomerAcquisitionRequest 创建链接请求 + * @return 创建链接详情 + * @throws WxErrorException the wx error exception + */ + WxCpCustomerAcquisitionCreateResult customerAcquisitionLinkCreate(WxCpCustomerAcquisitionRequest wxCpCustomerAcquisitionRequest) throws WxErrorException; + + /** + *
+   * 编辑获客链接
+   * 企业可通过此接口编辑获客链接,修改获客链接的关联范围或修改获客链接的名称。
+   * 请求方式:POST(HTTPS)
+   * 请求地址:
+   * 接口地址
+   * 文档地址
+   * 
+ * + * @param wxCpCustomerAcquisitionRequest 编辑链接请求 + * @return 编辑链接详情 + * @throws WxErrorException the wx error exception + */ + WxCpBaseResp customerAcquisitionUpdate(WxCpCustomerAcquisitionRequest wxCpCustomerAcquisitionRequest) throws WxErrorException; + + /** + *
+   * 删除获客链接
+   * 企业可通过此接口删除获客链接,删除后的获客链接将无法继续使用。
+   * 请求方式:POST(HTTPS)
+   * 请求地址:
+   * 接口地址
+   * 文档地址
+   * 
+ * + * @param linkId 获客链接的id + * @return 删除结果 + * @throws WxErrorException the wx error exception + */ + WxCpBaseResp customerAcquisitionLinkDelete(String linkId) throws WxErrorException; + + /** + *
+   * 获取获客客户列表
+   * 企业可通过此接口获取到由指定的获客链接添加的客户列表。
+   * 请求方式:POST(HTTPS)
+   * 请求地址:
+   * 接口地址
+   * 文档地址
+   * 
+ * + * @param linkId 获客链接id + * @param limit 返回的最大记录数,整型,最大值1000 + * @param cursor 用于分页查询的游标,字符串类型,由上一次调用返回,首次调用可不填 + * @return 由获客链接添加的客户信息列表 + * @throws WxErrorException the wx error exception + */ + WxCpCustomerAcquisitionCustomerList customerAcquisitionCustomer(String linkId, Integer limit, String cursor) throws WxErrorException; + + /** + *
+   * 查询剩余使用量
+   * 企业可通过此接口查询当前剩余的使用量。
+   * 请求方式:GET(HTTPS)
+   * 请求地址:
+   * 接口地址
+   * 文档地址
+   * 
+ * + * @return 剩余使用量 + * @throws WxErrorException the wx error exception + */ + WxCpCustomerAcquisitionQuota customerAcquisitionQuota() throws WxErrorException; + + + /** + * 查询链接使用详情 + * 服务商可通过此接口查询指定组件授权的获客链接在指定时间范围内的访问情况。 + * + * 请求方式:POST(HTTPS) + * 请求地址:https://qyapi.weixin.qq.com/cgi-bin/externalcontact/customer_acquisition/statistic?access_token=ACCESS_TOKEN + * + * @author Hugo + * @since 2023/12/5 14:34 + * @param linkId 获客链接的id + * @param startTime 统计起始时间 + * @param endTime 统计结束时间 + * @return 点击链接客户数和新增客户数 + * @throws WxErrorException the wx error exception + */ + WxCpCustomerAcquisitionStatistic customerAcquisitionStatistic(String linkId, Date startTime, Date endTime) 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 new file mode 100644 index 0000000000..b8ccea5e50 --- /dev/null +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpGroupRobotService.java @@ -0,0 +1,136 @@ +package me.chanjar.weixin.cp.api; + +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.cp.bean.article.NewArticle; +import me.chanjar.weixin.cp.bean.message.WxCpGroupRobotMessage; + +import java.util.List; + +/** + * 微信群机器人消息发送api + * 文档地址:https://work.weixin.qq.com/help?doc_id=13376 + * 调用地址:https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key= + * + * @author yr created on 2020-8-20 + */ +public interface WxCpGroupRobotService { + + /** + * 发送text类型的消息 + * + * @param content 文本内容,最长不超过2048个字节,必须是utf8编码 + * @param mentionedList userId的列表,提醒群中的指定成员(@某个成员),@all表示提醒所有人,如果开发者获取不到userId,可以使用mentioned_mobile_list + * @param mobileList 手机号列表,提醒手机号对应的群成员(@某个成员),@all表示提醒所有人 + * @throws WxErrorException 异常 + */ + void sendText(String content, List mentionedList, List mobileList) throws WxErrorException; + + /** + * 发送markdown类型的消息 + * + * @param content markdown内容,最长不超过4096个字节,必须是utf8编码 + * @throws WxErrorException 异常 + */ + void sendMarkdown(String content) throws WxErrorException; + + /** + * 发送image类型的消息 + * + * @param base64 图片内容的base64编码 + * @param md5 图片内容(base64编码前)的md5值 + * @throws WxErrorException 异常 + */ + void sendImage(String base64, String md5) throws WxErrorException; + + /** + * 发送news类型的消息 + * + * @param articleList 图文消息,支持1到8条图文 + * @throws WxErrorException 异常 + */ + void sendNews(List articleList) throws WxErrorException; + + /** + * 发送text类型的消息 + * + * @param webhookUrl webhook地址 + * @param content 文本内容,最长不超过2048个字节,必须是utf8编码 + * @param mentionedList userId的列表,提醒群中的指定成员(@某个成员),@all表示提醒所有人,如果开发者获取不到userId,可以使用mentioned_mobile_list + * @param mobileList 手机号列表,提醒手机号对应的群成员(@某个成员),@all表示提醒所有人 + * @throws WxErrorException 异常 + */ + void sendText(String webhookUrl, String content, List mentionedList, List mobileList) throws WxErrorException; + + /** + * 发送markdown类型的消息 + * + * @param webhookUrl webhook地址 + * @param content markdown内容,最长不超过4096个字节,必须是utf8编码 + * @throws WxErrorException 异常 + */ + 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类型的消息 + * + * @param webhookUrl webhook地址 + * @param base64 图片内容的base64编码 + * @param md5 图片内容(base64编码前)的md5值 + * @throws WxErrorException 异常 + */ + void sendImage(String webhookUrl, String base64, String md5) throws WxErrorException; + + /** + * 发送news类型的消息 + * + * @param webhookUrl webhook地址 + * @param articleList 图文消息,支持1到8条图文 + * @throws WxErrorException 异常 + */ + void sendNews(String webhookUrl, List articleList) throws WxErrorException; + + /** + * 发送文件类型的消息 + * + * @param webhookUrl webhook地址 + * @param mediaId 文件id + * @throws WxErrorException 异常 + */ + void sendFile(String webhookUrl, String mediaId) throws WxErrorException; + + /** + * 发送文件类型的消息 + * + * @param webhookUrl webhook地址 + * @param mediaId 语音文件id + * @throws WxErrorException 异常 + */ + void sendVoice(String webhookUrl, String mediaId) throws WxErrorException; + + /** + * 发送模板卡片消息 + * + * @param webhookUrl webhook地址 + * @param wxCpGroupRobotMessage 群机器人消息 + * @throws WxErrorException 异常 + */ + void sendTemplateCardMessage(String webhookUrl, WxCpGroupRobotMessage wxCpGroupRobotMessage) throws WxErrorException; + +} diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpIntelligentRobotService.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpIntelligentRobotService.java new file mode 100644 index 0000000000..bc5f3f1915 --- /dev/null +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpIntelligentRobotService.java @@ -0,0 +1,77 @@ +package me.chanjar.weixin.cp.api; + +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.cp.bean.intelligentrobot.*; + +/** + * 企业微信智能机器人接口 + * 官方文档: https://developer.work.weixin.qq.com/document/path/101039 + * + * @author Binary Wang + */ +public interface WxCpIntelligentRobotService { + + /** + * 创建智能机器人 + * + * @param request 创建请求参数 + * @return 创建结果 + * @throws WxErrorException 微信接口异常 + */ + WxCpIntelligentRobotCreateResponse createRobot(WxCpIntelligentRobotCreateRequest request) throws WxErrorException; + + /** + * 删除智能机器人 + * + * @param robotId 机器人ID + * @throws WxErrorException 微信接口异常 + */ + void deleteRobot(String robotId) throws WxErrorException; + + /** + * 更新智能机器人 + * + * @param request 更新请求参数 + * @throws WxErrorException 微信接口异常 + */ + void updateRobot(WxCpIntelligentRobotUpdateRequest request) throws WxErrorException; + + /** + * 查询智能机器人 + * + * @param robotId 机器人ID + * @return 机器人信息 + * @throws WxErrorException 微信接口异常 + */ + WxCpIntelligentRobot getRobot(String robotId) throws WxErrorException; + + /** + * 智能机器人会话 + * + * @param request 聊天请求参数 + * @return 聊天响应 + * @throws WxErrorException 微信接口异常 + */ + WxCpIntelligentRobotChatResponse chat(WxCpIntelligentRobotChatRequest request) throws WxErrorException; + + /** + * 重置智能机器人会话 + * + * @param robotId 机器人ID + * @param userid 用户ID + * @param sessionId 会话ID + * @throws WxErrorException 微信接口异常 + */ + void resetSession(String robotId, String userid, String sessionId) throws WxErrorException; + + /** + * 智能机器人主动发送消息 + * 官方文档: https://developer.work.weixin.qq.com/document/path/100719 + * + * @param request 发送消息请求参数 + * @return 发送消息响应 + * @throws WxErrorException 微信接口异常 + */ + WxCpIntelligentRobotSendMessageResponse sendMessage(WxCpIntelligentRobotSendMessageRequest request) throws WxErrorException; + +} \ No newline at end of file diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpKfService.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpKfService.java new file mode 100644 index 0000000000..046cfbc5bb --- /dev/null +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpKfService.java @@ -0,0 +1,294 @@ +package me.chanjar.weixin.cp.api; + +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.cp.bean.WxCpBaseResp; +import me.chanjar.weixin.cp.bean.kf.*; + +import java.util.List; + +/** + * 微信客服接口 + *

+ * 微信客服由腾讯微信团队为企业打造,用于满足企业的客服需求,帮助企业做好客户服务。企业可以在微信内、外各个场景中接入微信客服, + * 用户可以发起咨询,企业可以进行回复。 + * 企业可在微信客服官网使用企业微信扫码开通微信客服,开通后即可使用。 + * + * @author Fu created on 2022/1/19 19:25 + */ +public interface WxCpKfService { + + /** + * 添加客服帐号,并可设置客服名称和头像。目前一家企业最多可添加10个客服帐号 + * + * @param add 客服帐号信息 + * @return result -新创建的客服帐号ID + * @throws WxErrorException 异常 + */ + WxCpKfAccountAddResp addAccount(WxCpKfAccountAdd add) throws WxErrorException; + + /** + * 修改已有的客服帐号,可修改客服名称和头像。 + * + * @param upd 新的客服账号信息 + * @return result wx cp base resp + * @throws WxErrorException 异常 + */ + WxCpBaseResp updAccount(WxCpKfAccountUpd upd) throws WxErrorException; + + /** + * 删除已有的客服帐号 + * + * @param del 要删除的客服帐号 + * @return result wx cp base resp + * @throws WxErrorException 异常 + */ + WxCpBaseResp delAccount(WxCpKfAccountDel del) throws WxErrorException; + + /** + * 获取客服帐号列表,包括所有的客服帐号的客服ID、名称和头像。 + * + * @param offset 分页,偏移量, 默认为0 + * @param limit 分页,预期请求的数据量,默认为100,取值范围 1 ~ 100 + * @return 客服帐号列表 wx cp kf account list resp + * @throws WxErrorException 异常 + */ + WxCpKfAccountListResp listAccount(Integer offset, Integer limit) throws WxErrorException; + + /** + * 企业可通过此接口获取带有不同参数的客服链接,不同客服帐号对应不同的客服链接。获取后,企业可将链接嵌入到网页等场景中, + * 微信用户点击链接即可向对应的客服帐号发起咨询。企业可依据参数来识别用户的咨询来源等 + * + * @param link 参数 + * @return 链接 account link + * @throws WxErrorException 异常 + */ + WxCpKfAccountLinkResp getAccountLink(WxCpKfAccountLink link) throws WxErrorException; + + /** + * 接待人员管理 + * 添加指定客服帐号的接待人员,每个客服帐号目前最多可添加500个接待人员。 + * + * @param openKfid 客服帐号ID + * @param userIdList 接待人员userid列表。第三方应用填密文userid,即open_userid 可填充个数:1 ~ 100。超过100个需分批调用。 + * @return 添加客服账号结果 wx cp kf servicer op resp + * @throws WxErrorException 异常 + */ + 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; + + /** + * 接待人员管理 + * 从客服帐号删除接待人员 + * + * @param openKfid 客服帐号ID + * @param userIdList 接待人员userid列表。第三方应用填密文userid,即open_userid 可填充个数:1 ~ 100。超过100个需分批调用。 + * @return 删除客服账号结果 wx cp kf servicer op resp + * @throws WxErrorException 异常 + */ + 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; + + /** + * 接待人员管理 + * 获取某个客服帐号的接待人员列表 + * + * @param openKfid 客服帐号ID + * @return 接待人员列表 wx cp kf servicer list resp + * @throws WxErrorException 异常 + */ + WxCpKfServicerListResp listServicer(String openKfid) throws WxErrorException; + + /** + * 分配客服会话 + * 获取会话状态 + * + * @param openKfid 客服帐号ID + * @param externalUserId 微信客户的external_userid + * @return service state + * @throws WxErrorException the wx error exception + */ + WxCpKfServiceStateResp getServiceState(String openKfid, String externalUserId) + throws WxErrorException; + + /** + * 分配客服会话 + * 变更会话状态 + * + * @param openKfid 客服帐号ID + * @param externalUserId 微信客户的external_userid + * @param serviceState 变更的目标状态,状态定义和所允许的变更可参考概述中的流程图和表格 + * @param servicerUserId 接待人员的userid。第三方应用填密文userid,即open_userid。当state=3时要求必填,接待人员须处于“正在接待”中。 + * @return 部分状态返回回复语code wx cp kf service state trans resp + * @throws WxErrorException the wx error exception + */ + WxCpKfServiceStateTransResp transServiceState(String openKfid, String externalUserId, + Integer serviceState, String servicerUserId) throws WxErrorException; + + /** + * 读取消息 + * 微信客户发送的消息、接待人员在企业微信回复的消息、发送消息接口发送失败事件(如被用户拒收)、客户点击菜单消息的回复消息, + * 可以通过该接口获取具体的消息内容和事件。不支持读取通过发送消息接口发送的消息。 + * 支持的消息类型:文本、图片、语音、视频、文件、位置、链接、名片、小程序、菜单、事件。 + * + * @param cursor 上一次调用时返回的next_cursor,第一次拉取可以不填。不多于64字节 + * @param token 回调事件返回的token字段,10分钟内有效;可不填,如果不填接口有严格的频率限制。不多于128字节 + * @param limit 期望请求的数据量,默认值和最大值都为1000。 注意:可能会出现返回条数少于limit的情况,需结合返回的has_more字段判断是否继续请求。 + * @param voiceFormat 语音消息类型,0-Amr 1-Silk,默认0。可通过该参数控制返回的语音格式 + * @return 微信消息 wx cp kf msg list resp + * @throws WxErrorException 异常 + */ + @Deprecated + WxCpKfMsgListResp syncMsg(String cursor, String token, Integer limit, Integer voiceFormat) + throws WxErrorException; + + WxCpKfMsgListResp syncMsg(String cursor, String token, Integer limit, Integer voiceFormat,String open_kfid) + throws WxErrorException; + + /** + * 发送消息 + * 当微信客户处于“新接入待处理”或“由智能助手接待”状态下,可调用该接口给用户发送消息。 + * 注意仅当微信客户在主动发送消息给客服后的48小时内,企业可发送消息给客户,最多可发送5条消息;若用户继续发送消息,企业可再次下发消息。 + * 支持发送消息类型:文本、图片、语音、视频、文件、图文、小程序、菜单消息、地理位置。 + * + * @param request 发送信息 + * @return 发送结果 wx cp kf msg send resp + * @throws WxErrorException 异常 + */ + WxCpKfMsgSendResp sendMsg(WxCpKfMsgSendRequest request) throws WxErrorException; + + /** + * 发送欢迎语等事件响应消息 + * 当特定的事件回调消息包含code字段,或通过接口变更到特定的会话状态,会返回code字段。 + * 开发者可以此code为凭证,调用该接口给用户发送相应事件场景下的消息,如客服欢迎语、客服提示语和会话结束语等。 + * 除"用户进入会话事件"以外,响应消息仅支持会话处于获取该code的会话状态时发送,如将会话转入待接入池时获得的code仅能在会话状态为”待接入池排队中“时发送。 + *

+ * 目前支持的事件场景和相关约束如下: + *

+ * 事件场景 允许下发条数 code有效期 支持的消息类型 获取code途径 + * 用户进入会话,用于发送客服欢迎语 1条 20秒 文本、菜单 事件回调 + * 进入接待池,用于发送排队提示语等 1条 48小时 文本 转接会话接口 + * 从接待池接入会话,用于发送非工作 + * 时间的提示语或超时未回复的提示语 + * 等 1条 48小时 文本 事件回调、转接会话接口 + * 结束会话,用于发送结束会话提示语 + * 或满意度评价等 1条 20秒 文本、菜单 事件回调、转接会话接口 + * + * @param request the request + * @return wx cp kf msg send resp + * @throws WxErrorException the wx error exception + */ + WxCpKfMsgSendResp sendMsgOnEvent(WxCpKfMsgSendRequest request) throws WxErrorException; + + /** + * 获取客户基础信息 + * + * @param externalUserIdList the external user id list + * @return wx cp kf customer batch get resp + * @throws WxErrorException the wx error exception + */ + WxCpKfCustomerBatchGetResp customerBatchGet(List externalUserIdList) + throws WxErrorException; + + /** + *

+   * 获取「客户数据统计」企业汇总数据
+   * 通过此接口,可以获取咨询会话数、咨询客户数等企业汇总统计数据
+   * 请求方式:POST(HTTPS)
+   * 请求地址:
+   * https://qyapi.weixin.qq.com/cgi-bin/kf/get_corp_statistic?access_token=ACCESS_TOKEN
+   * 文档地址:
+   * https://developer.work.weixin.qq.com/document/path/95489
+   * 
+ * @param request 查询参数 + * @return 客户数据统计 -企业汇总数据 + * @throws WxErrorException the wx error exception + */ + WxCpKfGetCorpStatisticResp getCorpStatistic(WxCpKfGetCorpStatisticRequest request) throws WxErrorException; + + /** + *
+   * 获取「客户数据统计」接待人员明细数据
+   * 通过此接口,可获取接入人工会话数、咨询会话数等与接待人员相关的统计信息
+   * 请求方式:POST(HTTPS)
+   * 请求地址:
+   * https://qyapi.weixin.qq.com/cgi-bin/kf/get_servicer_statistic?access_token=ACCESS_TOKEN
+   * 文档地址:
+   * https://developer.work.weixin.qq.com/document/path/95490
+   * 
+ * @param request 查询参数 + * @return 客户数据统计 -企业汇总数据 + * @throws WxErrorException the wx error exception + */ + WxCpKfGetServicerStatisticResp getServicerStatistic(WxCpKfGetServicerStatisticRequest request) throws WxErrorException; + + // 「升级服务」配置 + + /** + * 获取配置的专员与客户群 + * + * @return upgrade service config + * @throws WxErrorException the wx error exception + */ + WxCpKfServiceUpgradeConfigResp getUpgradeServiceConfig() throws WxErrorException; + + /** + * 升级专员服务 + * + * @param openKfid 客服帐号ID + * @param externalUserId 微信客户的external_userid + * @param userid 服务专员的userid + * @param wording 推荐语 + * @return wx cp base resp + * @throws WxErrorException the wx error exception + */ + WxCpBaseResp upgradeMemberService(String openKfid, String externalUserId, + String userid, String wording) throws WxErrorException; + + /** + * 升级客户群服务 + * + * @param openKfid 客服帐号ID + * @param externalUserId 微信客户的external_userid + * @param chatId 客户群id + * @param wording 推荐语 + * @return wx cp base resp + * @throws WxErrorException the wx error exception + */ + WxCpBaseResp upgradeGroupchatService(String openKfid, String externalUserId, + String chatId, String wording) throws WxErrorException; + + /** + * 为客户取消推荐 + * + * @param openKfid 客服帐号ID + * @param externalUserId 微信客户的external_userid + * @return wx cp base resp + * @throws WxErrorException the wx error exception + */ + WxCpBaseResp cancelUpgradeService(String openKfid, String externalUserId) + throws WxErrorException; +} diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpLivingService.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpLivingService.java new file mode 100644 index 0000000000..63fabad7a1 --- /dev/null +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpLivingService.java @@ -0,0 +1,124 @@ +package me.chanjar.weixin.cp.api; + +import lombok.NonNull; +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.cp.bean.living.*; + +/** + * 企业微信直播接口. + * 官方文档:https://work.weixin.qq.com/api/doc/90000/90135/93633 + * + * @author Wang_Wong created on 2021-12-21 + */ +public interface WxCpLivingService { + + /** + * 获取微信观看直播凭证 + * 请求方式: POST(HTTPS) + * 请求地址: https://qyapi.weixin.qq.com/cgi-bin/living/get_living_code?access_token=ACCESS_TOKEN + * + * @param openId 用户openid + * @param livingId 直播id + * @return living_code 微信观看直播凭证 + * @throws WxErrorException the wx error exception + */ + String getLivingCode(@NonNull String openId, @NonNull String livingId) throws WxErrorException; + + /** + * 获取直播详情 + * 请求方式:GET(HTTPS) + * {@code 请求地址:https://qyapi.weixin.qq.com/cgi-bin/living/get_living_info?access_token=ACCESS_TOKEN&livingid=LIVINGID} + * + * @param livingId 直播id + * @return 获取的直播详情 living info + * @throws WxErrorException the wx error exception + */ + WxCpLivingInfo getLivingInfo(@NonNull String livingId) throws WxErrorException; + + /** + * 获取直播观看明细 + * 通过该接口可以获取所有观看直播的人员统计 + *

+ * 请求方式:POST(HTTPS) + * 请求地址:https://qyapi.weixin.qq.com/cgi-bin/living/get_watch_stat?access_token=ACCESS_TOKEN + * + * @param livingId 直播id + * @param nextKey 上一次调用时返回的next_key,初次调用可以填”0” + * @return watch stat + * @throws WxErrorException the wx error exception + */ + WxCpWatchStat getWatchStat(@NonNull String livingId, String nextKey) throws WxErrorException; + + /** + * 获取成员直播ID列表 + * 通过此接口可以获取指定成员的所有直播ID + *

+ * 请求方式:POST(HTTPS) + * 请求地址:https://qyapi.weixin.qq.com/cgi-bin/living/get_user_all_livingid?access_token=ACCESS_TOKEN + * + * @param userId 企业成员的userid + * @param cursor 上一次调用时返回的next_cursor,第一次拉取可以不填 + * @param limit 每次拉取的数据量,默认值和最大值都为100 + * @return user all living id + * @throws WxErrorException the wx error exception + */ + WxCpLivingResult.LivingIdResult getUserAllLivingId(@NonNull String userId, String cursor, Integer limit) throws WxErrorException; + + /** + * 获取跳转小程序商城的直播观众信息 + * 通过此接口,开发者可获取跳转小程序商城的直播间(“推广产品”直播)观众id、邀请人id及对应直播间id,以打通卖货直播的“人货场”信息闭环。 + *

+ * 请求方式:POST(HTTPS) + * 请求地址:https://qyapi.weixin.qq.com/cgi-bin/living/get_living_share_info?access_token=ACCESS_TOKEN + * + * @param wwShareCode "推广产品"直播观众跳转小程序商城时会在小程序path中带上ww_share_code=xxxxx参数 + * @return living share info + * @throws WxErrorException the wx error exception + */ + WxCpLivingShareInfo getLivingShareInfo(@NonNull String wwShareCode) throws WxErrorException; + + /** + * 创建预约直播 + * 请求方式: POST(HTTPS) + * 请求地址: https://qyapi.weixin.qq.com/cgi-bin/living/create?access_token=ACCESS_TOKEN + * + * @param request 创建预约直播请求参数. + * @return livingId (直播id) + * @throws WxErrorException the wx error exception + */ + String livingCreate(WxCpLivingCreateRequest request) throws WxErrorException; + + /** + * 修改预约直播 + * 请求方式: POST(HTTPS) + * 请求地址: https://qyapi.weixin.qq.com/cgi-bin/living/modify?access_token=ACCESS_TOKEN + * + * @param request 修改预约直播请求参数. + * @return wx cp living result + * @throws WxErrorException the wx error exception + */ + WxCpLivingResult livingModify(WxCpLivingModifyRequest request) throws WxErrorException; + + /** + * 取消预约直播 + * 请求方式: POST(HTTPS) + * 请求地址: https://qyapi.weixin.qq.com/cgi-bin/living/cancel?access_token=ACCESS_TOKEN + * + * @param livingId 直播id,仅允许取消预约状态下的直播id + * @return wx cp living result + * @throws WxErrorException the wx error exception + */ + WxCpLivingResult livingCancel(@NonNull String livingId) throws WxErrorException; + + /** + * 删除直播回放 + * 请求方式: POST(HTTPS) + * 请求地址: https://qyapi.weixin.qq.com/cgi-bin/living/delete_replay_data?access_token=ACCESS_TOKEN + * + * @param livingId 直播id + * @return wx cp living result + * @throws WxErrorException the wx error exception + */ + WxCpLivingResult deleteReplayData(@NonNull String livingId) 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 a51e04e175..dd5ce594b2 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpMediaService.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpMediaService.java @@ -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; @@ -31,16 +33,62 @@ public interface WxCpMediaService { * @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 + * @throws IOException the io exception */ WxMediaUploadResult upload(String mediaType, String fileType, InputStream inputStream) throws WxErrorException, IOException; + /** + *

+   *   上传多媒体文件.
+   * 
+ * + * @param mediaType 媒体类型, 请看{@link me.chanjar.weixin.common.api.WxConsts} + * @param filename 文件名.例如:wework.txt + * @param url 远程链接 + * @return wx media upload result + * @throws WxErrorException the wx error exception + * @throws IOException the io exception + */ + WxMediaUploadResult upload(String mediaType, String filename, String url) + throws WxErrorException, IOException; + + /** + *
+   *   上传多媒体文件.
+   * 
+ * + * @param mediaType 媒体类型, 请看{@link me.chanjar.weixin.common.api.WxConsts} + * @param file 文件对象, 上传的文件内容 + * @param filename 上传内容的实际文件名.例如:wework.txt + * @return wx media upload result + * @throws WxErrorException the wx error exception + */ + WxMediaUploadResult upload(String mediaType, File file, String filename) throws WxErrorException; + + /** + *
+   *   上传多媒体文件.
+   * 
+ * + * @param mediaType 媒体类型, 请看{@link me.chanjar.weixin.common.api.WxConsts} + * @param inputStream 上传的文件内容 + * @param filename 上传内容的实际文件名.例如:wework.txt + * @return wx media upload result + * @throws WxErrorException the wx error exception + */ + WxMediaUploadResult upload(String mediaType, InputStream inputStream, String filename) throws WxErrorException; + /** * 上传多媒体文件. * * @param mediaType 媒体类型 * @param file 文件对象 - * @see #upload(String, String, InputStream) + * @return the wx media upload result + * @throws WxErrorException the wx error exception + * @see #upload(String, String, InputStream) #upload(String, String, InputStream) */ WxMediaUploadResult upload(String mediaType, File file) throws WxErrorException; @@ -52,7 +100,8 @@ WxMediaUploadResult upload(String mediaType, String fileType, InputStream inputS *
* * @param mediaId 媒体id - * @return 保存到本地的临时文件 + * @return 保存到本地的临时文件 file + * @throws WxErrorException the wx error exception */ File download(String mediaId) throws WxErrorException; @@ -61,13 +110,14 @@ WxMediaUploadResult upload(String mediaType, String fileType, InputStream inputS * 获取高清语音素材. * 可以使用本接口获取从JSSDK的uploadVoice接口上传的临时语音素材,格式为speex,16K采样率。该音频比上文的临时素材获取接口(格式为amr,8K采样率)更加清晰,适合用作语音识别等对音质要求较高的业务。 * 请求方式:GET(HTTPS) - * 请求地址:https://qyapi.weixin.qq.com/cgi-bin/media/get/jssdk?access_token=ACCESS_TOKEN&media_id=MEDIA_ID + * {@code 请求地址:https://qyapi.weixin.qq.com/cgi-bin/media/get/jssdk?access_token=ACCESS_TOKEN&media_id=MEDIA_ID} * 仅企业微信2.4及以上版本支持。 - * 文档地址:https://work.weixin.qq.com/api/doc#90000/90135/90255 + * 文档地址:https://work.weixin.qq.com/api/doc/90000/90135/90255 *
* * @param mediaId 媒体id - * @return 保存到本地的临时文件 + * @return 保存到本地的临时文件 jssdk file + * @throws WxErrorException the wx error exception */ File getJssdkFile(String mediaId) throws WxErrorException; @@ -81,7 +131,25 @@ WxMediaUploadResult upload(String mediaType, String fileType, InputStream inputS * * * @param file 上传的文件对象 - * @return 返回图片url + * @return 返回图片url string + * @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 new file mode 100644 index 0000000000..d761f99d0b --- /dev/null +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpMeetingService.java @@ -0,0 +1,95 @@ +package me.chanjar.weixin.cp.api; + +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.cp.bean.oa.meeting.WxCpMeeting; +import me.chanjar.weixin.cp.bean.oa.meeting.WxCpMeetingUpdateResult; +import me.chanjar.weixin.cp.bean.oa.meeting.WxCpUserMeetingIdResult; + +/** + * 企业微信日程接口. + * 企业和开发者通过会议接口可以便捷地预定及管理会议,用于小组周会、部门例会等场景。 + * 调用接口的应用自动成为会议创建者,也可指定成员作为会议管理员辅助管理。 + * 官方文档:https://developer.work.weixin.qq.com/document/path/93626 + * + * @author wangmeng3486 created on 2023-01-31 + */ +public interface WxCpMeetingService { + /** + * 创建预约会议 + *

+ * 该接口用于创建一个预约会议。 + *

+ * 请求方式: POST(HTTPS) + * 请求地址: https://qyapi.weixin.qq.com/cgi-bin/meeting/create?access_token=ACCESS_TOKEN + * + * @param meeting the meeting + * @return 会议ID string + * @throws WxErrorException the wx error exception + */ + String create(WxCpMeeting meeting) throws WxErrorException; + + /** + * 修改预约会议 + *

+ * 该接口用于修改一个指定的预约会议。。 + *

+ * 请求方式: POST(HTTPS) + * 请求地址: https://qyapi.weixin.qq.com/cgi-bin/meeting/update?access_token=ACCESS_TOKEN + * + * @param meeting the meeting + * @return wx cp meeting update result + * @throws WxErrorException the wx error exception + */ + WxCpMeetingUpdateResult update(WxCpMeeting meeting) throws WxErrorException; + + + /** + * 取消预约会议 + * 该接口用于取消一个指定的预约会议。 + *

+ * 请求方式: POST(HTTPS) + * 请求地址: https://qyapi.weixin.qq.com/cgi-bin/meeting/cancel?access_token=ACCESS_TOKEN + * + * @param meetingId 会议ID + * @throws WxErrorException the wx error exception + */ + void cancel(String meetingId) throws WxErrorException; + + /** + * 获取会议详情 + *

+ * 该接口用于获取指定会议的详情内容。 + *

+ * 请求方式: POST(HTTPS) + * 请求地址: https://qyapi.weixin.qq.com/cgi-bin/oa/meeting/get?access_token=ACCESS_TOKEN + * + * @param meetingId the meeting ids + * @return the details + * @throws WxErrorException the wx error exception + */ + WxCpMeeting getDetail(String meetingId) throws WxErrorException; + + /** + * 获取成员会议ID列表 + * 该接口用于获取指定成员指定时间内的会议ID列表。 + *

+ * 权限说明: + * 只能拉取该应用创建的会议ID + * 自建应用需要配置在“可调用接口的应用”列表 + * 第三方服务商创建应用的时候,需要开启“会议接口权限” + * 代开发自建应用需要授权“会议接口权限” + *

+ * 请求方式: POST(HTTPS) + * 请求地址: https://qyapi.weixin.qq.com/cgi-bin/meeting/get_user_meetingid?access_token=ACCESS_TOKEN + * + * @param userId 企业成员的userid + * @param cursor 上一次调用时返回的cursor,初次调用可以填"0" + * @param limit 每次拉取的数据量,默认值和最大值都为100 + * @param beginTime 开始时间 + * @param endTime 结束时间,时间跨度不超过180天。如果begin_time和end_time都没填的话,默认end_time为当前时间 + * @return result of listUserMeetingIds + * @throws WxErrorException the wx error exception + */ + WxCpUserMeetingIdResult getUserMeetingIds(String userId, String cursor, Integer limit, + Long beginTime, Long endTime) throws WxErrorException; +} diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpMenuService.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpMenuService.java index 309b981211..07f300dd14 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpMenuService.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpMenuService.java @@ -22,7 +22,8 @@ public interface WxCpMenuService { * * * @param menu 菜单对象 - * @see #create(Integer, WxMenu) + * @throws WxErrorException the wx error exception + * @see #create(Integer, WxMenu) #create(Integer, WxMenu) */ void create(WxMenu menu) throws WxErrorException; @@ -36,7 +37,8 @@ public interface WxCpMenuService { * * @param agentId 企业号应用的id * @param menu 菜单对象 - * @see #create(me.chanjar.weixin.common.bean.menu.WxMenu) + * @throws WxErrorException the wx error exception + * @see #create(me.chanjar.weixin.common.bean.menu.WxMenu) #create(me.chanjar.weixin.common.bean.menu.WxMenu) */ void create(Integer agentId, WxMenu menu) throws WxErrorException; @@ -48,7 +50,8 @@ public interface WxCpMenuService { * 注意: 这个方法使用WxCpConfigStorage里的agentId * * - * @see #delete(Integer) + * @throws WxErrorException the wx error exception + * @see #delete(Integer) #delete(Integer) */ void delete() throws WxErrorException; @@ -61,7 +64,8 @@ public interface WxCpMenuService { * * * @param agentId 企业号应用的id - * @see #delete() + * @throws WxErrorException the wx error exception + * @see #delete() #delete() */ void delete(Integer agentId) throws WxErrorException; @@ -73,7 +77,9 @@ public interface WxCpMenuService { * 注意: 这个方法使用WxCpConfigStorage里的agentId * * - * @see #get(Integer) + * @return the wx menu + * @throws WxErrorException the wx error exception + * @see #get(Integer) #get(Integer) */ WxMenu get() throws WxErrorException; @@ -86,7 +92,9 @@ public interface WxCpMenuService { * * * @param agentId 企业号应用的id - * @see #get() + * @return the wx menu + * @throws WxErrorException the wx error exception + * @see #get() #get() */ WxMenu get(Integer agentId) throws WxErrorException; } diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpMessageService.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpMessageService.java new file mode 100644 index 0000000000..534cc89b36 --- /dev/null +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpMessageService.java @@ -0,0 +1,81 @@ +package me.chanjar.weixin.cp.api; + +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.cp.bean.message.*; + +/** + * 消息推送接口. + * + * @author Binary Wang created on 2020 -08-30 + */ +public interface WxCpMessageService { + /** + *

+   * 发送消息
+   * 详情请见: https://work.weixin.qq.com/api/doc/90000/90135/90236
+   * 
+ * + * @param message 要发送的消息对象 + * @return the wx cp message send result + * @throws WxErrorException the wx error exception + */ + WxCpMessageSendResult send(WxCpMessage message) throws WxErrorException; + + /** + *
+   * 查询应用消息发送统计
+   * 请求方式:POST(HTTPS)
+   * 请求地址:https://qyapi.weixin.qq.com/cgi-bin/message/get_statistics?access_token=ACCESS_TOKEN
+   *
+   * 详情请见: https://work.weixin.qq.com/api/doc/90000/90135/92369
+   * 
+ * + * @param timeType 查询哪天的数据,0:当天;1:昨天。默认为0。 + * @return 统计结果 statistics + * @throws WxErrorException the wx error exception + */ + WxCpMessageSendStatistics getStatistics(int timeType) throws WxErrorException; + + /** + *
+   * 互联企业的应用支持推送文本、图片、视频、文件、图文等类型。
+   *
+   * 请求地址:https://qyapi.weixin.qq.com/cgi-bin/linkedcorp/message/send?access_token=ACCESS_TOKEN
+   * 文章地址:https://work.weixin.qq.com/api/doc/90000/90135/90250
+   * 
+ * + * @param message 要发送的消息对象 + * @return the wx cp message send result + * @throws WxErrorException the wx error exception + */ + WxCpLinkedCorpMessageSendResult sendLinkedCorpMessage(WxCpLinkedCorpMessage message) throws WxErrorException; + + /** + * 发送「学校通知」 + * https://developer.work.weixin.qq.com/document/path/92321 + *

+ * 学校可以通过此接口来给家长发送不同类型的学校通知,来满足多种场景下的学校通知需求。目前支持的消息类型为文本、图片、语音、视频、文件、图文。 + *

+ * 请求方式:POST(HTTPS) + * 请求地址: https://qyapi.weixin.qq.com/cgi-bin/externalcontact/message/send?access_token=ACCESS_TOKEN + * + * @param message 要发送的消息对象 + * @return wx cp school contact message send result + * @throws WxErrorException the wx error exception + */ + WxCpSchoolContactMessageSendResult sendSchoolContactMessage(WxCpSchoolContactMessage message) throws WxErrorException; + + /** + *

+   * 撤回应用消息
+   *
+   * 请求地址: https://qyapi.weixin.qq.com/cgi-bin/message/recall?access_token=ACCESS_TOKEN
+   * 文档地址: https://developer.work.weixin.qq.com/document/path/94867
+   * 
+ * + * @param msgId 消息id + * @throws WxErrorException 异常 + */ + void recall(String msgId) throws WxErrorException; + +} diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpMsgAuditService.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpMsgAuditService.java new file mode 100644 index 0000000000..221caf2e70 --- /dev/null +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpMsgAuditService.java @@ -0,0 +1,132 @@ +package me.chanjar.weixin.cp.api; + +import lombok.NonNull; +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.cp.bean.msgaudit.*; + +import java.util.List; +import java.util.function.Consumer; + +/** + * 会话内容存档接口. + * 官方文档:https://developer.work.weixin.qq.com/document/path/91360 + *

+ * 如需自行实现,亦可调用Finance类库函数,进行实现: + * com.tencent.wework.Finance + * + * @author Wang_Wong created on 2022-01-14 + */ +public interface WxCpMsgAuditService { + + /** + * 拉取聊天记录函数 + * + * @param seq 从指定的seq开始拉取消息,注意的是返回的消息从seq+1开始返回,seq为之前接口返回的最大seq值。首次使用请使用seq:0 + * @param limit 一次拉取的消息条数,最大值1000条,超过1000条会返回错误 + * @param proxy 使用代理的请求,需要传入代理的链接。如:socks5://10.0.0.1:8081 或者 http://10.0.0.1:8081,如果没有传null + * @param passwd 代理账号密码,需要传入代理的账号密码。如 user_name:passwd_123,如果没有传null + * @param timeout 超时时间,根据实际需要填写 + * @return 返回是否调用成功 chat datas + * @throws Exception the exception + */ + WxCpChatDatas getChatDatas(long seq, @NonNull long limit, String proxy, String passwd, @NonNull long timeout) throws Exception; + + /** + * 获取解密的聊天数据Model + * + * @param sdk getChatDatas()获取到的sdk + * @param chatData getChatDatas()获取到的聊天数据 + * @param pkcs1 使用什么方式进行解密,1代表使用PKCS1进行解密,2代表PKCS8进行解密 ... + * @return 解密后的聊天数据 decrypt data + * @throws Exception the exception + */ + WxCpChatModel getDecryptData(@NonNull long sdk, @NonNull WxCpChatDatas.WxCpChatData chatData, + @NonNull Integer pkcs1) throws Exception; + + /** + * 获取解密的聊天数据明文 + * + * @param sdk getChatDatas()获取到的sdk + * @param chatData getChatDatas()获取到的聊天数据 + * @param pkcs1 使用什么方式进行解密,1代表使用PKCS1进行解密,2代表PKCS8进行解密 ... + * @return 解密后的明文 chat plain text + * @throws Exception the exception + */ + String getChatPlainText(@NonNull long sdk, @NonNull WxCpChatDatas.WxCpChatData chatData, @NonNull Integer pkcs1) throws Exception; + + /** + * 获取媒体文件 + * 针对图片、文件等媒体数据,提供sdk接口拉取数据内容。 + *

+ * 注意: + * 根据上面返回的文件类型,拼接好存放文件的绝对路径即可。此时绝对路径写入文件流,来达到获取媒体文件的目的。 + * 详情可以看官方文档,亦可阅读此接口源码。 + * + * @param sdk getChatDatas()获取到的sdk,注意,每次获取的sdk会不一样 + * @param sdkfileid 消息体内容中的sdkfileid信息 + * @param proxy 使用代理的请求,需要传入代理的链接。如:socks5://10.0.0.1:8081 或者 http://10.0.0.1:8081,如果没有传null + * @param passwd 代理账号密码,需要传入代理的账号密码。如 user_name:passwd_123,如果没有传null + * @param timeout 超时时间,分片数据需累加到文件存储。单次最大返回512K字节,如果文件比较大,自行设置长一点,比如timeout=10000 + * @param targetFilePath 目标文件绝对路径+实际文件名,比如:/usr/local/file/20220114/474f866b39d10718810d55262af82662.gif + * @throws WxErrorException the wx error exception + */ + void getMediaFile(@NonNull long sdk, @NonNull String sdkfileid, String proxy, String passwd, @NonNull long timeout, + @NonNull String targetFilePath) throws WxErrorException; + + /** + * 获取媒体文件 传入一个lambda,each所有的数据分片byte[],更加灵活 + * 针对图片、文件等媒体数据,提供sdk接口拉取数据内容。 + * 详情可以看官方文档,亦可阅读此接口源码。 + * + * @param sdk getChatDatas()获取到的sdk,注意,每次获取的sdk会不一样 + * @param sdkfileid 消息体内容中的sdkfileid信息 + * @param proxy 使用代理的请求,需要传入代理的链接。如:socks5://10.0.0.1:8081 或者 http://10.0.0.1:8081,如果没有传null + * @param passwd 代理账号密码,需要传入代理的账号密码。如 user_name:passwd_123,如果没有传null + * @param timeout 超时时间,分片数据需累加到文件存储。单次最大返回512K字节,如果文件比较大,自行设置长一点,比如timeout=10000 + * @param action 传入一个lambda,each所有的数据分片 + * @throws WxErrorException the wx error exception + */ + void getMediaFile(@NonNull long sdk, @NonNull String sdkfileid, String proxy, String passwd, @NonNull long timeout, + @NonNull Consumer action) throws WxErrorException; + + /** + * 获取会话内容存档开启成员列表 + * 企业可通过此接口,获取企业开启会话内容存档的成员列表 + *

+ * 请求方式:POST(HTTPS) + * 请求地址:https://qyapi.weixin.qq.com/cgi-bin/msgaudit/get_permit_user_list?access_token=ACCESS_TOKEN + * + * @param type 拉取对应版本的开启成员列表。1表示办公版;2表示服务版;3表示企业版。非必填,不填写的时候返回全量成员列表。 + * @return permit user list + * @throws WxErrorException the wx error exception + */ + List getPermitUserList(Integer type) throws WxErrorException; + + /** + * 获取会话内容存档内部群信息 + * 企业可通过此接口,获取会话内容存档本企业的内部群信息,包括群名称、群主id、公告、群创建时间以及所有群成员的id与加入时间。 + *

+ * 请求方式:POST(HTTPS) + * 请求地址:https://qyapi.weixin.qq.com/cgi-bin/msgaudit/groupchat/get?access_token=ACCESS_TOKEN + * + * @param roomid 待查询的群id + * @return group chat + * @throws WxErrorException the wx error exception + */ + WxCpGroupChat getGroupChat(@NonNull String roomid) throws WxErrorException; + + /** + * 获取会话同意情况 + * 企业可通过下述接口,获取会话中外部成员的同意情况 + *

+ * 单聊请求地址:https://qyapi.weixin.qq.com/cgi-bin/msgaudit/check_single_agree?access_token=ACCESS_TOKEN + *

+ * 请求方式:POST(HTTPS) + * + * @param checkAgreeRequest 待查询的会话信息 + * @return wx cp agree info + * @throws WxErrorException the wx error exception + */ + WxCpAgreeInfo checkSingleAgree(@NonNull WxCpCheckAgreeRequest checkAgreeRequest) throws WxErrorException; + +} diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpOAuth2Service.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpOAuth2Service.java index 7c42ea63fc..1824196720 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpOAuth2Service.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpOAuth2Service.java @@ -3,12 +3,15 @@ import me.chanjar.weixin.common.error.WxErrorException; import me.chanjar.weixin.cp.bean.WxCpOauth2UserInfo; import me.chanjar.weixin.cp.bean.WxCpUserDetail; +import me.chanjar.weixin.cp.bean.workbench.WxCpSecondVerificationInfo; /** *

  * OAuth2相关管理接口.
  *  Created by BinaryWang on 2017/6/24.
  * 
+ *

+ * 文档1:https://developer.work.weixin.qq.com/document/path/91856 * * @author Binary Wang */ @@ -20,7 +23,7 @@ public interface WxCpOAuth2Service { * * * @param state 状态码 - * @return url + * @return url string */ String buildAuthorizationUrl(String state); @@ -32,7 +35,7 @@ public interface WxCpOAuth2Service { * * @param redirectUri 跳转链接地址 * @param state 状态码 - * @return url + * @return url string */ String buildAuthorizationUrl(String redirectUri, String state); @@ -45,7 +48,7 @@ public interface WxCpOAuth2Service { * @param redirectUri 跳转链接地址 * @param state 状态码 * @param scope 取值参考me.chanjar.weixin.common.api.WxConsts.OAuth2Scope类 - * @return url + * @return url string */ String buildAuthorizationUrl(String redirectUri, String state, String scope); @@ -59,9 +62,9 @@ public interface WxCpOAuth2Service { * * * @param code 微信oauth授权返回的代码 - * @return WxCpOauth2UserInfo + * @return WxCpOauth2UserInfo user info * @throws WxErrorException 异常 - * @see #getUserInfo(Integer, String) + * @see #getUserInfo(Integer, String) #getUserInfo(Integer, String) */ WxCpOauth2UserInfo getUserInfo(String code) throws WxErrorException; @@ -78,27 +81,75 @@ public interface WxCpOAuth2Service { * * @param agentId 企业号应用的id * @param code 通过成员授权获取到的code,最大为512字节。每次成员授权带上的code将不一样,code只能使用一次,5分钟未被使用自动过期。 - * @return WxCpOauth2UserInfo + * @return WxCpOauth2UserInfo user info * @throws WxErrorException 异常 - * @see #getUserInfo(String) + * @see #getUserInfo(String) #getUserInfo(String) */ WxCpOauth2UserInfo getUserInfo(Integer agentId, String code) throws WxErrorException; /** + * 获取家校访问用户身份 + * 该接口用于根据code获取家长或者学生信息 *

-   * 使用user_ticket获取成员详情.
+   * 请求方式:GET(HTTPS)
+   * {@code 请求地址:https://qyapi.weixin.qq.com/cgi-bin/school/getuserinfo?access_token=ACCESS_TOKEN&code=CODE}
+   * 
+ * + * @param code the code + * @return school user info + * @throws WxErrorException the wx error exception + */ + WxCpOauth2UserInfo getSchoolUserInfo(String code) throws WxErrorException; + + /** + *
+   * 使用user_ticket获取成员详情
    *
-   * 文档地址:https://work.weixin.qq.com/api/doc#10028/%E4%BD%BF%E7%94%A8user_ticket%E8%8E%B7%E5%8F%96%E6%88%90%E5%91%98%E8%AF%A6%E6%83%85
+   * 文档地址:https://developer.work.weixin.qq.com/document/path/95833
    * 请求方式:POST(HTTPS)
-   * 请求地址:https://qyapi.weixin.qq.com/cgi-bin/user/getuserdetail?access_token=ACCESS_TOKEN
+   * 请求地址:https://qyapi.weixin.qq.com/cgi-bin/auth/getuserdetail?access_token=ACCESS_TOKEN
    *
-   * 权限说明:
-   * 需要有对应应用的使用权限,且成员必须在授权应用的可见范围内。
+   * 注意: 原/cgi-bin/user/getuserdetail接口的url已变更为/cgi-bin/auth/getuserdetail,旧接口暂时还可以使用,但建议使用新接口
+   *
+   * 权限说明:需要有对应应用的使用权限,且成员必须在授权应用的可见范围内。
+   * 适用范围:企业内部开发、服务商代开发
    * 
* * @param userTicket 成员票据 - * @return WxCpUserDetail + * @return WxCpUserDetail user detail * @throws WxErrorException 异常 */ WxCpUserDetail getUserDetail(String userTicket) throws WxErrorException; + + /** + *
+   * 获取用户登录身份
+   * {@code https://qyapi.weixin.qq.com/cgi-bin/auth/getuserinfo?access_token=ACCESS_TOKEN&code=CODE}
+   * 该接口可使用用户登录成功颁发的code来获取成员信息,适用于自建应用与代开发应用
+   *
+   * 注意: 旧的/user/getuserinfo 接口的url已变更为auth/getuserinfo,不过旧接口依旧可以使用,建议是关注新接口即可
+   *
+   * 适用范围:身份验证中网页授权开发和企业微信Web登录的获取用户登录身份
+   * 
+ * + * @param code 通过成员授权获取到的code,最大为512字节。每次成员授权带上的code将不一样,code只能使用一次,5分钟未被使用自动过期。 + * @return WxCpOauth2UserInfo user info + * @throws WxErrorException 异常 + * @see #getUserInfo(Integer, String) #getUserInfo(Integer, String) + */ + WxCpOauth2UserInfo getAuthUserInfo(String code) throws WxErrorException; + + /** + * 获取用户二次验证信息 + *
+   * api: https://qyapi.weixin.qq.com/cgi-bin/auth/get_tfa_info?access_token=ACCESS_TOKEN
+   * 权限说明:仅『通讯录同步』或者自建应用可调用,如用自建应用调用,用户需要在二次验证范围和应用可见范围内。
+   * 并发限制:20
+   * 
+ * + * @param code 用户进入二次验证页面时,企业微信颁发的code,每次成员授权带上的code将不一样,code只能使用一次,5分钟未被使用自动过期 + * @return 二次验证授权码,开发者可以调用通过二次验证接口,解锁企业微信终端.tfa_code有效期五分钟,且只能使用一次。 + * @throws WxErrorException 微信错误异常 + */ + WxCpSecondVerificationInfo getTfaInfo(String code) throws WxErrorException; } diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpOaAgentService.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpOaAgentService.java new file mode 100644 index 0000000000..6b8b98877b --- /dev/null +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpOaAgentService.java @@ -0,0 +1,28 @@ +package me.chanjar.weixin.cp.api; + +import lombok.NonNull; +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.cp.bean.oa.selfagent.WxCpOpenApprovalData; + +/** + * 企业微信自建应用接口. + * https://developer.work.weixin.qq.com/document/path/90269 + * + * @author Wang_Wong created on 2022-04-06 + */ +public interface WxCpOaAgentService { + + /** + * 查询第三方应用审批申请当前状态 + * 开发者也可主动查询审批单的当前审批状态。 + *

+ * 请求方式: POST(HTTPS) + * 请求地址: https://qyapi.weixin.qq.com/cgi-bin/corp/getopenapprovaldata?access_token=ACCESS_TOKEN + * + * @param thirdNo the third no + * @return open approval data + * @throws WxErrorException the wx error exception + */ + WxCpOpenApprovalData getOpenApprovalData(@NonNull String thirdNo) throws WxErrorException; + +} diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpOaCalendarService.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpOaCalendarService.java new file mode 100644 index 0000000000..50d5e8d946 --- /dev/null +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpOaCalendarService.java @@ -0,0 +1,82 @@ +package me.chanjar.weixin.cp.api; + +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.cp.bean.oa.calendar.WxCpOaCalendar; + +import java.util.List; + +/** + * 企业微信日历接口. + * + * @author Binary Wang created on 2020-09-20 + */ +public interface WxCpOaCalendarService { + /** + * 创建日历. + *

+   * 该接口用于通过应用在企业内创建一个日历。
+   * 注: 企业微信需要更新到3.0.2及以上版本
+   * 请求方式: POST(HTTPS)
+   * 请求地址: https://qyapi.weixin.qq.com/cgi-bin/oa/calendar/add?access_token=ACCESS_TOKEN
+   *
+   * 文档地址:https://work.weixin.qq.com/api/doc/90000/90135/92618
+   * 
+ * + * @param calendar 日历对象 + * @return 日历ID string + * @throws WxErrorException . + */ + String add(WxCpOaCalendar calendar) throws WxErrorException; + + /** + * 更新日历. + *
+   * 该接口用于修改指定日历的信息。
+   * 注意,更新操作是覆盖式,而不是增量式
+   * 企业微信需要更新到3.0.2及以上版本
+   * 请求方式: POST(HTTPS)
+   * 请求地址: https://qyapi.weixin.qq.com/cgi-bin/oa/calendar/update?access_token=ACCESS_TOKEN
+   *
+   * 文档地址:https://work.weixin.qq.com/api/doc/90000/90135/92619
+   * 
+ * + * @param calendar 日历对象 + * @throws WxErrorException . + */ + void update(WxCpOaCalendar calendar) throws WxErrorException; + + /** + * 获取日历. + *
+   * 该接口用于获取应用在企业内创建的日历信息。
+   *
+   * 注: 企业微信需要更新到3.0.2及以上版本
+   *
+   * 请求方式: POST(HTTPS)
+   * 请求地址: https://qyapi.weixin.qq.com/cgi-bin/oa/calendar/get?access_token=ACCESS_TOKEN
+   *
+   * 文档地址:https://work.weixin.qq.com/api/doc/90000/90135/92621
+   * 
+ * + * @param calIds 日历id列表 + * @return 日历对象列表 list + * @throws WxErrorException . + */ + List get(List calIds) throws WxErrorException; + + /** + * 删除日历. + *
+   * 该接口用于删除指定日历。
+   * 注: 企业微信需要更新到3.0.2及以上版本
+   * 请求方式: POST(HTTPS)
+   * 请求地址: https://qyapi.weixin.qq.com/cgi-bin/oa/calendar/del?access_token=ACCESS_TOKEN
+   *
+   * 文档地址:https://work.weixin.qq.com/api/doc/90000/90135/92620
+   * 
+ * + * @param calId 日历id + * @throws WxErrorException . + */ + void delete(String calId) throws WxErrorException; +} diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpOaMailService.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpOaMailService.java new file mode 100644 index 0000000000..07786080fd --- /dev/null +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpOaMailService.java @@ -0,0 +1,57 @@ +package me.chanjar.weixin.cp.api; + +import lombok.NonNull; +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.cp.bean.WxCpBaseResp; +import me.chanjar.weixin.cp.bean.oa.mail.WxCpMailCommonSendRequest; +import me.chanjar.weixin.cp.bean.oa.mail.WxCpMailMeetingSendRequest; +import me.chanjar.weixin.cp.bean.oa.mail.WxCpMailScheduleSendRequest; + +/** + * 企业微信y邮件相关接口. + * 邮件 + * + * @author Hugo + */ +public interface WxCpOaMailService { + + /** + * 发送普通邮件 + * 应用可以通过该接口发送普通邮件,支持附件能力。 + *

+ * 请求方式:POST(HTTPS) + * 请求地址: ... + * + * @param request 发送普通邮件请求参数 + * @return wx cp base resp + * @throws WxErrorException the wx error exception + */ + WxCpBaseResp mailCommonSend(@NonNull WxCpMailCommonSendRequest request) throws WxErrorException; + + /** + * 发送日程邮件 + * 应用可以通过该接口发送日程邮件。 + *

+ * 请求方式:POST(HTTPS) + * 请求地址: ... + * + * @param request 发送日程邮件请求参数 + * @return wx cp base resp + * @throws WxErrorException the wx error exception + */ + WxCpBaseResp mailScheduleSend(@NonNull WxCpMailScheduleSendRequest request) throws WxErrorException; + + /** + * 发送会议邮件 + * 应用可以通过该接口发送会议邮件。 + *

+ * 请求方式:POST(HTTPS) + * 请求地址: ... + * + * @param request 发送会议邮件请求参数 + * @return wx cp base resp + * @throws WxErrorException the wx error exception + */ + WxCpBaseResp mailMeetingSend(@NonNull WxCpMailMeetingSendRequest request) throws WxErrorException; + +} diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpOaMeetingRoomService.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpOaMeetingRoomService.java new file mode 100644 index 0000000000..cc039fd9f5 --- /dev/null +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpOaMeetingRoomService.java @@ -0,0 +1,173 @@ +package me.chanjar.weixin.cp.api; + +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.cp.bean.oa.meetingroom.*; + +import java.util.List; + +/** + * 企业微信会议室接口. + * + * @author lm93129 created on 2022年8月12日22:33:36 + */ +public interface WxCpOaMeetingRoomService { + /** + * 创建会议室. + *

+   * 该接口用于通过应用在企业内创建一个会议室。
+   * 请求方式: POST(HTTPS)
+   * 请求地址: https://qyapi.weixin.qq.com/cgi-bin/oa/meetingroom/add?access_token=ACCESS_TOKEN
+   *
+   * 文档地址:https://developer.work.weixin.qq.com/document/path/93619
+   * 
+ * + * @param meetingRoom 会议室对象 + * @return 会议室ID string + * @throws WxErrorException . + */ + String addMeetingRoom(WxCpOaMeetingRoom meetingRoom) throws WxErrorException; + + /** + * 查询会议室. + *
+   * 该接口用于通过应用在企业内查询会议室列表。
+   * 请求方式: POST(HTTPS)
+   * 请求地址: https://qyapi.weixin.qq.com/cgi-bin/oa/meetingroom/list?access_token=ACCESS_TOKEN
+   *
+   * 文档地址:https://developer.work.weixin.qq.com/document/path/93619
+   * 
+ * + * @param meetingRoomRequest 会议室查询对象 + * @return 会议室ID list + * @throws WxErrorException . + */ + List listMeetingRoom(WxCpOaMeetingRoom meetingRoomRequest) throws WxErrorException; + + /** + * 编辑会议室. + *
+   * 该接口用于通过应用在企业内编辑会议室。
+   * 请求方式: POST(HTTPS)
+   * 请求地址: https://qyapi.weixin.qq.com/cgi-bin/oa/meetingroom/edit?access_token=ACCESS_TOKEN
+   *
+   * 文档地址:https://developer.work.weixin.qq.com/document/path/93619
+   * 
+ * + * @param meetingRoom 会议室对象 + * @throws WxErrorException . + */ + void editMeetingRoom(WxCpOaMeetingRoom meetingRoom) throws WxErrorException; + + /** + * 删除会议室. + *
+   * 企业可通过此接口删除指定的会议室。
+   * 请求方式: POST(HTTPS)
+   * 请求地址: https://qyapi.weixin.qq.com/cgi-bin/oa/meetingroom/del?access_token=ACCESS_TOKEN
+   *
+   * 文档地址:https://developer.work.weixin.qq.com/document/path/93619
+   * 
+ * + * @param meetingRoomId 会议室ID + * @throws WxErrorException . + */ + void deleteMeetingRoom(Integer meetingRoomId) throws WxErrorException; + + /** + * 查询会议室的预定信息. + *
+   * 企业可通过此接口查询相关会议室在指定时间段的预定情况,如是否已被预定,预定者的userid等信息,不支持跨天查询。
+   * 请求方式: POST(HTTPS)
+   * 请求地址: https://qyapi.weixin.qq.com/cgi-bin/oa/meetingroom/get_booking_info?access_token=ACCESS_TOKEN
+   *
+   * 文档地址:https://developer.work.weixin.qq.com/document/path/93620
+   * 
+ * + * @param wxCpOaMeetingRoomBookingInfoRequest 会议室预定信息查询对象 + * @return 会议室预定信息 + * @throws WxErrorException . + */ + WxCpOaMeetingRoomBookingInfoResult getMeetingRoomBookingInfo(WxCpOaMeetingRoomBookingInfoRequest wxCpOaMeetingRoomBookingInfoRequest) throws WxErrorException; + + /** + * 预定会议室. + *
+   * 企业可通过此接口预定会议室并自动关联日程。
+   * 请求方式: POST(HTTPS)
+   * 请求地址: https://qyapi.weixin.qq.com/cgi-bin/oa/meetingroom/book?access_token=ACCESS_TOKEN
+   *
+   * 文档地址:https://developer.work.weixin.qq.com/document/path/93620
+   * 
+ * + * @param wxCpOaMeetingRoomBookRequest 会议室预定对象 + * @return 预定结果 + * @throws WxErrorException . + */ + WxCpOaMeetingRoomBookResult bookingMeetingRoom(WxCpOaMeetingRoomBookRequest wxCpOaMeetingRoomBookRequest) throws WxErrorException; + + /** + * 通过日程预定会议室. + *
+   * 企业可通过此接口为指定日程预定会议室,支持重复日程预定。
+   * 请求方式: POST(HTTPS)
+   * 请求地址: https://qyapi.weixin.qq.com/cgi-bin/oa/meetingroom/book_by_schedule?access_token=ACCESS_TOKEN
+   *
+   * 文档地址:https://developer.work.weixin.qq.com/document/path/93620
+   * 
+ * + * @param wxCpOaMeetingRoomBookByScheduleRequest 会议室预定对象 + * @return 预定结果 + * @throws WxErrorException . + */ + WxCpOaMeetingRoomBookResult bookingMeetingRoomBySchedule(WxCpOaMeetingRoomBookByScheduleRequest wxCpOaMeetingRoomBookByScheduleRequest) throws WxErrorException; + + /** + * 通过会议预定会议室. + *
+   * 企业可通过此接口为指定会议预定会议室,支持重复会议预定。
+   * 请求方式: POST(HTTPS)
+   * 请求地址: https://qyapi.weixin.qq.com/cgi-bin/oa/meetingroom/book_by_meeting?access_token=ACCESS_TOKEN
+   *
+   * 文档地址:https://developer.work.weixin.qq.com/document/path/93620
+   * 
+ * + * @param wxCpOaMeetingRoomBookByMeetingRequest 会议室预定对象 + * @return 预定结果 + * @throws WxErrorException . + */ + WxCpOaMeetingRoomBookResult bookingMeetingRoomByMeeting(WxCpOaMeetingRoomBookByMeetingRequest wxCpOaMeetingRoomBookByMeetingRequest) throws WxErrorException; + + + /** + * 取消预定会议室. + *
+   * 企业可通过此接口取消会议室的预定
+   * 请求方式: POST(HTTPS)
+   * 请求地址: https://qyapi.weixin.qq.com/cgi-bin/oa/meetingroom/cancel_book?access_token=ACCESS_TOKEN
+   *
+   * 文档地址:https://developer.work.weixin.qq.com/document/path/93620
+   * 
+ * + * @param wxCpOaMeetingRoomCancelBookRequest 取消预定会议室对象 + * @throws WxErrorException . + */ + void cancelBookMeetingRoom(WxCpOaMeetingRoomCancelBookRequest wxCpOaMeetingRoomCancelBookRequest) throws WxErrorException; + + + /** + * 根据会议室预定ID查询预定详情. + *
+   * 企业可通过此接口根据预定id查询相关会议室的预定情况
+   * 请求方式: POST(HTTPS)
+   * 请求地址: https://qyapi.weixin.qq.com/cgi-bin/oa/meetingroom/bookinfo/get?access_token=ACCESS_TOKEN
+   *
+   * 文档地址:https://developer.work.weixin.qq.com/document/path/93620
+   * 
+ * + * @param wxCpOaMeetingRoomBookingInfoByBookingIdRequest 根据会议室预定ID查询预定详情对象 + * @return 预定详情 + * @throws WxErrorException . + */ + WxCpOaMeetingRoomBookingInfoByBookingIdResult getBookingInfoByBookingId(WxCpOaMeetingRoomBookingInfoByBookingIdRequest wxCpOaMeetingRoomBookingInfoByBookingIdRequest) throws WxErrorException; + +} diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpOaScheduleService.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpOaScheduleService.java new file mode 100644 index 0000000000..70c108a059 --- /dev/null +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpOaScheduleService.java @@ -0,0 +1,87 @@ +package me.chanjar.weixin.cp.api; + +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.cp.bean.oa.WxCpOaSchedule; + +import java.util.List; + +/** + * 企业微信日程接口. + * 官方文档:https://work.weixin.qq.com/api/doc/90000/90135/93648 + * + * @author Binary Wang created on 2020 -12-25 + */ +public interface WxCpOaScheduleService { + /** + * 创建日程 + *

+ * 该接口用于在日历中创建一个日程。 + *

+ * 请求方式: POST(HTTPS) + * 请求地址: https://qyapi.weixin.qq.com/cgi-bin/oa/schedule/add?access_token=ACCESS_TOKEN + * + * @param schedule the schedule + * @param agentId 授权方安装的应用agentid。仅旧的第三方多应用套件需要填此参数 + * @return 日程ID string + * @throws WxErrorException the wx error exception + */ + String add(WxCpOaSchedule schedule, Integer agentId) throws WxErrorException; + + /** + * 更新日程 + *

+ * 该接口用于在日历中更新指定的日程。 + *

+ * 注意,更新操作是覆盖式,而不是增量式 + * 不可更新组织者和日程所属日历ID + *

+ * 请求方式: POST(HTTPS) + * 请求地址: https://qyapi.weixin.qq.com/cgi-bin/oa/schedule/update?access_token=ACCESS_TOKEN + * + * @param schedule the schedule + * @throws WxErrorException the wx error exception + */ + void update(WxCpOaSchedule schedule) throws WxErrorException; + + /** + * 获取日程详情 + *

+ * 该接口用于获取指定的日程详情。 + *

+ * 请求方式: POST(HTTPS) + * 请求地址: https://qyapi.weixin.qq.com/cgi-bin/oa/schedule/get?access_token=ACCESS_TOKEN + * + * @param scheduleIds the schedule ids + * @return the details + * @throws WxErrorException the wx error exception + */ + List getDetails(List scheduleIds) throws WxErrorException; + + /** + * 取消日程 + * 该接口用于取消指定的日程。 + *

+ * 请求方式: POST(HTTPS) + * 请求地址: https://qyapi.weixin.qq.com/cgi-bin/oa/schedule/del?access_token=ACCESS_TOKEN + * + * @param scheduleId 日程id + * @throws WxErrorException the wx error exception + */ + void delete(String scheduleId) throws WxErrorException; + + /** + * 获取日历下的日程列表 + * 该接口用于获取指定的日历下的日程列表。 + * 仅可获取应用自己创建的日历下的日程。 + *

+ * 请求方式: POST(HTTPS) + * 请求地址: https://qyapi.weixin.qq.com/cgi-bin/oa/schedule/get_by_calendar?access_token=ACCESS_TOKEN + * + * @param calId 日历ID + * @param offset 分页,偏移量, 默认为0 + * @param limit 分页,预期请求的数据量,默认为500,取值范围 1 ~ 1000 + * @return the string + * @throws WxErrorException the wx error exception + */ + List listByCalendar(String calId, Integer offset, Integer limit) throws WxErrorException; +} diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpOaService.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpOaService.java index c6f90d3e64..3494dcfa4e 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpOaService.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpOaService.java @@ -1,10 +1,9 @@ package me.chanjar.weixin.cp.api; +import lombok.NonNull; import me.chanjar.weixin.common.error.WxErrorException; -import me.chanjar.weixin.cp.bean.WxCpApprovalDataResult; -import me.chanjar.weixin.cp.bean.WxCpCheckinData; -import me.chanjar.weixin.cp.bean.WxCpCheckinOption; -import me.chanjar.weixin.cp.bean.WxCpDialRecord; +import me.chanjar.weixin.cp.bean.WxCpBaseResp; +import me.chanjar.weixin.cp.bean.oa.*; import java.util.Date; import java.util.List; @@ -12,53 +11,331 @@ /** * 企业微信OA相关接口. * - * @author Element - * @date 2019-04-06 10:52 + * @author Element, Wang_Wong + * @since 2019-04-06 10:52 */ public interface WxCpOaService { + /** + *

提交审批申请
+   * 调试工具
+   * 企业可通过审批应用或自建应用Secret调用本接口,代应用可见范围内员工在企业微信“审批应用”内提交指定类型的审批申请。
+   *
+   * 请求方式:POST(HTTPS)
+   * 请求地址: https://qyapi.weixin.qq.com/cgi-bin/oa/applyevent?access_token=ACCESS_TOKEN
+   * 文档地址
+   * 
+ * + * @param request 请求 + * @return 表单提交成功后 ,返回的表单编号 + * @throws WxErrorException . + */ + String apply(WxCpOaApplyEventRequest request) throws WxErrorException; + /** *
    *  获取打卡数据
-   *  API doc : https://work.weixin.qq.com/api/doc#90000/90135/90262
+   *  文档地址
    * 
* * @param openCheckinDataType 打卡类型。1:上下班打卡;2:外出打卡;3:全部打卡 * @param startTime 获取打卡记录的开始时间 * @param endTime 获取打卡记录的结束时间 * @param userIdList 需要获取打卡记录的用户列表 - * @return 打卡数据列表 + * @return 打卡数据列表 checkin data * @throws WxErrorException 异常 */ - List getCheckinData(Integer openCheckinDataType, Date startTime, Date endTime, List userIdList) throws WxErrorException; + List getCheckinData(Integer openCheckinDataType, Date startTime, Date endTime, + List userIdList) throws WxErrorException; /** *
    *   获取打卡规则
-   *   API doc : https://work.weixin.qq.com/api/doc#90000/90135/90263
+   *  文档地址
    * 
* * @param datetime 需要获取规则的当天日期 * @param userIdList 需要获取打卡规则的用户列表 - * @return 打卡规则列表 - * @throws WxErrorException 异常 + * @return 打卡规则列表 checkin option + * @throws WxErrorException . */ List getCheckinOption(Date datetime, List userIdList) throws WxErrorException; + /** *
-   *   获取审批数据
-   *   通过本接口来获取公司一段时间内的审批记录。一次拉取调用最多拉取10000个审批记录,可以通过多次拉取的方式来满足需求,但调用频率不可超过600次/分。
-   *   API doc : https://work.weixin.qq.com/api/doc#90000/90135/91530
+   *   获取企业所有打卡规则
+   * 文档地址
    * 
* - * @param startTime 获取审批记录的开始时间 - * @param endTime 获取审批记录的结束时间 - * @param nextSpnum 第一个拉取的审批单号,不填从该时间段的第一个审批单拉取 - * @throws WxErrorException 异常 + * @return 打卡规则列表 crop checkin option + * @throws WxErrorException the wx error exception + */ + List getCropCheckinOption() throws WxErrorException; + + /** + *
+   *
+   * 批量获取审批单号
+   *
+   * 审批应用及有权限的自建应用,可通过Secret调用本接口,以获取企业一段时间内企业微信“审批应用”单据的审批编号,支持按模板类型、申请人、部门、申请单审批状态等条件筛选。
+   * 自建应用调用此接口,需在“管理后台-应用管理-审批-API-审批数据权限”中,授权应用允许提交审批单据。
+   *
+   * 一次拉取调用最多拉取100个审批记录,可以通过多次拉取的方式来满足需求,但调用频率不可超过600次/分。
+   *
+   * 文档地址
+   * 
+ * + * @param startTime 开始时间 + * @param endTime 结束时间 + * @param cursor 分页查询游标,默认为0,后续使用返回的next_cursor进行分页拉取 + * @param size 一次请求拉取审批单数量,默认值为100,上限值为100 + * @param filters 筛选条件,可对批量拉取的审批申请设置约束条件,支持设置多个条件,nullable + * @return WxCpApprovalInfo approval info + * @throws WxErrorException . + */ + @Deprecated + WxCpApprovalInfo getApprovalInfo(@NonNull Date startTime, @NonNull Date endTime, Integer cursor, Integer size, + List filters) throws WxErrorException; + + /** + * short method + * + * @param startTime 开始时间 + * @param endTime 结束时间 + * @return WxCpApprovalInfo approval info + * @throws WxErrorException . + * @see me.chanjar.weixin.cp.api.WxCpOaService#getApprovalInfo me.chanjar.weixin.cp.api + * .WxCpOaService#getApprovalInfome.chanjar.weixin.cp.api.WxCpOaService#getApprovalInfo + */ + @Deprecated + WxCpApprovalInfo getApprovalInfo(@NonNull Date startTime, @NonNull Date endTime) throws WxErrorException; + + + /** + *
+   *
+   * 批量获取审批单号
+   *
+   * 审批应用及有权限的自建应用,可通过Secret调用本接口,以获取企业一段时间内企业微信“审批应用”单据的审批编号,支持按模板类型、申请人、部门、申请单审批状态等条件筛选。
+   * 自建应用调用此接口,需在“管理后台-应用管理-审批-API-审批数据权限”中,授权应用允许提交审批单据。
+   *
+   * 一次拉取调用最多拉取100个审批记录,可以通过多次拉取的方式来满足需求,但调用频率不可超过600次/分。
+   *
+   * 文档地址
+   *
+   * 1 接口频率限制 600次/分钟
+   * 2 请求的参数endtime需要大于startime, 起始时间跨度不能超过31天;
+   * 3 老的分页游标字段cursor和next_cursor待废弃,请开发者使用新字段new_cursor和new_next_cursor。
+   * 
+ * + * @param startTime 开始时间 + * @param endTime 结束时间 + * @param newCursor 分页查询游标,默认为0,后续使用返回的next_cursor进行分页拉取 + * @param size 一次请求拉取审批单数量,默认值为100,上限值为100 + * @param filters 筛选条件,可对批量拉取的审批申请设置约束条件,支持设置多个条件,nullable + * @return WxCpApprovalInfo approval info + * @throws WxErrorException . + */ + WxCpApprovalInfo getApprovalInfo(@NonNull Date startTime, @NonNull Date endTime, String newCursor, Integer size, + List filters) throws WxErrorException; + + + /** + *
+   *   获取审批申请详情
+   *
+   *   企业可通过审批应用或自建应用Secret调用本接口,根据审批单号查询企业微信“审批应用”的审批申请详情。
+   *
+   *  文档地址
+   * 
+ * + * @param spNo 审批单编号。 + * @return WxCpApprovaldetail approval detail + * @throws WxErrorException . + */ + WxCpApprovalDetailResult getApprovalDetail(@NonNull String spNo) throws WxErrorException; + + + /** + * 获取企业假期管理配置 + * 企业可通过审批应用或自建应用Secret调用本接口,获取可见范围内员工的“假期管理”配置,包括:各个假期的id、名称、请假单位、时长计算方式、发放规则等。 + * 第三方应用可获取应用可见范围内员工的“假期管理”配置,包括:各个假期的id、名称、请假单位、时长计算方式、发放规则等。 + *

+ * 请求方式:GET(HTTPS) + * 请求地址:https://qyapi.weixin.qq.com/cgi-bin/oa/vacation/getcorpconf?access_token=ACCESS_TOKEN + * + * @return corp conf + * @throws WxErrorException the wx error exception + */ + WxCpCorpConfInfo getCorpConf() throws WxErrorException; + + + /** + * 获取成员假期余额 + * 企业可通过审批应用或自建应用Secret调用本接口,获取可见范围内各个员工的假期余额数据。 + * 第三方应用可获取应用可见范围内各个员工的假期余额数据。 + *

+ * 请求方式:POST(HTTPS) + * 请求地址:https://qyapi.weixin.qq.com/cgi-bin/oa/vacation/getuservacationquota?access_token=ACCESS_TOKEN + * + * @param userId 需要获取假期余额的成员的userid + * @return user vacation quota + * @throws WxErrorException the wx error exception + */ + WxCpUserVacationQuota getUserVacationQuota(@NonNull String userId) throws WxErrorException; + + + /** + * 获取审批数据(旧) + * 提示:推荐使用新接口“批量获取审批单号”及“获取审批申请详情”,此接口后续将不再维护、逐步下线。 + * 通过本接口来获取公司一段时间内的审批记录。一次拉取调用最多拉取100个审批记录,可以通过多次拉取的方式来满足需求,但调用频率不可超过600次/分。 + *

+ * 请求方式:POST(HTTPS) + * 请求地址:https://qyapi.weixin.qq.com/cgi-bin/corp/getapprovaldata?access_token=ACCESS_TOKEN + * + * @param startTime 获取审批记录的开始时间。Unix时间戳 + * @param endTime 获取审批记录的结束时间。Unix时间戳 + * @param nextSpNum 第一个拉取的审批单号,不填从该时间段的第一个审批单拉取 + * @return approval data + * @throws WxErrorException the wx error exception + */ + WxCpGetApprovalData getApprovalData(@NonNull Long startTime, @NonNull Long endTime, Long nextSpNum) throws WxErrorException; + + + /** + * 修改成员假期余额 + * 企业可通过审批应用或自建应用Secret调用本接口,修改可见范围内员工的“假期余额”。 + * 第三方应用可通过应本接口修改应用可见范围内指定员工的“假期余额”。 + *

+ * 请求方式:POST(HTTPS) + * 请求地址:https://qyapi.weixin.qq.com/cgi-bin/oa/vacation/setoneuserquota?access_token=ACCESS_TOKEN + * + * @param userId 需要修改假期余额的成员的userid + * @param vacationId 假期id + * @param leftDuration 设置的假期余额,单位为秒,不能大于1000天或24000小时,当假期时间刻度为按小时请假时,必须为360整倍数,即0.1小时整倍数,按天请假时,必须为8640整倍数,即0.1天整倍数 + * @param timeAttr 假期时间刻度:0-按天请假;1-按小时请假 + * @param remarks 修改备注,用于显示在假期余额的修改记录当中,可对修改行为作说明,不超过200字符 + * @return one user quota + * @throws WxErrorException the wx error exception */ - WxCpApprovalDataResult getApprovalData(Date startTime, Date endTime, Long nextSpnum) throws WxErrorException; + WxCpBaseResp setOneUserQuota(@NonNull String userId, @NonNull Integer vacationId, @NonNull Integer leftDuration, + @NonNull Integer timeAttr, String remarks) throws WxErrorException; - List getDialRecord(Date startTime, Date endTime, Integer offset, Integer limit) throws WxErrorException; + /** + * 获取公费电话拨打记录 + * + * @param startTime 查询的起始时间戳 + * @param endTime 查询的结束时间戳 + * @param offset 分页查询的偏移量 + * @param limit 分页查询的每页大小,默认为100条,如该参数大于100则按100处理 + * @return . dial record + * @throws WxErrorException . + */ + List getDialRecord(Date startTime, Date endTime, Integer offset, + Integer limit) throws WxErrorException; + + /** + * 获取审批模板详情 + * + * @param templateId 模板ID + * @return . template detail + * @throws WxErrorException . + */ + WxCpOaApprovalTemplateResult getTemplateDetail(@NonNull String templateId) throws WxErrorException; + + /** + * 创建审批模板 + *
+ * 可以调用此接口创建审批模板。创建新模板后,管理后台及审批应用内将生成对应模板,并生效默认流程和规则配置。 + *

+   *  文档地址: https://developer.work.weixin.qq.com/document/path/97437
+   *  权限说明
+   * • 仅『审批』系统应用、自建应用和代开发自建应用可调用。
+   * 
+ * + * @param cpTemplate cpTemplate + * @return templateId + * @throws WxErrorException . + */ + String createOaApprovalTemplate(WxCpOaApprovalTemplate cpTemplate) throws WxErrorException; + + /** + * 更新审批模板 + *
+ * 可调用本接口更新审批模板。更新模板后,管理后台及审批应用内将更新原模板的内容,已配置的审批流程和规则不变。 + *
+   *  文档地址: https://developer.work.weixin.qq.com/document/path/97438
+   *  权限说明
+   * • 仅『审批』系统应用,自建应用和代开发自建应用可调用
+   * • 所有应用都可以通过本接口更新自己的模板
+   * • 『审批』系统应用可以修改管理员手动创建的模板
+   * • 自建应用和代开发自建应用不可通过本接口更新其他应用创建的模板
+   * 
+ * + * @param wxCpTemplate wxCpTemplate + * @throws WxErrorException . + */ + void updateOaApprovalTemplate(WxCpOaApprovalTemplate wxCpTemplate) throws WxErrorException; + + /** + * 获取打卡日报数据 + * + * @param startTime 获取日报的开始时间 + * @param endTime 获取日报的结束时间 + * @param userIdList 获取日报的userid列表 + * @return 日报数据列表 checkin day data + * @throws WxErrorException the wx error exception + */ + List getCheckinDayData(Date startTime, Date endTime, List userIdList) throws WxErrorException; + + + /** + * 获取打卡月报数据 + * + * @param startTime 获取月报的开始时间 + * @param endTime 获取月报的结束时间 + * @param userIdList 获取月报的userid列表 + * @return 月报数据列表 checkin month data + * @throws WxErrorException the wx error exception + */ + List getCheckinMonthData(Date startTime, Date endTime, List userIdList) throws WxErrorException; + + /** + * 获取打卡人员排班信息 + * + * @param startTime 获取排班信息的开始时间。Unix时间戳 + * @param endTime 获取排班信息的结束时间。Unix时间戳(与starttime跨度不超过一个月) + * @param userIdList 需要获取排班信息的用户列表(不超过100个) + * @return 排班表信息 checkin schedule list + * @throws WxErrorException the wx error exception + */ + List getCheckinScheduleList(Date startTime, Date endTime, List userIdList) throws WxErrorException; + + + /** + * 为打卡人员排班 + * + * @param wxCpSetCheckinSchedule the wx cp set checkin schedule + * @throws WxErrorException the wx error exception + */ + void setCheckinScheduleList(WxCpSetCheckinSchedule wxCpSetCheckinSchedule) throws WxErrorException; + + /** + *
+   * 录入打卡人员人脸信息
+   * 企业可通过打卡应用Secret调用本接口,为企业打卡人员录入人脸信息,人脸信息仅用于人脸打卡。
+   * 上传图片大小限制:图片数据不超过1M
+   * 请求方式:POST(HTTPS)
+   * 请求地址:
+   * https://qyapi.weixin.qq.com/cgi-bin/checkin/addcheckinuserface?access_token=ACCESS_TOKEN
+   * 文档地址:
+   * https://developer.work.weixin.qq.com/document/path/93378
+   * 
+ * @param userId 需要录入的用户id + * @param userFace 需要录入的人脸图片数据,需要将图片数据base64处理后填入,对已录入的人脸会进行更新处理 + * @throws WxErrorException the wx error exception + */ + void addCheckInUserFace(String userId, String userFace) throws WxErrorException; } diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpOaWeDocService.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpOaWeDocService.java new file mode 100644 index 0000000000..d63d32694a --- /dev/null +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpOaWeDocService.java @@ -0,0 +1,130 @@ +package me.chanjar.weixin.cp.api; + +import lombok.NonNull; +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.cp.bean.WxCpBaseResp; +import me.chanjar.weixin.cp.bean.oa.doc.*; + +/** + * 企业微信文档相关接口. + * 文档 + * + * @author Hugo + */ +public interface WxCpOaWeDocService { + + /** + * 新建文档 + * 该接口用于新建文档和表格,新建收集表可前往 收集表管理 查看。 + *

+ * 请求方式:POST(HTTPS) + * 请求地址: https://qyapi.weixin.qq.com/cgi-bin/wedoc/create_doc?access_token=ACCESS_TOKEN + * + * @param request 新建文档对应请求参数 + * @return url:新建文档的访问链接,docid:新建文档的docid + * @throws WxErrorException the wx error exception + */ + WxCpDocCreateData docCreate(@NonNull WxCpDocCreateRequest request) throws WxErrorException; + + /** + * 重命名文档/收集表 + * 该接口用于对指定文档/收集表进行重命名。 + *

+ * 请求方式:POST(HTTPS) + * 请求地址: https://qyapi.weixin.qq.com/cgi-bin/wedoc/rename_doc?access_token=ACCESS_TOKEN + * + * @param request 重命名文档/收集表 + * @return wx cp base resp + * @throws WxErrorException the wx error exception + */ + WxCpBaseResp docRename(@NonNull WxCpDocRenameRequest request) throws WxErrorException; + + /** + * 删除文档/收集表 + * 该接口用于删除指定文档/收集表。 + *

+ * 请求方式:POST(HTTPS) + * 请求地址: https://qyapi.weixin.qq.com/cgi-bin/wedoc/del_doc?access_token=ACCESS_TOKEN + * + * @param docId 文档docid(docid、formid只能填其中一个) + * @param formId 收集表id(docid、formid只能填其中一个) + * @return wx cp base resp + * @throws WxErrorException the wx error exception + */ + WxCpBaseResp docDelete(String docId, String formId) throws WxErrorException; + + /** + * 获取文档基础信息 + * 该接口用于获取指定文档的基础信息。 + *

+ * 请求方式:POST(HTTPS) + * 请求地址: https://qyapi.weixin.qq.com/cgi-bin/wedoc/get_doc_base_info?access_token=ACCESS_TOKEN + * + * @param docId 文档docid + * @return wx cp doc info + * @throws WxErrorException the wx error exception + */ + WxCpDocInfo docInfo(@NonNull String docId) throws WxErrorException; + + /** + * 分享文档 + * 该接口用于获取文档的分享链接。 + *

+ * 请求方式:POST(HTTPS) + * 请求地址: https://qyapi.weixin.qq.com/cgi-bin/wedoc/doc_share?access_token=ACCESS_TOKEN + * + * @param docId 文档docid + * @return url 文档分享链接 + * @throws WxErrorException the wx error exception + */ + WxCpDocShare docShare(@NonNull String docId) throws WxErrorException; + + /** + * 编辑表格内容 + * 该接口可以对一个在线表格批量执行多个更新操作 + *

+ * 注意: + * 1.批量更新请求中的各个操作会逐个按顺序执行,直到全部执行完成则请求返回,或者其中一个操作报错则不再继续执行后续的操作 + * 2.每一个更新操作在执行之前都会做请求校验(包括权限校验、参数校验等等),如果校验未通过则该更新操作会报错并返回,不再执行后续操作 + * 3.单次批量更新请求的操作数量 <= 5 + *

+ * 请求方式:POST(HTTPS) + * 请求地址: https://qyapi.weixin.qq.com/cgi-bin/wedoc/spreadsheet/batch_update?access_token=ACCESS_TOKEN + * + * @param request 编辑表格内容请求参数 + * @return 编辑表格内容批量更新的响应结果 + * @throws WxErrorException the wx error exception + */ + WxCpDocSheetBatchUpdateResponse docBatchUpdate(@NonNull WxCpDocSheetBatchUpdateRequest request) throws WxErrorException; + + /** + * 获取表格行列信息 + * 该接口用于获取在线表格的工作表、行数、列数等。 + *

+ * 请求方式:POST(HTTPS) + * 请求地址: https://qyapi.weixin.qq.com/cgi-bin/wedoc/spreadsheet/get_sheet_properties?access_token=ACCESS_TOKEN + * + * @param docId 在线表格的docid + * @return 返回表格行列信息 + * @throws WxErrorException + */ + WxCpDocSheetProperties getSheetProperties(@NonNull String docId) throws WxErrorException; + + + /** + * 本接口用于获取指定范围内的在线表格信息,单次查询的范围大小需满足以下限制: + *

+ * 查询范围行数 <=1000 + * 查询范围列数 <=200 + * 范围内的总单元格数量 <=10000 + *

+ * 请求方式:POST(HTTPS) + * 请求地址: https://qyapi.weixin.qq.com/cgi-bin/wedoc/spreadsheet/get_sheet_range_data?access_token=ACCESS_TOKEN + * + * @param request 获取指定范围内的在线表格信息请求参数 + * @return 返回指定范围内的在线表格信息 + * @throws WxErrorException + */ + WxCpDocSheetData getSheetRangeData(@NonNull WxCpDocSheetGetDataRequest request) throws WxErrorException; + +} diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpOaWeDriveService.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpOaWeDriveService.java new file mode 100644 index 0000000000..e7217616b8 --- /dev/null +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpOaWeDriveService.java @@ -0,0 +1,292 @@ +package me.chanjar.weixin.cp.api; + +import lombok.NonNull; +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.cp.bean.WxCpBaseResp; +import me.chanjar.weixin.cp.bean.oa.wedrive.*; + +import java.util.List; + +/** + * 企业微信微盘相关接口. + * ... + * + * @author Wang_Wong created on 2022-04-22 + */ +public interface WxCpOaWeDriveService { + + /** + * 新建空间 + * 该接口用于在微盘内新建空间,可以指定人创建空间。 + *

+ * 请求方式:POST(HTTPS) + * 请求地址: ... + * + * @param request 新建空间对应请求参数 + * @return spaceid (空间id) + * @throws WxErrorException the wx error exception + */ + WxCpSpaceCreateData spaceCreate(@NonNull WxCpSpaceCreateRequest request) throws WxErrorException; + + /** + * 重命名空间 + * 该接口用于重命名已有空间,接收userid参数,以空间管理员身份来重命名。 + *

+ * 请求方式:POST(HTTPS) + * 请求地址: ... + * + * @param request 重命名空间的请求参数 + * @return wx cp base resp + * @throws WxErrorException the wx error exception + */ + WxCpBaseResp spaceRename(@NonNull WxCpSpaceRenameRequest request) throws WxErrorException; + + /** + * 解散空间 + * 该接口用于解散已有空间,需要以空间管理员身份来解散。 + *

+ * 请求方式:POST(HTTPS) + * 请求地址: ... + * + * @param spaceId the space id + * @return wx cp base resp + * @throws WxErrorException the wx error exception + */ + WxCpBaseResp spaceDismiss(@NonNull String spaceId) throws WxErrorException; + + /** + * 获取空间信息 + * 该接口用于获取空间成员列表、信息、权限等信息。 + *

+ * 请求方式:POST(HTTPS) + * 请求地址: ... + * + * @param spaceId the space id + * @return wx cp space info + * @throws WxErrorException the wx error exception + */ + WxCpSpaceInfo spaceInfo(@NonNull String spaceId) throws WxErrorException; + + /** + * 添加成员/部门 + * 该接口用于对指定空间添加成员/部门,可一次性添加多个。 + *

+ * 请求方式:POST(HTTPS) + * 请求地址: ... + * + * @param request 添加成员/部门请求参数 + * @return wx cp base resp + * @throws WxErrorException the wx error exception + */ + WxCpBaseResp spaceAclAdd(@NonNull WxCpSpaceAclAddRequest request) throws WxErrorException; + + /** + * 移除成员/部门 + * 该接口用于对指定空间移除成员/部门,操作者需要有移除权限。 + *

+ * 请求方式:POST(HTTPS) + * 请求地址: ... + * + * @param request 移除成员/部门请求参数 + * @return wx cp base resp + * @throws WxErrorException the wx error exception + */ + WxCpBaseResp spaceAclDel(@NonNull WxCpSpaceAclDelRequest request) throws WxErrorException; + + /** + * 权限管理 + * 该接口用于修改空间权限,需要传入userid,修改权限范围继承传入用户的权限范围。 + *

+ * 请求方式:POST(HTTPS) + * 请求地址: ... + * + * @param request 权限管理请求参数 + * @return wx cp base resp + * @throws WxErrorException the wx error exception + */ + WxCpBaseResp spaceSetting(@NonNull WxCpSpaceSettingRequest request) throws WxErrorException; + + /** + * 获取邀请链接 + * 该接口用于获取空间邀请分享链接。 + *

+ * 请求方式:POST(HTTPS) + * 请求地址: ... + * + * @param spaceId the space id + * @return wx cp space share + * @throws WxErrorException the wx error exception + */ + WxCpSpaceShare spaceShare(@NonNull String spaceId) throws WxErrorException; + + /** + * 获取文件列表 + * 该接口用于获取指定地址下的文件列表。 + *

+ * 请求方式:POST(HTTPS) + * 请求地址: ... + * + * @param request 获取文件列表请求参数 + * @return wx cp file list + * @throws WxErrorException the wx error exception + */ + WxCpFileList fileList(@NonNull WxCpFileListRequest request) throws WxErrorException; + + /** + * 上传文件 + * 该接口用于向微盘中的指定位置上传文件。 + *

+ * 请求方式:POST(HTTPS) + * 请求地址: ... + * + * @param request 上传文件请求参数 + * @return wx cp file upload + * @throws WxErrorException the wx error exception + */ + WxCpFileUpload fileUpload(@NonNull WxCpFileUploadRequest request) throws WxErrorException; + + /** + * 下载文件 + * 该接口用于下载文件,请求的userid需有下载权限。 + *

+ * 请求方式:POST(HTTPS) + * 请求地址: ... + * + * @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" + * } + * @throws WxErrorException the wx error exception + */ + WxCpFileDownload fileDownload(String fileId, String selectedTicket) throws WxErrorException; + + /** + * 重命名文件 + * 该接口用于对指定文件进行重命名。 + *

+ * 请求方式:POST(HTTPS) + * 请求地址: ... + * + * @param fileId the file id + * @param newName the new name + * @return wx cp file rename + * @throws WxErrorException the wx error exception + */ + WxCpFileRename fileRename(@NonNull String fileId, @NonNull String newName) throws WxErrorException; + + /** + * 新建文件夹/文档 + * 该接口用于在微盘指定位置新建文件夹、文档(更多文档接口能力可见文档API接口说明)。 + *

+ * 请求方式:POST(HTTPS) + * 请求地址: ... + * + * @param spaceId 空间spaceid + * @param fatherId 父目录fileid, 在根目录时为空间spaceid + * @param fileType 文件类型, 1:文件夹 3:文档(文档) 4:文档(表格) + * @param fileName 文件名字(注意:文件名最多填255个字符, 英文算1个, 汉字算2个) + * @return wx cp file create + * @throws WxErrorException the wx error exception + */ + WxCpFileCreate fileCreate(@NonNull String spaceId, @NonNull String fatherId, @NonNull Integer fileType, + @NonNull String fileName) throws WxErrorException; + + /** + * 移动文件 + * 该接口用于将文件移动到指定位置。 + *

+ * 请求方式:POST(HTTPS) + * 请求地址: ... + * + * @param request 移动文件的请求参数 + * @return wx cp file move + * @throws WxErrorException the wx error exception + */ + WxCpFileMove fileMove(@NonNull WxCpFileMoveRequest request) throws WxErrorException; + + /** + * 删除文件 + * 该接口用于删除指定文件。 + *

+ * 请求方式:POST(HTTPS) + * 请求地址: ... + * + * @param fileIds 文件fileid列表 + * @return wx cp base resp + * @throws WxErrorException the wx error exception + */ + WxCpBaseResp fileDelete(@NonNull List fileIds) throws WxErrorException; + + /** + * 文件信息 + * 该接口用于获取指定文件的信息。 + *

+ * 请求方式:POST(HTTPS) + * 请求地址: ... + * + * @param fileId the file id + * @return wx cp file info + * @throws WxErrorException the wx error exception + */ + WxCpFileInfo fileInfo(@NonNull String fileId) throws WxErrorException; + + /** + * 新增指定人 + * 该接口用于对指定文件添加指定人/部门。 + *

+ * 请求方式:POST(HTTPS) + * 请求地址: ... + * + * @param request 新增指定人请求参数 + * @return wx cp base resp + * @throws WxErrorException the wx error exception + */ + WxCpBaseResp fileAclAdd(@NonNull WxCpFileAclAddRequest request) throws WxErrorException; + + /** + * 删除指定人 + * 该接口用于删除指定文件的指定人/部门。 + *

+ * 请求方式:POST(HTTPS) + * 请求地址: ... + * + * @param request 请求参数 + * @return wx cp base resp + * @throws WxErrorException the wx error exception + */ + WxCpBaseResp fileAclDel(@NonNull WxCpFileAclDelRequest request) throws WxErrorException; + + /** + * 分享设置 + * 该接口用于文件的分享设置。 + *

+ * 请求方式:POST(HTTPS) + * 请求地址: ... + * + * @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 fileId, @NonNull Integer authScope, Integer auth) throws WxErrorException; + + /** + * 获取分享链接 + * 该接口用于获取文件的分享链接。 + *

+ * 请求方式:POST(HTTPS) + * 请求地址: ... + * + * @param fileId the file id + * @return wx cp file share + * @throws WxErrorException the wx error exception + */ + WxCpFileShare fileShare(@NonNull String fileId) throws WxErrorException; + +} diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpSchoolHealthService.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpSchoolHealthService.java new file mode 100644 index 0000000000..091f242820 --- /dev/null +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpSchoolHealthService.java @@ -0,0 +1,73 @@ +package me.chanjar.weixin.cp.api; + +import lombok.NonNull; +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.cp.bean.school.health.WxCpGetHealthReportStat; +import me.chanjar.weixin.cp.bean.school.health.WxCpGetReportAnswer; +import me.chanjar.weixin.cp.bean.school.health.WxCpGetReportJobIds; +import me.chanjar.weixin.cp.bean.school.health.WxCpGetReportJobInfo; + +/** + * 企业微信家校应用 健康上报接口. + * https://developer.work.weixin.qq.com/document/path/93676 + * + * @author Wang_Wong created on : 2022/5/31 9:10 + */ +public interface WxCpSchoolHealthService { + + /** + * 获取健康上报使用统计 + * 请求方式:POST(HTTPS) + * 请求地址:https://qyapi.weixin.qq.com/cgi-bin/health/get_health_report_stat?access_token=ACCESS_TOKEN + * + * @param date 具体某天的使用统计,最长支持获取30天前数据 + * @return health report stat + * @throws WxErrorException the wx error exception + */ + WxCpGetHealthReportStat getHealthReportStat(@NonNull String date) throws WxErrorException; + + /** + * 获取健康上报任务ID列表 + * 通过此接口可以获取企业当前正在运行的上报任务ID列表。 + *

+ * 请求方式:POST(HTTPS) + * 请求地址:https://qyapi.weixin.qq.com/cgi-bin/health/get_report_jobids?access_token=ACCESS_TOKEN + * + * @param offset 否 分页,偏移量, 默认为0 + * @param limit 否 分页,预期请求的数据量,默认为100,取值范围 1 ~ 100 + * @return report job ids + * @throws WxErrorException the wx error exception + */ + WxCpGetReportJobIds getReportJobIds(Integer offset, Integer limit) throws WxErrorException; + + /** + * 获取健康上报任务详情 + * 通过此接口可以获取指定的健康上报任务详情。 + *

+ * 请求方式:POST(HTTPS) + * 请求地址:https://qyapi.weixin.qq.com/cgi-bin/health/get_report_job_info?access_token=ACCESS_TOKEN + * + * @param jobId 是 任务ID + * @param date 是 具体某天任务详情,仅支持获取最近14天数据 + * @return report job info + * @throws WxErrorException the wx error exception + */ + WxCpGetReportJobInfo getReportJobInfo(@NonNull String jobId, @NonNull String date) throws WxErrorException; + + /** + * 获取用户填写答案 + * 通过此接口可以获取指定的健康上报任务详情。 + *

+ * 请求方式:POST(HTTPS) + * 请求地址:https://qyapi.weixin.qq.com/cgi-bin/health/get_report_answer?access_token=ACCESS_TOKEN + * + * @param jobId the job id + * @param date the date + * @param offset the offset + * @param limit the limit + * @return report answer + * @throws WxErrorException the wx error exception + */ + WxCpGetReportAnswer getReportAnswer(@NonNull String jobId, @NonNull String date, Integer offset, Integer limit) throws WxErrorException; + +} diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpSchoolService.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpSchoolService.java new file mode 100644 index 0000000000..5f1d41c197 --- /dev/null +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpSchoolService.java @@ -0,0 +1,148 @@ +package me.chanjar.weixin.cp.api; + +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.cp.bean.living.WxCpLivingResult; +import me.chanjar.weixin.cp.bean.school.*; + +import java.util.List; + +/** + * 企业微信家校应用 复学码相关接口. + * https://developer.work.weixin.qq.com/document/path/93744 + *

+ * 权限说明: + * 仅复学码应用可以调用 + * + * @author Wang_Wong created on : 2022/5/31 9:10 + */ +public interface WxCpSchoolService { + + /** + * 获取老师健康信息 + * 请求方式: POST(HTTPS) + * 请求地址: https://qyapi.weixin.qq.com/cgi-bin/school/user/get_teacher_customize_health_info?access_token=ACCESS_TOKEN + * + * @param date the date + * @param nextKey the next key + * @param limit the limit + * @return teacher customize health info + * @throws WxErrorException the wx error exception + */ + WxCpCustomizeHealthInfo getTeacherCustomizeHealthInfo(String date, String nextKey, Integer limit) throws WxErrorException; + + /** + * 获取学生健康信息 + * 请求方式: POST(HTTPS) + * 请求地址: https://qyapi.weixin.qq.com/cgi-bin/school/user/get_student_customize_health_info?access_token=ACCESS_TOKEN + * + * @param date the date + * @param nextKey the next key + * @param limit the limit + * @return student customize health info + * @throws WxErrorException the wx error exception + */ + WxCpCustomizeHealthInfo getStudentCustomizeHealthInfo(String date, String nextKey, Integer limit) throws WxErrorException; + + /** + * 获取师生健康码 + * 请求方式:POST(HTTPS) + * 请求地址:https://qyapi.weixin.qq.com/cgi-bin/school/user/get_health_qrcode?access_token=ACCESS_TOKEN + * + * @param userIds the user ids + * @param type the type + * @return health qr code + * @throws WxErrorException the wx error exception + */ + WxCpResultList getHealthQrCode(List userIds, Integer type) throws WxErrorException; + + /** + * 获取学生付款结果 + * 请求方式: POST(HTTPS) + * 请求地址: https://qyapi.weixin.qq.com/cgi-bin/school/get_payment_result?access_token=ACCESS_TOKEN + * + * @param paymentId the payment id + * @return payment result + * @throws WxErrorException the wx error exception + */ + WxCpPaymentResult getPaymentResult(String paymentId) throws WxErrorException; + + /** + * 获取订单详情 + * 请求方式: POST(HTTPS) + * 请求地址: https://qyapi.weixin.qq.com/cgi-bin/school/get_trade?access_token=ACCESS_TOKEN + * + * @param paymentId the payment id + * @param tradeNo the trade no + * @return trade + * @throws WxErrorException the wx error exception + */ + WxCpTrade getTrade(String paymentId, String tradeNo) throws WxErrorException; + + /** + * 获取直播详情 + *

+   * 请求方式:GET(HTTPS)
+   * {@code 请求地址:https://qyapi.weixin.qq.com/cgi-bin/school/living/get_living_info?access_token=ACCESS_TOKEN&livingid=LIVINGID}
+   * 
+ * + * @param livingId the living id + * @return living info + * @throws WxErrorException the wx error exception + */ + WxCpSchoolLivingInfo getLivingInfo(String livingId) throws WxErrorException; + + /** + * 获取老师直播ID列表 + * 通过此接口可以获取指定老师的所有直播ID + *

+ * 请求方式:POST(HTTPS) + * 请求地址:https://qyapi.weixin.qq.com/cgi-bin/living/get_user_all_livingid?access_token=ACCESS_TOKEN + * + * @param userId the user id + * @param cursor the cursor + * @param limit the limit + * @return user all living id + * @throws WxErrorException the wx error exception + */ + WxCpLivingResult.LivingIdResult getUserAllLivingId(String userId, String cursor, Integer limit) throws WxErrorException; + + /** + * 获取观看直播统计 + * 通过该接口可以获取所有观看直播的人员统计 + *

+ * 请求方式:POST(HTTPS) + * 请求地址:https://qyapi.weixin.qq.com/cgi-bin/school/living/get_watch_stat?access_token=ACCESS_TOKEN + * + * @param livingId the living id + * @param nextKey the next key + * @return watch stat + * @throws WxErrorException the wx error exception + */ + WxCpSchoolWatchStat getWatchStat(String livingId, String nextKey) throws WxErrorException; + + /** + * 获取未观看直播统计 + * 通过该接口可以获取未观看直播的学生统计,学生的家长必须是已经关注「学校通知」才会纳入统计范围。 + *

+ * 请求方式:POST(HTTPS) + * 请求地址:https://qyapi.weixin.qq.com/cgi-bin/school/living/get_unwatch_stat?access_token=ACCESS_TOKEN + * + * @param livingId the living id + * @param nextKey the next key + * @return unwatch stat + * @throws WxErrorException the wx error exception + */ + WxCpSchoolUnwatchStat getUnwatchStat(String livingId, String nextKey) throws WxErrorException; + + /** + * 删除直播回放 + * 请求方式: POST(HTTPS) + * 请求地址: https://qyapi.weixin.qq.com/cgi-bin/living/delete_replay_data?access_token=ACCESS_TOKEN + * + * @param livingId the living id + * @return wx cp living result + * @throws WxErrorException the wx error exception + */ + WxCpLivingResult deleteReplayData(String livingId) 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 new file mode 100644 index 0000000000..d004ca8aa5 --- /dev/null +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpSchoolUserService.java @@ -0,0 +1,375 @@ +package me.chanjar.weixin.cp.api; + +import lombok.NonNull; +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.cp.bean.WxCpBaseResp; +import me.chanjar.weixin.cp.bean.WxCpOauth2UserInfo; +import me.chanjar.weixin.cp.bean.school.user.*; + +import java.util.List; + +/** + * 企业微信家校沟通相关接口. + * https://developer.work.weixin.qq.com/document/path/91638 + * + * @author Wang_Wong created on : 2022/6/18 9:10 + */ +public interface WxCpSchoolUserService { + + /** + * 获取访问用户身份 + * 该接口用于根据code获取成员信息 + *

+   * 请求方式:GET(HTTPS)
+   * {@code 请求地址:https://qyapi.weixin.qq.com/cgi-bin/user/getuserinfo?access_token=ACCESS_TOKEN&code=CODE}
+   * 
+ * + * @param code the code + * @return user info + * @throws WxErrorException the wx error exception + */ + WxCpOauth2UserInfo getUserInfo(@NonNull String code) throws WxErrorException; + + /** + * 获取家校访问用户身份 + * 该接口用于根据code获取家长或者学生信息 + *
+   * 请求方式:GET(HTTPS)
+   * {@code 请求地址:https://qyapi.weixin.qq.com/cgi-bin/school/getuserinfo?access_token=ACCESS_TOKEN&code=CODE}
+   * 
+ * + * @param code the code + * @return school user info + * @throws WxErrorException the wx error exception + */ + WxCpOauth2UserInfo getSchoolUserInfo(@NonNull String code) throws WxErrorException; + + /** + * 创建学生 + * 请求方式:POST(HTTPS) + * 请求地址:https://qyapi.weixin.qq.com/cgi-bin/school/user/create_student?access_token=ACCESS_TOKEN + * + * @param studentUserId the student user id + * @param name the name + * @param departments the departments + * @return wx cp base resp + * @throws WxErrorException the wx error exception + */ + WxCpBaseResp createStudent(@NonNull String studentUserId, @NonNull String name, @NonNull List departments) throws WxErrorException; + + /** + * 批量创建学生 + * 请求方式:POST(HTTPS) + * 请求地址:https://qyapi.weixin.qq.com/cgi-bin/school/user/batch_create_student?access_token=ACCESS_TOKEN + * + * @param request the request + * @return wx cp batch result list + * @throws WxErrorException the wx error exception + */ + WxCpBatchResultList batchCreateStudent(@NonNull WxCpBatchCreateStudentRequest request) throws WxErrorException; + + /** + * 批量删除学生 + * 请求方式:POST(HTTPS) + * 请求地址:https://qyapi.weixin.qq.com/cgi-bin/school/user/batch_delete_student?access_token=ACCESS_TOKEN + * + * @param request the request + * @return wx cp batch result list + * @throws WxErrorException the wx error exception + */ + WxCpBatchResultList batchDeleteStudent(@NonNull WxCpBatchDeleteStudentRequest request) throws WxErrorException; + + /** + * 批量更新学生 + * 请求方式:POST(HTTPS) + * 请求地址:https://qyapi.weixin.qq.com/cgi-bin/school/user/batch_update_student?access_token=ACCESS_TOKEN + * + * @param request the request + * @return wx cp batch result list + * @throws WxErrorException the wx error exception + */ + WxCpBatchResultList batchUpdateStudent(@NonNull WxCpBatchUpdateStudentRequest request) throws WxErrorException; + + /** + * 删除学生 + *
+   * 请求方式:GET(HTTPS)
+   * {@code 请求地址:https://qyapi.weixin.qq.com/cgi-bin/school/user/delete_student?access_token=ACCESS_TOKEN&userid=USERID}
+   * 
+ * + * @param studentUserId the student user id + * @return wx cp base resp + * @throws WxErrorException the wx error exception + */ + WxCpBaseResp deleteStudent(@NonNull String studentUserId) throws WxErrorException; + + /** + * 更新学生 + * 请求方式:POST(HTTPS) + * 请求地址:https://qyapi.weixin.qq.com/cgi-bin/school/user/update_student?access_token=ACCESS_TOKEN + * + * @param studentUserId the student user id + * @param newStudentUserId the new student user id + * @param name the name + * @param departments the departments + * @return wx cp base resp + * @throws WxErrorException the wx error exception + */ + WxCpBaseResp updateStudent(@NonNull String studentUserId, String newStudentUserId, String name, + List departments) throws WxErrorException; + + /** + * 创建家长 + * 请求方式:POST(HTTPS) + * 请求地址:https://qyapi.weixin.qq.com/cgi-bin/school/user/create_parent?access_token=ACCESS_TOKEN + * + * @param request the request + * @return wx cp base resp + * @throws WxErrorException the wx error exception + */ + WxCpBaseResp createParent(@NonNull WxCpCreateParentRequest request) throws WxErrorException; + + /** + * 批量创建家长 + * 请求方式:POST(HTTPS) + * 请求地址:https://qyapi.weixin.qq.com/cgi-bin/school/user/batch_create_parent?access_token=ACCESS_TOKEN + * + * @param request the request + * @return wx cp batch result list + * @throws WxErrorException the wx error exception + */ + WxCpBatchResultList batchCreateParent(@NonNull WxCpBatchCreateParentRequest request) throws WxErrorException; + + /** + * 批量删除家长 + * 请求方式:POST(HTTPS) + * 请求地址:https://qyapi.weixin.qq.com/cgi-bin/school/user/batch_delete_parent?access_token=ACCESS_TOKEN + * + * @param userIdList the user id list + * @return wx cp batch result list + * @throws WxErrorException the wx error exception + */ + WxCpBatchResultList batchDeleteParent(@NonNull String... userIdList) throws WxErrorException; + + /** + * 批量更新家长 + * 请求方式:POST(HTTPS) + * 请求地址:https://qyapi.weixin.qq.com/cgi-bin/school/user/batch_update_parent?access_token=ACCESS_TOKEN + * + * @param request the request + * @return wx cp batch result list + * @throws WxErrorException the wx error exception + */ + WxCpBatchResultList batchUpdateParent(@NonNull WxCpBatchUpdateParentRequest request) throws WxErrorException; + + /** + * 读取学生或家长 + *
+   * 请求方式:GET(HTTPS)
+   * {@code 请求地址:https://qyapi.weixin.qq.com/cgi-bin/school/user/get?access_token=ACCESS_TOKEN&userid=USERID}
+   * 
+ * + * @param userId the user id + * @return user + * @throws WxErrorException the wx error exception + */ + WxCpUserResult getUser(@NonNull String userId) throws WxErrorException; + + /** + * 获取部门成员详情 + *
+   * 请求方式:GET(HTTPS)
+   * {@code 请求地址:https://qyapi.weixin.qq.com/cgi-bin/school/user/list?access_token=ACCESS_TOKEN&department_id=DEPARTMENT_ID&fetch_child=FETCH_CHILD}
+   * 
+ * + * @param departmentId 获取的部门id + * @param fetchChild 1/0:是否递归获取子部门下面的成员 + * @return user list + * @throws WxErrorException the wx error exception + */ + WxCpUserListResult getUserList(@NonNull Integer departmentId, Integer fetchChild) throws WxErrorException; + + /** + * 获取部门家长详情 + *
+   * 请求方式:GET(HTTPS)
+   * {@code 请求地址:https://qyapi.weixin.qq.com/cgi-bin/school/user/list_parent?access_token=ACCESS_TOKEN&department_id=DEPARTMENT_ID}
+   * 
+ * + * @param departmentId 获取的部门id + * @return user list parent + * @throws WxErrorException the wx error exception + */ + WxCpListParentResult getUserListParent(@NonNull Integer departmentId) throws WxErrorException; + + /** + * 更新家长 + * 请求方式:POST(HTTPS) + * 请求地址:https://qyapi.weixin.qq.com/cgi-bin/school/user/update_parent?access_token=ACCESS_TOKEN + * + * @param request the request + * @return wx cp base resp + * @throws WxErrorException the wx error exception + */ + WxCpBaseResp updateParent(@NonNull WxCpUpdateParentRequest request) throws WxErrorException; + + /** + * 删除家长 + *
+   * 请求方式:GET(HTTPS)
+   * {@code 请求地址:https://qyapi.weixin.qq.com/cgi-bin/school/user/delete_parent?access_token=ACCESS_TOKEN&userid=USERID}
+   * 
+ * + * @param userId the user id + * @return wx cp base resp + * @throws WxErrorException the wx error exception + */ + WxCpBaseResp deleteParent(@NonNull String userId) throws WxErrorException; + + /** + * 设置家校通讯录自动同步模式 + * 企业和第三方可通过此接口修改家校通讯录与班级标签之间的自动同步模式,注意,一旦设置禁止自动同步,将无法再次开启。 + *

+ * 请求方式:POST(HTTPS) + * 请求地址:https://qyapi.weixin.qq.com/cgi-bin/school/set_arch_sync_mode?access_token=ACCESS_TOKEN + * + * @param archSyncMode 家校通讯录同步模式:1-禁止将标签同步至家校通讯录,2-禁止将家校通讯录同步至标签,3-禁止家校通讯录和标签相互同步 + * @return arch sync mode + * @throws WxErrorException the wx error exception + */ + WxCpBaseResp setArchSyncMode(@NonNull Integer archSyncMode) throws WxErrorException; + + /** + * 创建部门 + *

+ * 请求方式:POST(HTTPS) + * 请求地址:https://qyapi.weixin.qq.com/cgi-bin/school/department/create?access_token=ACCESS_TOKEN + * + * @param request 请求参数对象 + * @return wx cp create department + * @throws WxErrorException the wx error exception + */ + WxCpCreateDepartment createDepartment(@NonNull WxCpCreateDepartmentRequest request) throws WxErrorException; + + /** + * 更新部门 + *

+ * 请求方式:POST(HTTPS) + * 请求地址:https://qyapi.weixin.qq.com/cgi-bin/school/department/update?access_token=ACCESS_TOKEN + * + * @param request the request + * @return wx cp base resp + * @throws WxErrorException the wx error exception + */ + WxCpBaseResp updateDepartment(@NonNull WxCpUpdateDepartmentRequest request) throws WxErrorException; + + /** + * 删除部门 + * 请求方式:GET(HTTPS) + * {@code 请求地址:https://qyapi.weixin.qq.com/cgi-bin/school/department/delete?access_token=ACCESS_TOKEN&id=ID} + * + * @param id the id + * @return wx cp base resp + * @throws WxErrorException the wx error exception + */ + WxCpBaseResp deleteDepartment(Integer id) throws WxErrorException; + + /** + * 设置关注「学校通知」的模式 + * 可通过此接口修改家长关注「学校通知」的模式:“可扫码填写资料加入”或“禁止扫码填写资料加入” + *

+ * 请求方式:POST(HTTPS) + * 请求地址:https://qyapi.weixin.qq.com/cgi-bin/externalcontact/set_subscribe_mode?access_token=ACCESS_TOKEN + * + * @param subscribeMode 关注模式, 1:可扫码填写资料加入, 2:禁止扫码填写资料加入 + * @return subscribe mode + * @throws WxErrorException the wx error exception + */ + WxCpBaseResp setSubscribeMode(@NonNull Integer subscribeMode) throws WxErrorException; + + /** + * 获取关注「学校通知」的模式 + * 可通过此接口获取家长关注「学校通知」的模式:“可扫码填写资料加入”或“禁止扫码填写资料加入” + *

+ * 请求方式:GET(HTTPS) + * 请求地址:https://qyapi.weixin.qq.com/cgi-bin/externalcontact/get_subscribe_mode?access_token=ACCESS_TOKEN + * + * @return subscribe mode + * @throws WxErrorException the wx error exception + */ + Integer getSubscribeMode() throws WxErrorException; + + /** + * 获取外部联系人详情 + * 学校可通过此接口,根据外部联系人的userid(如何获取?),拉取外部联系人详情。 + * + * 请求方式:GET(HTTPS) + * {@code 请求地址:https://qyapi.weixin.qq.com/cgi-bin/externalcontact/get?access_token=ACCESS_TOKEN&external_userid=EXTERNAL_USERID} + * + * @param externalUserId 外部联系人的userid,注意不是学校成员的帐号 + * @return external contact + * @throws WxErrorException the wx error exception + */ + WxCpExternalContact getExternalContact(@NonNull String externalUserId) throws WxErrorException; + + /** + * 获取可使用的家长范围 + * 获取可在微信「学校通知-学校应用」使用该应用的家长范围,以学生或部门列表的形式返回。应用只能给该列表下的家长发送「学校通知」。注意该范围只能由学校的系统管理员在「管理端-家校沟通-配置」配置。 + * + * 请求方式:GET(HTTPS) + * {@code 请求地址:https://qyapi.weixin.qq.com/cgi-bin/school/agent/get_allow_scope?access_token=ACCESS_TOKEN&agentid=AGENTID} + * + * @param agentId the agent id + * @return allow scope + * @throws WxErrorException the wx error exception + */ + WxCpAllowScope getAllowScope(@NonNull Integer agentId) throws WxErrorException; + + /** + * 外部联系人openid转换 + * 企业和服务商可通过此接口,将微信外部联系人的userid(如何获取?)转为微信openid,用于调用支付相关接口。暂不支持企业微信外部联系人(ExternalUserid为wo开头)的userid转openid。 + *

+ * 请求方式:POST(HTTPS) + * 请求地址:https://qyapi.weixin.qq.com/cgi-bin/externalcontact/convert_to_openid?access_token=ACCESS_TOKEN + * + * @param externalUserId the external user id + * @return string + * @throws WxErrorException the wx error exception + */ + String convertToOpenId(@NonNull String externalUserId) throws WxErrorException; + + /** + * 获取部门列表 + * 请求方式:GET(HTTPS) + * {@code 请求地址:https://qyapi.weixin.qq.com/cgi-bin/school/department/list?access_token=ACCESS_TOKEN&id=ID} + * + * @param id 部门id。获取指定部门及其下的子部门。 如果不填,默认获取全量组织架构 + * @return wx cp department list + * @throws WxErrorException the wx error exception + */ + WxCpDepartmentList listDepartment(Integer id) throws WxErrorException; + + /** + * 获取「学校通知」二维码 + * 学校可通过此接口获取「学校通知」二维码,家长可通过扫描此二维码关注「学校通知」并接收学校推送的消息。 + * 请求方式:GET(HTTPS) + * 请求地址:https://qyapi.weixin.qq.com/cgi-bin/externalcontact/get_subscribe_qr_code?access_token=ACCESS_TOKEN + * + * @return subscribe qr code + * @throws WxErrorException the wx error exception + */ + WxCpSubscribeQrCode getSubscribeQrCode() throws WxErrorException; + + /** + * 修改自动升年级的配置 + * 请求方式: POST(HTTPS) + * 请求地址: https://qyapi.weixin.qq.com/cgi-bin/school/set_upgrade_info?access_token=ACCESS_TOKEN + * + * @param upgradeTime the upgrade time + * @param upgradeSwitch the upgrade switch + * @return upgrade info + * @throws WxErrorException the wx error exception + */ + WxCpSetUpgradeInfo setUpgradeInfo(Long upgradeTime, Integer upgradeSwitch) throws WxErrorException; + +} 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 73776228ed..76012a2812 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpService.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpService.java @@ -2,14 +2,14 @@ import me.chanjar.weixin.common.bean.WxJsapiSignature; import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.common.service.WxService; import me.chanjar.weixin.common.session.WxSession; import me.chanjar.weixin.common.session.WxSessionManager; import me.chanjar.weixin.common.util.http.MediaUploadRequestExecutor; import me.chanjar.weixin.common.util.http.RequestExecutor; import me.chanjar.weixin.common.util.http.RequestHttp; +import me.chanjar.weixin.cp.bean.WxCpAgentJsapiSignature; import me.chanjar.weixin.cp.bean.WxCpMaJsCode2SessionResult; -import me.chanjar.weixin.cp.bean.WxCpMessage; -import me.chanjar.weixin.cp.bean.WxCpMessageSendResult; import me.chanjar.weixin.cp.bean.WxCpProviderToken; import me.chanjar.weixin.cp.config.WxCpConfigStorage; @@ -18,7 +18,7 @@ * * @author chanjaster */ -public interface WxCpService { +public interface WxCpService extends WxService { /** *

    * 验证推送过来的消息的正确性
@@ -29,13 +29,16 @@ public interface WxCpService {
    * @param timestamp    时间戳
    * @param nonce        随机数
    * @param data         微信传输过来的数据,有可能是echoStr,有可能是xml消息
+   * @return the boolean
    */
   boolean checkSignature(String msgSignature, String timestamp, String nonce, String data);
 
   /**
    * 获取access_token, 不强制刷新access_token
    *
-   * @see #getAccessToken(boolean)
+   * @return the access token
+   * @throws WxErrorException the wx error exception
+   * @see #getAccessToken(boolean) #getAccessToken(boolean)#getAccessToken(boolean)#getAccessToken(boolean)
    */
   String getAccessToken() throws WxErrorException;
 
@@ -49,13 +52,17 @@ public interface WxCpService {
    * 
* * @param forceRefresh 强制刷新 + * @return the access token + * @throws WxErrorException the wx error exception */ String getAccessToken(boolean forceRefresh) throws WxErrorException; /** * 获得jsapi_ticket,不强制刷新jsapi_ticket * - * @see #getJsapiTicket(boolean) + * @return the jsapi ticket + * @throws WxErrorException the wx error exception + * @see #getJsapiTicket(boolean) #getJsapiTicket(boolean)#getJsapiTicket(boolean)#getJsapiTicket(boolean) */ String getJsapiTicket() throws WxErrorException; @@ -68,6 +75,8 @@ public interface WxCpService { * * * @param forceRefresh 强制刷新 + * @return the jsapi ticket + * @throws WxErrorException the wx error exception */ String getJsapiTicket(boolean forceRefresh) throws WxErrorException; @@ -78,7 +87,9 @@ public interface WxCpService { * 签名的jsapi_ticket必须使用以下接口获取。且必须用wx.agentConfig中的agentid对应的应用secret去获取access_token。 * 签名用的noncestr和timestamp必须与wx.agentConfig中的nonceStr和timestamp相同。 * - * @see #getJsapiTicket(boolean) + * @return the agent jsapi ticket + * @throws WxErrorException the wx error exception + * @see #getJsapiTicket(boolean) #getJsapiTicket(boolean)#getJsapiTicket(boolean)#getJsapiTicket(boolean) */ String getAgentJsapiTicket() throws WxErrorException; @@ -96,6 +107,8 @@ public interface WxCpService { * * * @param forceRefresh 强制刷新 + * @return the agent jsapi ticket + * @throws WxErrorException the wx error exception */ String getAgentJsapiTicket(boolean forceRefresh) throws WxErrorException; @@ -107,36 +120,55 @@ public interface WxCpService { * * * @param url url + * @return the wx jsapi signature + * @throws WxErrorException the wx error exception */ WxJsapiSignature createJsapiSignature(String url) throws WxErrorException; /** *
-   * 发送消息
-   * 详情请见: http://qydev.weixin.qq.com/wiki/index.php?title=%E5%8F%91%E9%80%81%E6%8E%A5%E5%8F%A3%E8%AF%B4%E6%98%8E
+   *   创建调用wx.agentConfig时所需要的签名
+   *
+   * 详情请见:https://open.work.weixin.qq.com/api/doc/90000/90136/94313
    * 
* - * @param message 要发送的消息对象 + * @param url url + * @return the agent jsapi signature + * @throws WxErrorException the wx error exception */ - WxCpMessageSendResult messageSend(WxCpMessage message) throws WxErrorException; + WxCpAgentJsapiSignature createAgentJsapiSignature(String url) throws WxErrorException; /** * 小程序登录凭证校验 * * @param jsCode 登录时获取的 code + * @return the wx cp ma js code 2 session result + * @throws WxErrorException the wx error exception */ WxCpMaJsCode2SessionResult jsCode2Session(String jsCode) throws WxErrorException; /** *
-   * 获取微信服务器的ip段
+   * 获取企业微信回调IP段
    * http://qydev.weixin.qq.com/wiki/index.php?title=回调模式#.E8.8E.B7.E5.8F.96.E5.BE.AE.E4.BF.A1.E6.9C.8D.E5.8A.A1.E5.99.A8.E7.9A.84ip.E6.AE.B5
    * 
* * @return { "ip_list": ["101.226.103.*", "101.226.62.*"] } + * @throws WxErrorException the wx error exception */ String[] getCallbackIp() throws WxErrorException; + /** + *
+   * 获取企业微信接口IP段
+   * https://developer.work.weixin.qq.com/document/path/92520
+   * 
+ * + * @return 企业微信接口IP段 + * @throws WxErrorException the wx error exception + */ + String[] getApiDomainIp() throws WxErrorException; + /** *
    * 获取服务商凭证
@@ -147,31 +179,20 @@ public interface WxCpService {
    *
    * @param corpId         服务商的corpid
    * @param providerSecret 服务商的secret,在服务商管理后台可见
-   * @return {
-   * "errcode":0 ,
-   * "errmsg":"ok" ,
-   * "provider_access_token":"enLSZ5xxxxxxJRL",
-   * "expires_in":7200
-   * }
+   * @return { "errcode":0 , "errmsg":"ok" , "provider_access_token":"enLSZ5xxxxxxJRL", "expires_in":7200 }
    * @throws WxErrorException .
    */
   WxCpProviderToken getProviderToken(String corpId, String providerSecret) throws WxErrorException;
 
   /**
-   * 当本Service没有实现某个API的时候,可以用这个,针对所有微信API中的GET请求
-   *
-   * @param url        接口地址
-   * @param queryParam 请求参数
-   */
-  String get(String url, String queryParam) throws WxErrorException;
-
-  /**
-   * 当本Service没有实现某个API的时候,可以用这个,针对所有微信API中的POST请求
+   * 当不需要自动带accessToken的时候,可以用这个发起post请求
    *
    * @param url      接口地址
    * @param postData 请求body字符串
+   * @return the string
+   * @throws WxErrorException the wx error exception
    */
-  String post(String url, String postData) throws WxErrorException;
+  String postWithoutToken(String url, String postData) throws WxErrorException;
 
   /**
    * 
@@ -180,11 +201,13 @@ public interface WxCpService {
    * 可以参考,{@link MediaUploadRequestExecutor}的实现方法
    * 
* + * @param 请求值类型 + * @param 返回值类型 * @param executor 执行器 * @param uri 请求地址 * @param data 参数 - * @param 请求值类型 - * @param 返回值类型 + * @return the t + * @throws WxErrorException the wx error exception */ T execute(RequestExecutor executor, String uri, E data) throws WxErrorException; @@ -212,6 +235,7 @@ public interface WxCpService { * 获取某个sessionId对应的session,如果sessionId没有对应的session,则新建一个并返回。 * * @param id id可以为任意字符串,建议使用FromUserName作为id + * @return the session */ WxSession getSession(String id); @@ -220,13 +244,14 @@ public interface WxCpService { * * @param id id可以为任意字符串,建议使用FromUserName作为id * @param create 是否新建 + * @return the session */ WxSession getSession(String id, boolean create); /** * 获取WxSessionManager 对象 * - * @return WxSessionManager + * @return WxSessionManager session manager */ WxSessionManager getSessionManager(); @@ -244,20 +269,37 @@ public interface WxCpService { * 上传部门列表覆盖企业号上的部门信息 * * @param mediaId 媒体id + * @return the string + * @throws WxErrorException the wx error exception */ String replaceParty(String mediaId) throws WxErrorException; + /** + * 上传用户列表,增量更新成员 + * + * @param mediaId 媒体id + * @return jobId 异步任务id + * @throws WxErrorException the wx error exception + */ + String syncUser(String mediaId) throws WxErrorException; + /** * 上传用户列表覆盖企业号上的用户信息 * * @param mediaId 媒体id + * @return the string + * @throws WxErrorException the wx error exception */ String replaceUser(String mediaId) throws WxErrorException; /** * 获取异步任务结果 + * + * @param jobId 异步任务id + * @return the task result + * @throws WxErrorException the wx error exception */ - String getTaskResult(String joinId) throws WxErrorException; + String getTaskResult(String jobId) throws WxErrorException; /** * 初始化http请求对象 @@ -265,9 +307,9 @@ public interface WxCpService { void initHttp(); /** - * 获取WxMpConfigStorage 对象 + * 获取WxCpConfigStorage 对象 * - * @return WxMpConfigStorage + * @return WxCpConfigStorage wx cp config storage */ WxCpConfigStorage getWxCpConfigStorage(); @@ -278,70 +320,278 @@ public interface WxCpService { */ void setWxCpConfigStorage(WxCpConfigStorage wxConfigProvider); + /** + * 构造扫码登录链接 - 构造独立窗口登录二维码 + * + * @param redirectUri 重定向地址,需要进行UrlEncode + * @param state 用于保持请求和回调的状态,授权请求后原样带回给企业。该参数可用于防止csrf攻击(跨站请求伪造攻击),建议企业带上该参数,可设置为简单的随机数加session进行校验 + * @return . string + */ + String buildQrConnectUrl(String redirectUri, String state); + /** * 获取部门相关接口的服务类对象 + * + * @return the department service */ WxCpDepartmentService getDepartmentService(); /** * 获取媒体相关接口的服务类对象 + * + * @return the media service */ WxCpMediaService getMediaService(); /** * 获取菜单相关接口的服务类对象 + * + * @return the menu service */ WxCpMenuService getMenuService(); /** * 获取Oauth2相关接口的服务类对象 + * + * @return the oauth 2 service */ WxCpOAuth2Service getOauth2Service(); /** * 获取标签相关接口的服务类对象 + * + * @return the tag service */ WxCpTagService getTagService(); /** * 获取用户相关接口的服务类对象 + * + * @return the user service */ WxCpUserService getUserService(); + /** + * Gets external contact service. + * + * @return the external contact service + */ WxCpExternalContactService getExternalContactService(); /** * 获取群聊服务 * - * @return 群聊服务 + * @return 群聊服务 chat service */ WxCpChatService getChatService(); /** * 获取任务卡片服务 * - * @return 任务卡片服务 + * @return 任务卡片服务 task card service */ WxCpTaskCardService getTaskCardService(); + /** + * Gets agent service. + * + * @return the agent service + */ WxCpAgentService getAgentService(); - WxCpOaService getOAService(); + /** + * Gets message service. + * + * @return the message service + */ + WxCpMessageService getMessageService(); + + /** + * 获取OA相关接口的服务类对象. + * + * @return the oa service + */ + WxCpOaService getOaService(); + + /** + * 获取家校应用复学码相关接口的服务类对象 + * + * @return school service + */ + WxCpSchoolService getSchoolService(); + + /** + * 获取家校沟通相关接口的服务类对象 + * + * @return school user service + */ + WxCpSchoolUserService getSchoolUserService(); + + /** + * 获取家校应用健康上报的服务类对象 + * + * @return school health service + */ + WxCpSchoolHealthService getSchoolHealthService(); + + /** + * 获取直播相关接口的服务类对象 + * + * @return the Living service + */ + WxCpLivingService getLivingService(); + + /** + * 获取OA 自建应用相关接口的服务类对象 + * + * @return oa agent service + */ + WxCpOaAgentService getOaAgentService(); + + /** + * 获取OA效率工具 微盘的服务类对象 + * + * @return oa we drive service + */ + WxCpOaWeDriveService getOaWeDriveService(); + + /** + * 获取会话存档相关接口的服务类对象 + * + * @return msg audit service + */ + WxCpMsgAuditService getMsgAuditService(); + + /** + * 获取日历相关接口的服务类对象 + * + * @return the oa calendar service + */ + WxCpOaCalendarService getOaCalendarService(); + + /** + * 获取会议室相关接口的服务类对象 + * + * @return the oa meetingroom service + */ + WxCpOaMeetingRoomService getOaMeetingRoomService(); + + /** + * 获取日程相关接口的服务类对象 + * + * @return the oa schedule service + */ + WxCpOaScheduleService getOaScheduleService(); + + /** + * 获取群机器人消息推送服务 + * + * @return 群机器人消息推送服务 group robot service + */ + WxCpGroupRobotService getGroupRobotService(); + + /** + * 获取工作台服务 + * + * @return the workbench service + */ + WxCpAgentWorkBenchService getWorkBenchService(); + + /** + * 获取微信客服服务 + * + * @return 微信客服服务 kf service + */ + WxCpKfService getKfService(); /** * http请求对象 + * + * @return the request http */ RequestHttp getRequestHttp(); + /** + * Sets user service. + * + * @param userService the user service + */ void setUserService(WxCpUserService userService); + /** + * Sets department service. + * + * @param departmentService the department service + */ void setDepartmentService(WxCpDepartmentService departmentService); + /** + * Sets media service. + * + * @param mediaService the media service + */ void setMediaService(WxCpMediaService mediaService); + /** + * Sets menu service. + * + * @param menuService the menu service + */ void setMenuService(WxCpMenuService menuService); + /** + * Sets oauth 2 service. + * + * @param oauth2Service the oauth 2 service + */ void setOauth2Service(WxCpOAuth2Service oauth2Service); + /** + * Sets tag service. + * + * @param tagService the tag service + */ void setTagService(WxCpTagService tagService); + + /** + * Sets kf service. + * + * @param kfService the kf service + */ + void setKfService(WxCpKfService kfService); + + /** + * 获取异步导出服务 + * + * @return 异步导出服务 export service + */ + WxCpExportService getExportService(); + + /** + * 设置异步导出服务 + * + * @param exportService 异步导出服务 + */ + void setExportService(WxCpExportService exportService); + + /** + * 相关接口的服务类对象 + * + * @return the meeting service + */ + WxCpMeetingService getMeetingService(); + + /** + * 企业互联的服务类对象 + * + * @return 企业互联服务对象 + */ + WxCpCorpGroupService getCorpGroupService(); + + /** + * 获取智能机器人服务 + * + * @return 智能机器人服务 intelligent robot service + */ + WxCpIntelligentRobotService getIntelligentRobotService(); } diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpTagService.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpTagService.java index 52bf932303..4469bcc9e9 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpTagService.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpTagService.java @@ -17,7 +17,6 @@ * @author Binary Wang */ public interface WxCpTagService { - /** * 创建标签. *
@@ -27,22 +26,11 @@ public interface WxCpTagService {
    *
    * @param name 标签名称,长度限制为32个字以内(汉字或英文字母),标签名不可与其他标签重名。
    * @param id   标签id,非负整型,指定此参数时新增的标签会生成对应的标签id,不指定时则以目前最大的id自增。
-   * @return 标签id
+   * @return 标签id string
    * @throws WxErrorException .
    */
   String create(String name, Integer id) throws WxErrorException;
 
-  /**
-   * 创建标签.
-   *
-   * @param tagName 标签名
-   * @return 标签id
-   * @throws WxErrorException .
-   * @deprecated 建议使用 {@link #create(String, Integer)},其中后面的参数可以为空
-   */
-  @Deprecated
-  String create(String tagName) throws WxErrorException;
-
   /**
    * 更新标签.
    *
@@ -63,7 +51,7 @@ public interface WxCpTagService {
   /**
    * 获得标签列表.
    *
-   * @return 标签列表
+   * @return 标签列表 list
    * @throws WxErrorException .
    */
   List listAll() throws WxErrorException;
@@ -72,7 +60,7 @@ public interface WxCpTagService {
    * 获取标签成员.
    *
    * @param tagId 标签ID
-   * @return 成员列表
+   * @return 成员列表 list
    * @throws WxErrorException .
    */
   List listUsersByTagId(String tagId) throws WxErrorException;
@@ -82,7 +70,7 @@ public interface WxCpTagService {
    * 对应: http://qydev.weixin.qq.com/wiki/index.php?title=管理标签 中的get接口
    *
    * @param tagId 标签id
-   * @return .
+   * @return . wx cp tag get result
    * @throws WxErrorException .
    */
   WxCpTagGetResult get(String tagId) throws WxErrorException;
@@ -93,7 +81,7 @@ public interface WxCpTagService {
    * @param tagId    标签id
    * @param userIds  用户ID 列表
    * @param partyIds 企业部门ID列表
-   * @return .
+   * @return . wx cp tag add or remove users result
    * @throws WxErrorException .
    */
   WxCpTagAddOrRemoveUsersResult addUsers2Tag(String tagId, List userIds, List partyIds) throws WxErrorException;
@@ -104,7 +92,7 @@ public interface WxCpTagService {
    * @param tagId    标签id
    * @param userIds  用户id列表
    * @param partyIds 企业部门ID列表
-   * @return .
+   * @return . wx cp tag add or remove users result
    * @throws WxErrorException .
    */
   WxCpTagAddOrRemoveUsersResult removeUsersFromTag(String tagId, List userIds, List partyIds) throws WxErrorException;
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpTaskCardService.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpTaskCardService.java
index 5bf50d36dc..303d22f692 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpTaskCardService.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpTaskCardService.java
@@ -1,6 +1,7 @@
 package me.chanjar.weixin.cp.api;
 
 import me.chanjar.weixin.common.error.WxErrorException;
+import me.chanjar.weixin.cp.bean.message.TemplateCardMessage;
 
 import java.util.List;
 
@@ -8,10 +9,10 @@
  * 
  *  任务卡片管理接口.
  *  Created by Jeff on 2019-05-16.
+ *  Updted by HeXiao on 2022-03-09.
  * 
* - * @author Jeff - * @date 2019-05-16 + * @author Jeff created on 2019-05-16 */ public interface WxCpTaskCardService { @@ -23,9 +24,30 @@ public interface WxCpTaskCardService { * 注意: 这个方法使用WxCpConfigStorage里的agentId *
* - * @param userIds 企业的成员ID列表 - * @param taskId 任务卡片ID - * @param clickedKey 已点击按钮的Key + * @param userIds 企业的成员ID列表 + * @param taskId 任务卡片ID + * @param replaceName 替换文案 + * @throws WxErrorException the wx error exception */ - void update(List userIds, String taskId, String clickedKey) throws WxErrorException; + void update(List userIds, String taskId, String replaceName) throws WxErrorException; + + + /** + * 更新按钮为不可点击状态 + * 详情请见https://developer.work.weixin.qq.com/document/path/94888#%E6%9B%B4%E6%96%B0%E6%8C%89%E9%92%AE%E4%B8%BA%E4%B8 + * %8D%E5%8F%AF%E7%82%B9%E5%87%BB%E7%8A%B6%E6%80%81 + * + * @param userIds 企业的成员ID列表 + * @param partyIds 企业的部门ID列表 + * @param tagIds 企业的标签ID列表 + * @param atAll 更新整个任务接收人员 + * @param responseCode 更新卡片所需要消费的code,可通过发消息接口和回调接口返回值获取,一个code只能调用一次该接口,且只能在24小时内调用 + * @param replaceName 需要更新的按钮的文案 + * @throws WxErrorException the wx error exception + */ + void updateTemplateCardButton(List userIds, List partyIds, + List tagIds, Integer atAll, String responseCode, + String replaceName) throws WxErrorException; + + void updateTemplateCardButton(TemplateCardMessage templateCardMessage) throws WxErrorException; } diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpTpService.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpTpService.java deleted file mode 100644 index edc2f552ce..0000000000 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpTpService.java +++ /dev/null @@ -1,168 +0,0 @@ -package me.chanjar.weixin.cp.api; - -import me.chanjar.weixin.common.bean.WxAccessToken; -import me.chanjar.weixin.common.error.WxErrorException; -import me.chanjar.weixin.common.util.http.MediaUploadRequestExecutor; -import me.chanjar.weixin.common.util.http.RequestExecutor; -import me.chanjar.weixin.common.util.http.RequestHttp; -import me.chanjar.weixin.cp.bean.WxCpMaJsCode2SessionResult; -import me.chanjar.weixin.cp.bean.WxCpTpCorp; -import me.chanjar.weixin.cp.config.WxCpTpConfigStorage; - -/** - * 微信第三方应用API的Service. - * - * @author zhenjun cai - */ -public interface WxCpTpService { - /** - *
-   * 验证推送过来的消息的正确性
-   * 详情请见: https://work.weixin.qq.com/api/doc#90000/90139/90968/消息体签名校验
-   * 
- * - * @param msgSignature 消息签名 - * @param timestamp 时间戳 - * @param nonce 随机数 - * @param data 微信传输过来的数据,有可能是echoStr,有可能是xml消息 - */ - boolean checkSignature(String msgSignature, String timestamp, String nonce, String data); - - /** - * 获取suite_access_token, 不强制刷新suite_access_token - * - * @see #getSuiteAccessToken(boolean) - */ - String getSuiteAccessToken() throws WxErrorException; - - /** - *
-   * 获取suite_access_token,本方法线程安全
-   * 且在多线程同时刷新时只刷新一次,避免超出2000次/日的调用次数上限
-   * 另:本service的所有方法都会在suite_access_token过期是调用此方法
-   * 程序员在非必要情况下尽量不要主动调用此方法
-   * 详情请见: https://work.weixin.qq.com/api/doc#90001/90143/90600
-   * 
- * - * @param forceRefresh 强制刷新 - */ - String getSuiteAccessToken(boolean forceRefresh) throws WxErrorException; - - /** - * 获得suite_ticket,不强制刷新suite_ticket - * - * @see #getSuiteTicket(boolean) - */ - String getSuiteTicket() throws WxErrorException; - - /** - *
-   * 获得suite_ticket
-   * 由于suite_ticket是微信服务器定时推送(每10分钟),不能主动获取,如果碰到过期只能抛异常
-   *
-   * 详情请见:https://work.weixin.qq.com/api/doc#90001/90143/90628
-   * 
- * - * @param forceRefresh 强制刷新 - */ - String getSuiteTicket(boolean forceRefresh) throws WxErrorException; - - /** - * 小程序登录凭证校验 - * - * @param jsCode 登录时获取的 code - */ - WxCpMaJsCode2SessionResult jsCode2Session(String jsCode) throws WxErrorException; - - /** - * 获取企业凭证 - * - * @param authCorpid 授权方corpid - * @param permanentCode 永久授权码,通过get_permanent_code获取 - */ - WxAccessToken getCorpToken(String authCorpid, String permanentCode) throws WxErrorException; - - /** - * 获取企业永久授权码 . - * - * @param authCode . - * @return . - */ - WxCpTpCorp getPermanentCode(String authCode) throws WxErrorException; - - /** - * 当本Service没有实现某个API的时候,可以用这个,针对所有微信API中的GET请求. - * - * @param url 接口地址 - * @param queryParam 请求参数 - */ - String get(String url, String queryParam) throws WxErrorException; - - /** - * 当本Service没有实现某个API的时候,可以用这个,针对所有微信API中的POST请求. - * - * @param url 接口地址 - * @param postData 请求body字符串 - */ - String post(String url, String postData) throws WxErrorException; - - /** - *
-   * Service没有实现某个API的时候,可以用这个,
-   * 比{@link #get}和{@link #post}方法更灵活,可以自己构造RequestExecutor用来处理不同的参数和不同的返回类型。
-   * 可以参考,{@link MediaUploadRequestExecutor}的实现方法
-   * 
- * - * @param executor 执行器 - * @param uri 请求地址 - * @param data 参数 - * @param 请求值类型 - * @param 返回值类型 - */ - T execute(RequestExecutor executor, String uri, E data) throws WxErrorException; - - /** - *
-   * 设置当微信系统响应系统繁忙时,要等待多少 retrySleepMillis(ms) * 2^(重试次数 - 1) 再发起重试.
-   * 默认:1000ms
-   * 
- * - * @param retrySleepMillis 重试休息时间 - */ - void setRetrySleepMillis(int retrySleepMillis); - - /** - *
-   * 设置当微信系统响应系统繁忙时,最大重试次数.
-   * 默认:5次
-   * 
- * - * @param maxRetryTimes 最大重试次数 - */ - void setMaxRetryTimes(int maxRetryTimes); - - /** - * 初始化http请求对象 - */ - void initHttp(); - - /** - * 获取WxMpConfigStorage 对象. - * - * @return WxMpConfigStorage - */ - WxCpTpConfigStorage getWxCpTpConfigStorage(); - - /** - * 注入 {@link WxCpTpConfigStorage} 的实现. - * - * @param wxConfigProvider 配置对象 - */ - void setWxCpTpConfigStorage(WxCpTpConfigStorage wxConfigProvider); - - /** - * http请求对象. - */ - RequestHttp getRequestHttp(); - -} diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpUserService.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpUserService.java index 8cc77a2d55..7a7b5f40a8 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpUserService.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpUserService.java @@ -2,9 +2,14 @@ import me.chanjar.weixin.common.error.WxErrorException; import me.chanjar.weixin.cp.bean.WxCpInviteResult; +import me.chanjar.weixin.cp.bean.WxCpOpenUseridToUseridResult; import me.chanjar.weixin.cp.bean.WxCpUser; -import me.chanjar.weixin.cp.bean.WxCpUserExternalContactInfo; +import me.chanjar.weixin.cp.bean.WxCpUseridToOpenUseridResult; +import me.chanjar.weixin.cp.bean.external.contact.WxCpExternalContactInfo; +import me.chanjar.weixin.cp.bean.user.WxCpDeptUserResult; +import java.util.ArrayList; +import java.util.Date; import java.util.List; import java.util.Map; @@ -25,19 +30,24 @@ public interface WxCpUserService { *
* * @param userId 用户id + * @throws WxErrorException the wx error exception */ void authenticate(String userId) throws WxErrorException; /** *
-   * 获取部门成员(详情).
+   * 获取部门成员详情
+   * 请求方式:GET(HTTPS)
+   * {@code 请求地址:https://qyapi.weixin.qq.com/cgi-bin/user/list?access_token=ACCESS_TOKEN&department_id=DEPARTMENT_ID&fetch_child=FETCH_CHILD}
    *
-   * http://qydev.weixin.qq.com/wiki/index.php?title=管理成员#.E8.8E.B7.E5.8F.96.E9.83.A8.E9.97.A8.E6.88.90.E5.91.98.28.E8.AF.A6.E6.83.85.29
+   * 文档地址:https://work.weixin.qq.com/api/doc/90000/90135/90201
    * 
* * @param departId 必填。部门id * @param fetchChild 非必填。1/0:是否递归获取子部门下面的成员 * @param status 非必填。0获取全部员工,1获取已关注成员列表,2获取禁用成员列表,4获取未关注成员列表。status可叠加 + * @return the list + * @throws WxErrorException the wx error exception */ List listByDepartment(Long departId, Boolean fetchChild, Integer status) throws WxErrorException; @@ -51,6 +61,8 @@ public interface WxCpUserService { * @param departId 必填。部门id * @param fetchChild 非必填。1/0:是否递归获取子部门下面的成员 * @param status 非必填。0获取全部员工,1获取已关注成员列表,2获取禁用成员列表,4获取未关注成员列表。status可叠加 + * @return the list + * @throws WxErrorException the wx error exception */ List listSimpleByDepartment(Long departId, Boolean fetchChild, Integer status) throws WxErrorException; @@ -58,6 +70,7 @@ public interface WxCpUserService { * 新建用户. * * @param user 用户对象 + * @throws WxErrorException the wx error exception */ void create(WxCpUser user) throws WxErrorException; @@ -65,6 +78,7 @@ public interface WxCpUserService { * 更新用户. * * @param user 用户对象 + * @throws WxErrorException the wx error exception */ void update(WxCpUser user) throws WxErrorException; @@ -75,6 +89,7 @@ public interface WxCpUserService { * * * @param userIds 员工UserID列表。对应管理端的帐号 + * @throws WxErrorException the wx error exception */ void delete(String... userIds) throws WxErrorException; @@ -82,6 +97,8 @@ public interface WxCpUserService { * 获取用户. * * @param userid 用户id + * @return the by id + * @throws WxErrorException the wx error exception */ WxCpUser getById(String userid) throws WxErrorException; @@ -97,6 +114,8 @@ public interface WxCpUserService { * @param userIds 成员ID列表, 最多支持1000个。 * @param partyIds 部门ID列表,最多支持100个。 * @param tagIds 标签ID列表,最多支持100个。 + * @return the wx cp invite result + * @throws WxErrorException the wx error exception */ WxCpInviteResult invite(List userIds, List partyIds, List tagIds) throws WxErrorException; @@ -114,9 +133,9 @@ public interface WxCpUserService { * * @param userId 企业内的成员id * @param agentId 非必填,整型,仅用于发红包。其它场景该参数不要填,如微信支付、企业转账、电子发票 - * @return map对象,可能包含以下值: - * - openid 企业微信成员userid对应的openid,若有传参agentid,则是针对该agentid的openid。否则是针对企业微信corpid的openid - * - appid 应用的appid,若请求包中不包含agentid则不返回appid。该appid在使用微信红包时会用到 + * @return map对象 ,可能包含以下值: - openid 企业微信成员userid对应的openid,若有传参agentid,则是针对该agentid的openid。否则是针对企业微信corpid的openid - + * appid 应用的appid,若请求包中不包含agentid则不返回appid。该appid在使用微信红包时会用到 + * @throws WxErrorException the wx error exception */ Map userId2Openid(String userId, Integer agentId) throws WxErrorException; @@ -134,6 +153,7 @@ public interface WxCpUserService { * * @param openid 在使用微信支付、微信红包和企业转账之后,返回结果的openid * @return userid 该openid在企业微信对应的成员userid + * @throws WxErrorException the wx error exception */ String openid2UserId(String openid) throws WxErrorException; @@ -149,11 +169,29 @@ public interface WxCpUserService { * * * @param mobile 手机号码。长度为5~32个字节 - * @return userid mobile对应的成员userid + * @return userid mobile对应的成员userid * @throws WxErrorException . */ String getUserId(String mobile) throws WxErrorException; + /** + *
+   *
+   * 通过邮箱获取其所对应的userid。
+   *
+   * 请求方式:POST(HTTPS)
+   * 请求地址:https://qyapi.weixin.qq.com/cgi-bin/user/get_userid_by_email?access_token=ACCESS_TOKEN
+   *
+   * 文档地址:https://developer.work.weixin.qq.com/document/path/95895
+   * 
+ * + * @param email 邮箱 + * @param emailType 邮箱类型:1-企业邮箱;2-个人邮箱 + * @return userid email对应的成员userid + * @throws WxErrorException . + */ + String getUserIdByEmail(String email,int emailType) throws WxErrorException; + /** * 获取外部联系人详情. *
@@ -164,10 +202,86 @@ public interface WxCpUserService {
    * 
* * @param userId 外部联系人的userid - * @return 联系人详情 + * @return 联系人详情 external contact + * @throws WxErrorException . + */ + WxCpExternalContactInfo getExternalContact(String userId) throws WxErrorException; + + /** + *
+   *
+   * 获取加入企业二维码。
+   *
+   * 请求方式:GET(HTTPS)
+   * {@code 请求地址:https://qyapi.weixin.qq.com/cgi-bin/corp/get_join_qrcode?access_token=ACCESS_TOKEN&size_type=SIZE_TYPE}
+   *
+   * 文档地址:https://work.weixin.qq.com/api/doc/90000/90135/91714
+   * 
+ * + * @param sizeType qrcode尺寸类型,1: 171 x 171; 2: 399 x 399; 3: 741 x 741; 4: 2052 x 2052 + * @return join_qrcode 二维码链接,有效期7天 + * @throws WxErrorException . + */ + String getJoinQrCode(int sizeType) throws WxErrorException; + + /** + *
+   *
+   * 获取企业活跃成员数。
+   *
+   * 请求方式:POST(HTTPS)
+   * 请求地址:https://qyapi.weixin.qq.com/cgi-bin/user/get_active_stat?access_token=ACCESS_TOKEN
+   *
+   * 文档地址:https://developer.work.weixin.qq.com/document/path/92714
+   * 
+ * + * @param date 具体某天的活跃人数,最长支持获取30天前数据 + * @return join_qrcode 活跃成员数 * @throws WxErrorException . */ - WxCpUserExternalContactInfo getExternalContact(String userId) throws WxErrorException; + Integer getActiveStat(Date date) throws WxErrorException; + + /** + * userid转换为open_userid + * 将自建应用或代开发应用获取的userid转换为第三方应用的userid + * https://developer.work.weixin.qq.com/document/path/95603 + * + * @param useridList the userid list + * @return the WxCpUseridToOpenUseridResult + * @throws WxErrorException the wx error exception + */ + WxCpUseridToOpenUseridResult useridToOpenUserid(ArrayList useridList) throws WxErrorException; + /** + * open_userid转换为userid + * 将代开发应用或第三方应用获取的密文open_userid转换为明文userid + *
+   * 文档地址:https://developer.work.weixin.qq.com/document/path/95884#userid%E8%BD%AC%E6%8D%A2
+   *
+   * 权限说明:
+   *
+   * 需要使用自建应用或基础应用的access_token
+   * 成员需要同时在access_token和source_agentid所对应应用的可见范围内
+   * 
+ * @param openUseridList open_userid列表,最多不超过1000个。必须是source_agentid对应的应用所获取 + * @param sourceAgentId 企业授权的代开发自建应用或第三方应用的agentid + * @return the WxCpOpenUseridToUseridResult + * @throws WxErrorException the wx error exception + */ + WxCpOpenUseridToUseridResult openUseridToUserid(List openUseridList, String sourceAgentId) throws WxErrorException; + + /** + * 获取成员ID列表 + * 获取企业成员的userid与对应的部门ID列表,预计于2022年8月8号发布。若需要获取其他字段,参见「适配建议」。 + *

+ * 请求方式:POST(HTTPS) + * 请求地址:https://qyapi.weixin.qq.com/cgi-bin/user/list_id?access_token=ACCESS_TOKEN + * + * @param cursor the cursor + * @param limit the limit + * @return user list id + * @throws WxErrorException the wx error exception + */ + WxCpDeptUserResult getUserListId(String cursor, Integer limit) throws WxErrorException; } diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/BaseWxCpServiceImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/BaseWxCpServiceImpl.java index 9c7396ab1b..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 @@ -2,30 +2,31 @@ import com.google.common.base.Joiner; import com.google.gson.JsonArray; -import com.google.gson.JsonElement; import com.google.gson.JsonObject; -import com.google.gson.JsonParser; import lombok.extern.slf4j.Slf4j; -import me.chanjar.weixin.common.WxType; +import me.chanjar.weixin.common.api.WxConsts; +import me.chanjar.weixin.common.bean.CommonUploadParam; +import me.chanjar.weixin.common.bean.ToJson; import me.chanjar.weixin.common.bean.WxJsapiSignature; +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.executor.CommonUploadRequestExecutor; import me.chanjar.weixin.common.session.StandardSessionManager; import me.chanjar.weixin.common.session.WxSession; import me.chanjar.weixin.common.session.WxSessionManager; import me.chanjar.weixin.common.util.DataUtils; import me.chanjar.weixin.common.util.RandomUtils; import me.chanjar.weixin.common.util.crypto.SHA1; -import me.chanjar.weixin.common.util.http.RequestExecutor; -import me.chanjar.weixin.common.util.http.RequestHttp; -import me.chanjar.weixin.common.util.http.SimpleGetRequestExecutor; -import me.chanjar.weixin.common.util.http.SimplePostRequestExecutor; +import me.chanjar.weixin.common.util.http.*; +import me.chanjar.weixin.common.util.json.GsonParser; import me.chanjar.weixin.cp.api.*; +import me.chanjar.weixin.cp.bean.WxCpAgentJsapiSignature; import me.chanjar.weixin.cp.bean.WxCpMaJsCode2SessionResult; -import me.chanjar.weixin.cp.bean.WxCpMessage; -import me.chanjar.weixin.cp.bean.WxCpMessageSendResult; import me.chanjar.weixin.cp.bean.WxCpProviderToken; import me.chanjar.weixin.cp.config.WxCpConfigStorage; +import org.apache.commons.lang3.StringUtils; import java.io.File; import java.io.IOException; @@ -37,21 +38,43 @@ /** * . * + * @param the type parameter + * @param

the type parameter * @author chanjarster */ @Slf4j public abstract class BaseWxCpServiceImpl implements WxCpService, RequestHttp { private WxCpUserService userService = new WxCpUserServiceImpl(this); - private WxCpChatService chatService = new WxCpChatServiceImpl(this); + private final WxCpChatService chatService = new WxCpChatServiceImpl(this); private WxCpDepartmentService departmentService = new WxCpDepartmentServiceImpl(this); private WxCpMediaService mediaService = new WxCpMediaServiceImpl(this); private WxCpMenuService menuService = new WxCpMenuServiceImpl(this); private WxCpOAuth2Service oauth2Service = new WxCpOAuth2ServiceImpl(this); private WxCpTagService tagService = new WxCpTagServiceImpl(this); private WxCpAgentService agentService = new WxCpAgentServiceImpl(this); - private WxCpOaService oaService = new WxCpOaServiceImpl(this); - private WxCpTaskCardService taskCardService = new WxCpTaskCardServiceImpl(this); - private WxCpExternalContactService externalContactService = new WxCpExternalContactServiceImpl(this); + private final WxCpOaService oaService = new WxCpOaServiceImpl(this); + private final WxCpSchoolService schoolService = new WxCpSchoolServiceImpl(this); + private final WxCpSchoolUserService schoolUserService = new WxCpSchoolUserServiceImpl(this); + private final WxCpSchoolHealthService schoolHealthService = new WxCpSchoolHealthServiceImpl(this); + private final WxCpLivingService livingService = new WxCpLivingServiceImpl(this); + private final WxCpOaAgentService oaAgentService = new WxCpOaAgentServiceImpl(this); + private final WxCpOaWeDriveService oaWeDriveService = new WxCpOaWeDriveServiceImpl(this); + private final WxCpMsgAuditService msgAuditService = new WxCpMsgAuditServiceImpl(this); + private final WxCpTaskCardService taskCardService = new WxCpTaskCardServiceImpl(this); + private final WxCpExternalContactService externalContactService = new WxCpExternalContactServiceImpl(this); + private final WxCpGroupRobotService groupRobotService = new WxCpGroupRobotServiceImpl(this); + private final WxCpMessageService messageService = new WxCpMessageServiceImpl(this); + private final WxCpOaCalendarService oaCalendarService = new WxCpOaCalendarServiceImpl(this); + private final WxCpOaMeetingRoomService oaMeetingRoomService = new WxCpOaMeetingRoomServiceImpl(this); + private final WxCpOaScheduleService oaScheduleService = new WxCpOaOaScheduleServiceImpl(this); + private final WxCpAgentWorkBenchService workBenchService = new WxCpAgentWorkBenchServiceImpl(this); + private WxCpKfService kfService = new WxCpKfServiceImpl(this); + + private WxCpExportService exportService = new WxCpExportServiceImpl(this); + + private final WxCpMeetingService meetingService = new WxCpMeetingServiceImpl(this); + private final WxCpCorpGroupService corpGroupService = new WxCpCorpGroupServiceImpl(this); + private final WxCpIntelligentRobotService intelligentRobotService = new WxCpIntelligentRobotServiceImpl(this); /** * 全局的是否正在刷新access token的锁. @@ -68,6 +91,9 @@ public abstract class BaseWxCpServiceImpl implements WxCpService, RequestH */ protected final Object globalAgentJsapiTicketRefreshLock = new Object(); + /** + * The Config storage. + */ protected WxCpConfigStorage configStorage; private WxSessionManager sessionManager = new StandardSessionManager(); @@ -85,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; } } @@ -110,7 +136,7 @@ public String getAgentJsapiTicket(boolean forceRefresh) throws WxErrorException synchronized (this.globalAgentJsapiTicketRefreshLock) { if (this.configStorage.isAgentJsapiTicketExpired()) { String responseContent = this.get(this.configStorage.getApiUrl(GET_AGENT_CONFIG_TICKET), null); - JsonObject jsonObject = new JsonParser().parse(responseContent).getAsJsonObject(); + JsonObject jsonObject = GsonParser.parse(responseContent); this.configStorage.updateAgentJsapiTicket(jsonObject.get("ticket").getAsString(), jsonObject.get("expires_in").getAsInt()); } @@ -135,7 +161,7 @@ public String getJsapiTicket(boolean forceRefresh) throws WxErrorException { synchronized (this.globalJsapiTicketRefreshLock) { if (this.configStorage.isJsapiTicketExpired()) { String responseContent = this.get(this.configStorage.getApiUrl(GET_JSAPI_TICKET), null); - JsonObject tmpJsonObject = new JsonParser().parse(responseContent).getAsJsonObject(); + JsonObject tmpJsonObject = GsonParser.parse(responseContent); this.configStorage.updateJsapiTicket(tmpJsonObject.get("ticket").getAsString(), tmpJsonObject.get("expires_in").getAsInt()); } @@ -169,13 +195,27 @@ public WxJsapiSignature createJsapiSignature(String url) throws WxErrorException } @Override - public WxCpMessageSendResult messageSend(WxCpMessage message) throws WxErrorException { - Integer agentId = message.getAgentId(); - if (null == agentId) { - message.setAgentId(this.getWxCpConfigStorage().getAgentId()); - } + public WxCpAgentJsapiSignature createAgentJsapiSignature(String url) throws WxErrorException { + long timestamp = System.currentTimeMillis() / 1000; + String noncestr = RandomUtils.getRandomStr(); + String jsapiTicket = getAgentJsapiTicket(false); + String signature = SHA1.genWithAmple( + "jsapi_ticket=" + jsapiTicket, + "noncestr=" + noncestr, + "timestamp=" + timestamp, + "url=" + url + ); + + WxCpAgentJsapiSignature jsapiSignature = new WxCpAgentJsapiSignature(); + jsapiSignature.setTimestamp(timestamp); + jsapiSignature.setNonceStr(noncestr); + jsapiSignature.setUrl(url); + jsapiSignature.setSignature(signature); + + jsapiSignature.setCorpid(this.configStorage.getCorpId()); + jsapiSignature.setAgentid(this.configStorage.getAgentId()); - return WxCpMessageSendResult.fromJson(this.post(this.configStorage.getApiUrl(MESSAGE_SEND), message.toJson())); + return jsapiSignature; } @Override @@ -190,9 +230,25 @@ public WxCpMaJsCode2SessionResult jsCode2Session(String jsCode) throws WxErrorEx @Override public String[] getCallbackIp() throws WxErrorException { - String responseContent = get(this.configStorage.getApiUrl(GET_CALLBACK_IP), null); - JsonElement tmpJsonElement = new JsonParser().parse(responseContent); - JsonArray jsonArray = tmpJsonElement.getAsJsonObject().get("ip_list").getAsJsonArray(); + return getIp(GET_CALLBACK_IP); + } + + @Override + public String[] getApiDomainIp() throws WxErrorException { + return getIp(GET_API_DOMAIN_IP); + } + + /** + * 获取 IP + * + * @param suffixUrl 接口URL 后缀 + * @return 返回结果 + * @throws WxErrorException 异常信息 + */ + private String[] getIp(String suffixUrl) throws WxErrorException { + String responseContent = get(this.configStorage.getApiUrl(suffixUrl), null); + JsonObject tmpJsonObject = GsonParser.parse(responseContent); + JsonArray jsonArray = tmpJsonObject.get("ip_list").getAsJsonArray(); String[] ips = new String[jsonArray.size()]; for (int i = 0; i < jsonArray.size(); i++) { ips[i] = jsonArray.get(i).getAsString(); @@ -205,7 +261,8 @@ public WxCpProviderToken getProviderToken(String corpId, String providerSecret) JsonObject jsonObject = new JsonObject(); jsonObject.addProperty("corpid", corpId); jsonObject.addProperty("provider_secret", providerSecret); - return WxCpProviderToken.fromJson(this.post(this.configStorage.getApiUrl(GET_PROVIDER_TOKEN), jsonObject.toString())); + return WxCpProviderToken.fromJson(this.post(this.configStorage.getApiUrl(Tp.GET_PROVIDER_TOKEN), + jsonObject.toString())); } @Override @@ -218,6 +275,32 @@ public String post(String url, String postData) throws WxErrorException { return execute(SimplePostRequestExecutor.create(this), url, postData); } + @Override + public String post(String url, JsonObject jsonObject) throws WxErrorException { + return this.post(url, jsonObject.toString()); + } + + @Override + public String post(String url, ToJson obj) throws WxErrorException { + return this.post(url, obj.toJson()); + } + + @Override + public String upload(String url, CommonUploadParam param) throws WxErrorException { + RequestExecutor executor = CommonUploadRequestExecutor.create(getRequestHttp()); + return this.execute(executor, url, param); + } + + @Override + public String post(String url, Object obj) throws WxErrorException { + return this.post(url, obj.toString()); + } + + @Override + public String postWithoutToken(String url, String postData) throws WxErrorException { + return this.executeNormal(SimplePostRequestExecutor.create(this), url, postData); + } + /** * 向微信端发送请求,在这里执行的策略是当发生access_token过期时才去刷新,然后重新执行请求,而不是全局定时请求. */ @@ -226,12 +309,12 @@ public T execute(RequestExecutor executor, String uri, E data) thro int retryTimes = 0; do { try { - return this.executeInternal(executor, uri, data); + return this.executeInternal(executor, uri, data, false); } catch (WxErrorException e) { if (retryTimes + 1 > this.maxRetryTimes) { log.warn("重试达到最大次数【{}】", this.maxRetryTimes); //最后一次重试失败后,直接抛出异常,不再等待 - throw new RuntimeException("微信服务端异常,超出重试次数"); + throw new WxRuntimeException("微信服务端异常,超出重试次数"); } WxError error = e.getError(); @@ -253,10 +336,22 @@ public T execute(RequestExecutor executor, String uri, E data) thro } while (retryTimes++ < this.maxRetryTimes); log.warn("重试达到最大次数【{}】", this.maxRetryTimes); - throw new RuntimeException("微信服务端异常,超出重试次数"); + throw new WxRuntimeException("微信服务端异常,超出重试次数"); } - protected T executeInternal(RequestExecutor executor, String uri, E data) throws WxErrorException { + /** + * Execute internal t. + * + * @param the type parameter + * @param the type parameter + * @param executor the executor + * @param uri the uri + * @param data the data + * @param doNotAutoRefresh the do not auto refresh + * @return the t + * @throws WxErrorException the wx error exception + */ + protected T executeInternal(RequestExecutor executor, String uri, E data, boolean doNotAutoRefresh) throws WxErrorException { E dataForLog = DataUtils.handleDataWithSecret(data); if (uri.contains("access_token=")) { @@ -272,26 +367,47 @@ protected T executeInternal(RequestExecutor executor, String uri, E return result; } catch (WxErrorException e) { WxError error = e.getError(); - /* - * 发生以下情况时尝试刷新access_token - * 40001 获取access_token时AppSecret错误,或者access_token无效 - * 42001 access_token超时 - * 40014 不合法的access_token,请开发者认真比对access_token的有效性(如是否过期),或查看是否正在为恰当的公众号调用接口 - */ - if (error.getErrorCode() == 42001 || error.getErrorCode() == 40001 || error.getErrorCode() == 40014) { + + if (WxConsts.ACCESS_TOKEN_ERROR_CODES.contains(error.getErrorCode())) { // 强制设置wxCpConfigStorage它的access token过期了,这样在下一次请求里就会刷新access token this.configStorage.expireAccessToken(); - return execute(executor, uri, data); + if (this.getWxCpConfigStorage().autoRefreshToken() && !doNotAutoRefresh) { + log.warn("即将重新获取新的access_token,错误代码:{},错误信息:{}", error.getErrorCode(), error.getErrorMsg()); + //下一次不再自动重试 + //当小程序误调用第三方平台专属接口时,第三方无法使用小程序的access token,如果可以继续自动获取token会导致无限循环重试,直到栈溢出 + return this.executeInternal(executor, uri, data, true); + } } if (error.getErrorCode() != 0) { - log.error("\n【请求地址】: {}\n【请求参数】:{}\n【错误信息】:{}", uriWithAccessToken, dataForLog, error); + log.warn("\n【请求地址】: {}\n【请求参数】:{}\n【错误信息】:{}", uriWithAccessToken, dataForLog, error); throw new WxErrorException(error, e); } return null; } catch (IOException e) { - log.error("\n【请求地址】: {}\n【请求参数】:{}\n【异常信息】:{}", uriWithAccessToken, dataForLog, e.getMessage()); - throw new RuntimeException(e); + log.warn("\n【请求地址】: {}\n【请求参数】:{}\n【异常信息】:{}", uriWithAccessToken, dataForLog, e.getMessage()); + throw new WxRuntimeException(e); + } + } + + /** + * 普通请求,不自动带accessToken + */ + private T executeNormal(RequestExecutor executor, String uri, E data) throws WxErrorException { + try { + T result = executor.execute(uri, data, WxType.CP); + log.debug("\n【请求地址】: {}\n【请求参数】:{}\n【响应数据】:{}", uri, data, result); + return result; + } catch (WxErrorException e) { + WxError error = e.getError(); + if (error.getErrorCode() != 0) { + log.error("\n【请求地址】: {}\n【请求参数】:{}\n【错误信息】:{}", uri, data, error); + throw new WxErrorException(error, e); + } + return null; + } catch (IOException e) { + log.error("\n【请求地址】: {}\n【请求参数】:{}\n【异常信息】:{}", uri, data, e.getMessage()); + throw new WxErrorException(e); } } @@ -345,6 +461,15 @@ public String replaceParty(String mediaId) throws WxErrorException { return post(this.configStorage.getApiUrl(BATCH_REPLACE_PARTY), jsonObject.toString()); } + @Override + public String syncUser(String mediaId) throws WxErrorException { + JsonObject jsonObject = new JsonObject(); + jsonObject.addProperty("media_id", mediaId); + String responseContent = post(this.configStorage.getApiUrl(BATCH_SYNC_USER), jsonObject.toString()); + JsonObject tmpJson = GsonParser.parse(responseContent); + return tmpJson.get("jobid").getAsString(); + } + @Override public String replaceUser(String mediaId) throws WxErrorException { JsonObject jsonObject = new JsonObject(); @@ -353,15 +478,33 @@ public String replaceUser(String mediaId) throws WxErrorException { } @Override - public String getTaskResult(String joinId) throws WxErrorException { - String url = this.configStorage.getApiUrl(BATCH_GET_RESULT + joinId); + public String getTaskResult(String jobId) throws WxErrorException { + String url = this.configStorage.getApiUrl(BATCH_GET_RESULT + jobId); return get(url, null); } + @Override + public String buildQrConnectUrl(String redirectUri, String state) { + return String.format("https://open.work.weixin.qq.com/wwopen/sso/qrConnect?appid=%s&agentid=%s&redirect_uri=%s" + + "&state=%s", + this.configStorage.getCorpId(), this.configStorage.getAgentId(), + URIUtil.encodeURIComponent(redirectUri), StringUtils.trimToEmpty(state)); + } + + /** + * Gets tmp dir file. + * + * @return the tmp dir file + */ public File getTmpDirFile() { return this.tmpDirFile; } + /** + * Sets tmp dir file. + * + * @param tmpDirFile the tmp dir file + */ public void setTmpDirFile(File tmpDirFile) { this.tmpDirFile = tmpDirFile; } @@ -407,10 +550,65 @@ public WxCpChatService getChatService() { } @Override - public WxCpOaService getOAService() { + public WxCpOaService getOaService() { return oaService; } + @Override + public WxCpSchoolService getSchoolService() { + return schoolService; + } + + @Override + public WxCpSchoolUserService getSchoolUserService() { + return schoolUserService; + } + + @Override + public WxCpSchoolHealthService getSchoolHealthService() { + return schoolHealthService; + } + + @Override + public WxCpLivingService getLivingService() { + return livingService; + } + + @Override + public WxCpOaAgentService getOaAgentService() { + return oaAgentService; + } + + @Override + public WxCpOaWeDriveService getOaWeDriveService() { + return oaWeDriveService; + } + + @Override + public WxCpMsgAuditService getMsgAuditService() { + return msgAuditService; + } + + @Override + public WxCpOaCalendarService getOaCalendarService() { + return this.oaCalendarService; + } + + @Override + public WxCpOaMeetingRoomService getOaMeetingRoomService() { + return this.oaMeetingRoomService; + } + + @Override + public WxCpGroupRobotService getGroupRobotService() { + return groupRobotService; + } + + @Override + public WxCpAgentWorkBenchService getWorkBenchService() { + return workBenchService; + } + @Override public WxCpTaskCardService getTaskCardService() { return taskCardService; @@ -456,7 +654,58 @@ public WxCpAgentService getAgentService() { return agentService; } + @Override + public WxCpMessageService getMessageService() { + return this.messageService; + } + + /** + * Sets agent service. + * + * @param agentService the agent service + */ public void setAgentService(WxCpAgentService agentService) { this.agentService = agentService; } + + @Override + public WxCpOaScheduleService getOaScheduleService() { + return this.oaScheduleService; + } + + @Override + public WxCpKfService getKfService() { + return kfService; + } + + @Override + public void setKfService(WxCpKfService kfService) { + this.kfService = kfService; + } + + + @Override + public WxCpExportService getExportService() { + return exportService; + } + + @Override + public void setExportService(WxCpExportService exportService) { + this.exportService = exportService; + } + + @Override + public WxCpMeetingService getMeetingService() { + return meetingService; + } + + @Override + 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/BaseWxCpTpServiceImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/BaseWxCpTpServiceImpl.java deleted file mode 100644 index fe8e3c08e3..0000000000 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/BaseWxCpTpServiceImpl.java +++ /dev/null @@ -1,241 +0,0 @@ -package me.chanjar.weixin.cp.api.impl; - -import com.google.common.base.Joiner; -import com.google.gson.JsonObject; -import com.google.gson.JsonParser; -import lombok.extern.slf4j.Slf4j; -import me.chanjar.weixin.common.WxType; -import me.chanjar.weixin.common.bean.WxAccessToken; -import me.chanjar.weixin.common.error.WxError; -import me.chanjar.weixin.common.error.WxErrorException; -import me.chanjar.weixin.common.util.DataUtils; -import me.chanjar.weixin.common.util.crypto.SHA1; -import me.chanjar.weixin.common.util.http.RequestExecutor; -import me.chanjar.weixin.common.util.http.RequestHttp; -import me.chanjar.weixin.common.util.http.SimpleGetRequestExecutor; -import me.chanjar.weixin.common.util.http.SimplePostRequestExecutor; -import me.chanjar.weixin.cp.api.WxCpTpService; -import me.chanjar.weixin.cp.bean.WxCpMaJsCode2SessionResult; -import me.chanjar.weixin.cp.bean.WxCpTpCorp; -import me.chanjar.weixin.cp.config.WxCpTpConfigStorage; - -import java.io.File; -import java.io.IOException; -import java.util.HashMap; -import java.util.Map; - -import static me.chanjar.weixin.cp.constant.WxCpApiPathConsts.Tp.*; - -/** - * . - * - * @author zhenjun cai - */ -@Slf4j -public abstract class BaseWxCpTpServiceImpl implements WxCpTpService, RequestHttp { - - /** - * 全局的是否正在刷新access token的锁. - */ - protected final Object globalSuiteAccessTokenRefreshLock = new Object(); - - /** - * 全局的是否正在刷新jsapi_ticket的锁. - */ - protected final Object globalSuiteTicketRefreshLock = new Object(); - - protected WxCpTpConfigStorage configStorage; - - /** - * 临时文件目录. - */ - private File tmpDirFile; - private int retrySleepMillis = 1000; - private int maxRetryTimes = 5; - - @Override - public boolean checkSignature(String msgSignature, String timestamp, String nonce, String data) { - try { - 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()); - return false; - } - } - - @Override - public String getSuiteAccessToken() throws WxErrorException { - return getSuiteAccessToken(false); - } - - @Override - public String getSuiteTicket() throws WxErrorException { - return getSuiteTicket(false); - } - - @Override - public String getSuiteTicket(boolean forceRefresh) throws WxErrorException { -// suite ticket由微信服务器推送,不能强制刷新 -// if (forceRefresh) { -// this.configStorage.expireSuiteTicket(); -// } - - if (this.configStorage.isSuiteTicketExpired()) { - // 本地suite ticket 不存在或者过期 - WxError wxError = WxError.fromJson("{\"errcode\":40085, \"errmsg\":\"invaild suite ticket\"}", WxType.CP); - throw new WxErrorException(wxError); - } - return this.configStorage.getSuiteTicket(); - } - - - @Override - public WxCpMaJsCode2SessionResult jsCode2Session(String jsCode) throws WxErrorException { - Map params = new HashMap<>(2); - params.put("js_code", jsCode); - params.put("grant_type", "authorization_code"); - - final String url = configStorage.getApiUrl(JSCODE_TO_SESSION); - return WxCpMaJsCode2SessionResult.fromJson(this.get(url, Joiner.on("&").withKeyValueSeparator("=").join(params))); - } - - - @Override - public WxAccessToken getCorpToken(String authCorpid, String permanentCode) throws WxErrorException { - JsonObject jsonObject = new JsonObject(); - jsonObject.addProperty("auth_corpid", authCorpid); - jsonObject.addProperty("permanent_code", permanentCode); - String result = post(configStorage.getApiUrl(GET_CORP_TOKEN), jsonObject.toString()); - - return WxAccessToken.fromJson(result); - } - - @Override - public WxCpTpCorp getPermanentCode(String authCode) throws WxErrorException { - JsonObject jsonObject = new JsonObject(); - jsonObject.addProperty("auth_code", authCode); - - String result = post(configStorage.getApiUrl(GET_PERMANENT_CODE), jsonObject.toString()); - jsonObject = new JsonParser().parse(result).getAsJsonObject(); - WxCpTpCorp wxCpTpCorp = WxCpTpCorp.fromJson(jsonObject.get("auth_corp_info").getAsJsonObject().toString()); - wxCpTpCorp.setPermanentCode(jsonObject.get("permanent_code").getAsString()); - return wxCpTpCorp; - } - - @Override - public String get(String url, String queryParam) throws WxErrorException { - return execute(SimpleGetRequestExecutor.create(this), url, queryParam); - } - - @Override - public String post(String url, String postData) throws WxErrorException { - return execute(SimplePostRequestExecutor.create(this), url, postData); - } - - /** - * 向微信端发送请求,在这里执行的策略是当发生access_token过期时才去刷新,然后重新执行请求,而不是全局定时请求. - */ - @Override - public T execute(RequestExecutor executor, String uri, E data) throws WxErrorException { - int retryTimes = 0; - do { - try { - return this.executeInternal(executor, uri, data); - } catch (WxErrorException e) { - if (retryTimes + 1 > this.maxRetryTimes) { - log.warn("重试达到最大次数【{}】", this.maxRetryTimes); - //最后一次重试失败后,直接抛出异常,不再等待 - throw new RuntimeException("微信服务端异常,超出重试次数"); - } - - WxError error = e.getError(); - /* - * -1 系统繁忙, 1000ms后重试 - */ - if (error.getErrorCode() == -1) { - int sleepMillis = this.retrySleepMillis * (1 << retryTimes); - try { - log.debug("微信系统繁忙,{} ms 后重试(第{}次)", sleepMillis, retryTimes + 1); - Thread.sleep(sleepMillis); - } catch (InterruptedException e1) { - Thread.currentThread().interrupt(); - } - } else { - throw e; - } - } - } while (retryTimes++ < this.maxRetryTimes); - - log.warn("重试达到最大次数【{}】", this.maxRetryTimes); - throw new RuntimeException("微信服务端异常,超出重试次数"); - } - - protected T executeInternal(RequestExecutor executor, String uri, E data) throws WxErrorException { - E dataForLog = DataUtils.handleDataWithSecret(data); - - if (uri.contains("suite_access_token=")) { - throw new IllegalArgumentException("uri参数中不允许有suite_access_token: " + uri); - } - String suiteAccessToken = getSuiteAccessToken(false); - - String uriWithAccessToken = uri + (uri.contains("?") ? "&" : "?") + "suite_access_token=" + suiteAccessToken; - - try { - T result = executor.execute(uriWithAccessToken, data, WxType.CP); - log.debug("\n【请求地址】: {}\n【请求参数】:{}\n【响应数据】:{}", uriWithAccessToken, dataForLog, result); - return result; - } catch (WxErrorException e) { - WxError error = e.getError(); - /* - * 发生以下情况时尝试刷新suite_access_token - * 42009 suite_access_token已过期 - */ - if (error.getErrorCode() == 42009) { - // 强制设置wxCpTpConfigStorage它的suite access token过期了,这样在下一次请求里就会刷新suite access token - this.configStorage.expireSuiteAccessToken(); - return execute(executor, uri, data); - } - - if (error.getErrorCode() != 0) { - log.error("\n【请求地址】: {}\n【请求参数】:{}\n【错误信息】:{}", uriWithAccessToken, dataForLog, error); - throw new WxErrorException(error, e); - } - return null; - } catch (IOException e) { - log.error("\n【请求地址】: {}\n【请求参数】:{}\n【异常信息】:{}", uriWithAccessToken, dataForLog, e.getMessage()); - throw new RuntimeException(e); - } - } - - @Override - public void setWxCpTpConfigStorage(WxCpTpConfigStorage wxConfigProvider) { - this.configStorage = wxConfigProvider; - this.initHttp(); - } - - @Override - public void setRetrySleepMillis(int retrySleepMillis) { - this.retrySleepMillis = retrySleepMillis; - } - - - @Override - public void setMaxRetryTimes(int maxRetryTimes) { - this.maxRetryTimes = maxRetryTimes; - } - - public File getTmpDirFile() { - return this.tmpDirFile; - } - - public void setTmpDirFile(File tmpDirFile) { - this.tmpDirFile = tmpDirFile; - } - - @Override - public RequestHttp getRequestHttp() { - return this; - } - -} diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpAgentServiceImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpAgentServiceImpl.java index 94aa977b25..cc08d33bb1 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpAgentServiceImpl.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpAgentServiceImpl.java @@ -1,15 +1,17 @@ package me.chanjar.weixin.cp.api.impl; import com.google.gson.JsonObject; -import com.google.gson.JsonParser; import com.google.gson.reflect.TypeToken; import lombok.RequiredArgsConstructor; -import me.chanjar.weixin.common.WxType; +import me.chanjar.weixin.common.api.WxConsts; +import me.chanjar.weixin.common.enums.WxType; import me.chanjar.weixin.common.error.WxError; import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.common.util.json.GsonParser; import me.chanjar.weixin.cp.api.WxCpAgentService; import me.chanjar.weixin.cp.api.WxCpService; import me.chanjar.weixin.cp.bean.WxCpAgent; +import me.chanjar.weixin.cp.bean.WxCpTpAdmin; import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder; import java.util.List; @@ -27,7 +29,7 @@ */ @RequiredArgsConstructor public class WxCpAgentServiceImpl implements WxCpAgentService { - private static final JsonParser JSON_PARSER = new JsonParser(); + private final WxCpService mainService; @@ -45,8 +47,8 @@ public WxCpAgent get(Integer agentId) throws WxErrorException { public void set(WxCpAgent agentInfo) throws WxErrorException { String url = this.mainService.getWxCpConfigStorage().getApiUrl(AGENT_SET); String responseContent = this.mainService.post(url, agentInfo.toJson()); - JsonObject jsonObject = JSON_PARSER.parse(responseContent).getAsJsonObject(); - if (jsonObject.get("errcode").getAsInt() != 0) { + JsonObject jsonObject = GsonParser.parse(responseContent); + if (jsonObject.get(WxConsts.ERR_CODE).getAsInt() != 0) { throw new WxErrorException(WxError.fromJson(responseContent, WxType.CP)); } } @@ -55,8 +57,8 @@ public void set(WxCpAgent agentInfo) throws WxErrorException { public List list() throws WxErrorException { String url = this.mainService.getWxCpConfigStorage().getApiUrl(AGENT_LIST); String responseContent = this.mainService.get(url, null); - JsonObject jsonObject = JSON_PARSER.parse(responseContent).getAsJsonObject(); - if (jsonObject.get("errcode").getAsInt() != 0) { + JsonObject jsonObject = GsonParser.parse(responseContent); + if (jsonObject.get(WxConsts.ERR_CODE).getAsInt() != 0) { throw new WxErrorException(WxError.fromJson(responseContent, WxType.CP)); } @@ -64,4 +66,21 @@ public List list() throws WxErrorException { }.getType()); } + @Override + public WxCpTpAdmin getAdminList(Integer agentId) throws WxErrorException { + if (agentId == null) { + throw new IllegalArgumentException("缺少agentid参数"); + } + + JsonObject jsonObject = new JsonObject(); + jsonObject.addProperty("agentid", agentId); + String url = this.mainService.getWxCpConfigStorage().getApiUrl(AGENT_GET_ADMIN_LIST); + String responseContent = this.mainService.post(url, jsonObject.toString()); + JsonObject respObj = GsonParser.parse(responseContent); + if (respObj.get(WxConsts.ERR_CODE).getAsInt() != 0) { + throw new WxErrorException(WxError.fromJson(responseContent, WxType.CP)); + } + return WxCpTpAdmin.fromJson(responseContent); + } + } 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 new file mode 100644 index 0000000000..b0bbb38642 --- /dev/null +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpAgentWorkBenchServiceImpl.java @@ -0,0 +1,47 @@ +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.WxCpAgentWorkBenchService; +import me.chanjar.weixin.cp.api.WxCpService; +import me.chanjar.weixin.cp.bean.WxCpAgentWorkBench; + +import static me.chanjar.weixin.cp.constant.WxCpApiPathConsts.WorkBench.*; + +/** + * 工作台自定义展示实现 + * + * @author songshiyu + * created at 11:24 2020/9/28 + */ +@RequiredArgsConstructor +public class WxCpAgentWorkBenchServiceImpl implements WxCpAgentWorkBenchService { + private final WxCpService mainService; + + @Override + public void setWorkBenchTemplate(WxCpAgentWorkBench wxCpAgentWorkBench) throws WxErrorException { + final String url = String.format(this.mainService.getWxCpConfigStorage().getApiUrl(WORKBENCH_TEMPLATE_SET)); + this.mainService.post(url, wxCpAgentWorkBench.toTemplateString()); + } + + @Override + public String getWorkBenchTemplate(Long agentId) throws WxErrorException { + final String url = String.format(this.mainService.getWxCpConfigStorage().getApiUrl(WORKBENCH_TEMPLATE_GET)); + JsonObject jsonObject = new JsonObject(); + jsonObject.addProperty("agentid", agentId); + return this.mainService.post(url, jsonObject.toString()); + } + + @Override + public void setWorkBenchData(WxCpAgentWorkBench wxCpAgentWorkBench) throws WxErrorException { + 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/WxCpChatServiceImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpChatServiceImpl.java index b1f374c071..c47785f6e5 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpChatServiceImpl.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpChatServiceImpl.java @@ -1,14 +1,13 @@ package me.chanjar.weixin.cp.api.impl; -import com.google.gson.JsonParser; import lombok.RequiredArgsConstructor; import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.common.util.json.GsonParser; import me.chanjar.weixin.common.util.json.WxGsonBuilder; import me.chanjar.weixin.cp.api.WxCpChatService; import me.chanjar.weixin.cp.api.WxCpService; -import me.chanjar.weixin.cp.bean.WxCpAppChatMessage; import me.chanjar.weixin.cp.bean.WxCpChat; -import me.chanjar.weixin.cp.constant.WxCpApiPathConsts; +import me.chanjar.weixin.cp.bean.message.WxCpAppChatMessage; import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder; import org.apache.commons.lang3.StringUtils; @@ -25,11 +24,10 @@ */ @RequiredArgsConstructor public class WxCpChatServiceImpl implements WxCpChatService { - private static final JsonParser JSON_PARSER = new JsonParser(); private final WxCpService cpService; @Override - public String chatCreate(String name, String owner, List users, String chatId) throws WxErrorException { + public String create(String name, String owner, List users, String chatId) throws WxErrorException { Map data = new HashMap<>(4); if (StringUtils.isNotBlank(name)) { data.put("name", name); @@ -45,16 +43,11 @@ public String chatCreate(String name, String owner, List users, String c } final String url = this.cpService.getWxCpConfigStorage().getApiUrl(APPCHAT_CREATE); String result = this.cpService.post(url, WxGsonBuilder.create().toJson(data)); - return new JsonParser().parse(result).getAsJsonObject().get("chatid").getAsString(); - } - - @Override - public String create(String name, String owner, List users, String chatId) throws WxErrorException { - return this.chatCreate(name, owner, users, chatId); + return GsonParser.parse(result).get("chatid").getAsString(); } @Override - public void chatUpdate(String chatId, String name, String owner, List usersToAdd, List usersToDelete) + public void update(String chatId, String name, String owner, List usersToAdd, List usersToDelete) throws WxErrorException { Map data = new HashMap<>(5); if (StringUtils.isNotBlank(chatId)) { @@ -78,24 +71,13 @@ public void chatUpdate(String chatId, String name, String owner, List us } @Override - public void update(String chatId, String name, String owner, List usersToAdd, List usersToDelete) - throws WxErrorException { - chatUpdate(chatId, name, owner, usersToAdd, usersToDelete); - } - - @Override - public WxCpChat chatGet(String chatId) throws WxErrorException { + public WxCpChat get(String chatId) throws WxErrorException { final String url = this.cpService.getWxCpConfigStorage().getApiUrl(APPCHAT_GET_CHATID + chatId); String result = this.cpService.get(url, null); - final String chatInfo = JSON_PARSER.parse(result).getAsJsonObject().getAsJsonObject("chat_info").toString(); + final String chatInfo = GsonParser.parse(result).getAsJsonObject("chat_info").toString(); return WxCpGsonBuilder.create().fromJson(chatInfo, WxCpChat.class); } - @Override - public WxCpChat get(String chatId) throws WxErrorException { - return this.chatGet(chatId); - } - @Override public void sendMsg(WxCpAppChatMessage message) throws WxErrorException { this.cpService.post(this.cpService.getWxCpConfigStorage().getApiUrl(APPCHAT_SEND), message.toJson()); 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 new file mode 100644 index 0000000000..e3dc1cbe1c --- /dev/null +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpCorpGroupServiceImpl.java @@ -0,0 +1,45 @@ +package me.chanjar.weixin.cp.api.impl; + +import com.google.gson.JsonObject; +import com.google.gson.reflect.TypeToken; +import lombok.RequiredArgsConstructor; +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.common.util.json.GsonParser; +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.util.json.WxCpGsonBuilder; + +import java.util.List; + +import static me.chanjar.weixin.cp.constant.WxCpApiPathConsts.CorpGroup.LIST_SHARE_APP_INFO; + +/** + * 企业互联相关接口实现类 + * + * @author libo <422423229@qq.com> + * @since 2023-02-27 9:57 PM + */ +@RequiredArgsConstructor +public class WxCpCorpGroupServiceImpl implements WxCpCorpGroupService { + private final WxCpService cpService; + + @Override + public List listAppShareInfo(Integer agentId, Integer businessType, String corpId, + Integer limit, String cursor) throws WxErrorException { + final String url = this.cpService.getWxCpConfigStorage().getApiUrl(LIST_SHARE_APP_INFO); + JsonObject jsonObject = new JsonObject(); + jsonObject.addProperty("agentid", agentId); + jsonObject.addProperty("corpid", corpId); + jsonObject.addProperty("business_type", businessType); + jsonObject.addProperty("limit", limit); + jsonObject.addProperty("cursor", cursor); + String responseContent = this.cpService.post(url, jsonObject); + JsonObject tmpJson = GsonParser.parse(responseContent); + + return WxCpGsonBuilder.create().fromJson(tmpJson.get("corp_list"), + new TypeToken>() { + }.getType() + ); + } +} diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpDepartmentServiceImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpDepartmentServiceImpl.java index fb2224e335..b6d9cf29b1 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpDepartmentServiceImpl.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpDepartmentServiceImpl.java @@ -1,15 +1,14 @@ package me.chanjar.weixin.cp.api.impl; -import com.google.gson.JsonElement; -import com.google.gson.JsonParser; +import com.google.gson.JsonObject; import com.google.gson.reflect.TypeToken; import lombok.RequiredArgsConstructor; import me.chanjar.weixin.common.error.WxErrorException; import me.chanjar.weixin.common.util.json.GsonHelper; +import me.chanjar.weixin.common.util.json.GsonParser; import me.chanjar.weixin.cp.api.WxCpDepartmentService; import me.chanjar.weixin.cp.api.WxCpService; import me.chanjar.weixin.cp.bean.WxCpDepart; -import me.chanjar.weixin.cp.constant.WxCpApiPathConsts; import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder; import java.util.List; @@ -32,8 +31,20 @@ public class WxCpDepartmentServiceImpl implements WxCpDepartmentService { public Long create(WxCpDepart depart) throws WxErrorException { String url = this.mainService.getWxCpConfigStorage().getApiUrl(DEPARTMENT_CREATE); String responseContent = this.mainService.post(url, depart.toJson()); - JsonElement tmpJsonElement = new JsonParser().parse(responseContent); - return GsonHelper.getAsLong(tmpJsonElement.getAsJsonObject().get("id")); + JsonObject tmpJsonObject = GsonParser.parse(responseContent); + return GsonHelper.getAsLong(tmpJsonObject.get("id")); + } + + @Override + public WxCpDepart get(Long id) throws WxErrorException { + String url = String.format(this.mainService.getWxCpConfigStorage().getApiUrl(DEPARTMENT_GET), id); + String responseContent = this.mainService.get(url, null); + JsonObject tmpJsonObject = GsonParser.parse(responseContent); + return WxCpGsonBuilder.create() + .fromJson(tmpJsonObject.get("department"), + new TypeToken() { + }.getType() + ); } @Override @@ -56,9 +67,25 @@ public List list(Long id) throws WxErrorException { } String responseContent = this.mainService.get(url, null); - JsonElement tmpJsonElement = new JsonParser().parse(responseContent); + JsonObject tmpJsonObject = GsonParser.parse(responseContent); + return WxCpGsonBuilder.create() + .fromJson(tmpJsonObject.get("department"), + new TypeToken>() { + }.getType() + ); + } + + @Override + public List simpleList(Long id) throws WxErrorException { + String url = this.mainService.getWxCpConfigStorage().getApiUrl(DEPARTMENT_SIMPLE_LIST); + if (id != null) { + url += "?id=" + id; + } + + String responseContent = this.mainService.get(url, null); + JsonObject tmpJsonObject = GsonParser.parse(responseContent); return WxCpGsonBuilder.create() - .fromJson(tmpJsonElement.getAsJsonObject().get("department"), + .fromJson(tmpJsonObject.get("department_id"), new TypeToken>() { }.getType() ); diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpExportServiceImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpExportServiceImpl.java new file mode 100644 index 0000000000..638dd4e1c3 --- /dev/null +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpExportServiceImpl.java @@ -0,0 +1,58 @@ +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.common.util.json.GsonParser; +import me.chanjar.weixin.cp.api.WxCpExportService; +import me.chanjar.weixin.cp.api.WxCpService; +import me.chanjar.weixin.cp.bean.export.WxCpExportRequest; +import me.chanjar.weixin.cp.bean.export.WxCpExportResult; +import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder; + +import static me.chanjar.weixin.cp.constant.WxCpApiPathConsts.Export.*; + +/** + * 异步导出接口 + * + * @author zhongjun created on 2022/4/21 + */ +@RequiredArgsConstructor +public class WxCpExportServiceImpl implements WxCpExportService { + + private final WxCpService mainService; + + @Override + public String simpleUser(WxCpExportRequest params) throws WxErrorException { + return export(SIMPLE_USER, params); + } + + @Override + public String user(WxCpExportRequest params) throws WxErrorException { + return export(USER, params); + } + + @Override + public String department(WxCpExportRequest params) throws WxErrorException { + return export(DEPARTMENT, params); + } + + @Override + public String tagUser(WxCpExportRequest params) throws WxErrorException { + return export(TAG_USER, params); + } + + @Override + public WxCpExportResult getResult(String jobId) throws WxErrorException { + String url = String.format(this.mainService.getWxCpConfigStorage().getApiUrl(GET_RESULT), jobId); + String responseContent = this.mainService.get(url, null); + return WxCpGsonBuilder.create().fromJson(responseContent, WxCpExportResult.class); + } + + private String export(String path, WxCpExportRequest params) throws WxErrorException { + String url = this.mainService.getWxCpConfigStorage().getApiUrl(path); + String responseContent = this.mainService.post(url, params.toJson()); + JsonObject tmpJson = GsonParser.parse(responseContent); + return tmpJson.get("jobid").getAsString(); + } +} 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 8a5b7d56e8..d43589595f 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpExternalContactServiceImpl.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpExternalContactServiceImpl.java @@ -1,41 +1,919 @@ package me.chanjar.weixin.cp.api.impl; +import static me.chanjar.weixin.cp.constant.WxCpApiPathConsts.ExternalContact.*; + +import com.google.gson.Gson; +import com.google.gson.JsonObject; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.util.Collections; +import java.util.Date; +import java.util.List; +import java.util.UUID; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; +import me.chanjar.weixin.common.bean.result.WxMediaUploadResult; +import me.chanjar.weixin.common.error.WxCpErrorMsgEnum; import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.common.error.WxRuntimeException; +import me.chanjar.weixin.common.util.BeanUtils; +import me.chanjar.weixin.common.util.fs.FileUtils; +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.WxCpExternalContactService; import me.chanjar.weixin.cp.api.WxCpService; -import me.chanjar.weixin.cp.bean.WxCpUserExternalContactInfo; -import me.chanjar.weixin.cp.bean.WxCpUserExternalContactList; -import me.chanjar.weixin.cp.bean.WxCpUserWithExternalPermission; +import me.chanjar.weixin.cp.bean.WxCpBaseResp; +import me.chanjar.weixin.cp.bean.external.*; +import me.chanjar.weixin.cp.bean.external.acquisition.*; +import me.chanjar.weixin.cp.bean.external.contact.*; +import me.chanjar.weixin.cp.bean.external.interceptrule.WxCpInterceptRule; +import me.chanjar.weixin.cp.bean.external.interceptrule.WxCpInterceptRuleAddRequest; +import me.chanjar.weixin.cp.bean.external.interceptrule.WxCpInterceptRuleInfo; +import me.chanjar.weixin.cp.bean.external.interceptrule.WxCpInterceptRuleList; +import org.apache.commons.lang3.ArrayUtils; +import org.apache.commons.lang3.StringUtils; -import java.util.List; +/** + * The type Wx cp external contact service. + * + * @author 曹祖鹏, yuanqixun, Mr.Pan, Wang_Wong + */ +@RequiredArgsConstructor +public class WxCpExternalContactServiceImpl implements WxCpExternalContactService { + private final WxCpService mainService; -import static me.chanjar.weixin.cp.constant.WxCpApiPathConsts.ExternalContact.*; + @Override + public WxCpContactWayResult addContactWay(WxCpContactWayInfo info) throws WxErrorException { -public class WxCpExternalContactServiceImpl implements WxCpExternalContactService { - private WxCpService mainService; + if (info.getContactWay().getUsers() != null && info.getContactWay().getUsers().size() > 100) { + throw new WxRuntimeException("「联系我」使用人数默认限制不超过100人(包括部门展开后的人数)"); + } + + final String url = this.mainService.getWxCpConfigStorage().getApiUrl(ADD_CONTACT_WAY); - public WxCpExternalContactServiceImpl(WxCpService mainService) { - this.mainService = mainService; + return WxCpContactWayResult.fromJson(this.mainService.post(url, info.getContactWay().toJson())); } @Override - public WxCpUserExternalContactInfo getExternalContact(String userId) throws WxErrorException { - final String url = this.mainService.getWxCpConfigStorage().getApiUrl(GET_EXTERNAL_CONTACT + userId); - String responseContent = this.mainService.get(url, null); - return WxCpUserExternalContactInfo.fromJson(responseContent); + public WxCpContactWayInfo getContactWay(String configId) throws WxErrorException { + JsonObject json = new JsonObject(); + json.addProperty("config_id", configId); + + final String url = this.mainService.getWxCpConfigStorage().getApiUrl(GET_CONTACT_WAY); + 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())) { + throw new WxRuntimeException("更新「联系我」方式需要指定configId"); + } + if (info.getContactWay().getUsers() != null && info.getContactWay().getUsers().size() > 100) { + throw new WxRuntimeException("「联系我」使用人数默认限制不超过100人(包括部门展开后的人数)"); + } + + final String url = this.mainService.getWxCpConfigStorage().getApiUrl(UPDATE_CONTACT_WAY); + + return WxCpBaseResp.fromJson(this.mainService.post(url, info.getContactWay().toJson())); + } + + @Override + public WxCpBaseResp deleteContactWay(String configId) throws WxErrorException { + JsonObject json = new JsonObject(); + json.addProperty("config_id", configId); + + final String url = this.mainService.getWxCpConfigStorage().getApiUrl(DEL_CONTACT_WAY); + + return WxCpBaseResp.fromJson(this.mainService.post(url, json.toString())); + } + + @Override + public WxCpBaseResp closeTempChat(String userId, String externalUserId) throws WxErrorException { + + JsonObject json = new JsonObject(); + json.addProperty("userid", userId); + json.addProperty("external_userid", externalUserId); + + + final String url = this.mainService.getWxCpConfigStorage().getApiUrl(CLOSE_TEMP_CHAT); + + return WxCpBaseResp.fromJson(this.mainService.post(url, json.toString())); + } + + @Override + public WxCpExternalContactInfo getExternalContact(String externalUserId) throws WxErrorException { + final String url = this.mainService.getWxCpConfigStorage().getApiUrl(GET_EXTERNAL_CONTACT + externalUserId); + return WxCpExternalContactInfo.fromJson(this.mainService.get(url, null)); + } + + @Override + public WxCpExternalContactInfo getContactDetail(String externalUserId, String cursor) throws WxErrorException { + String params = externalUserId; + if (StringUtils.isNotEmpty(cursor)) { + params = params + "&cursor=" + cursor; + } + final String url = this.mainService.getWxCpConfigStorage().getApiUrl(GET_CONTACT_DETAIL + params); + return WxCpExternalContactInfo.fromJson(this.mainService.get(url, null)); + } + + @Override + public String convertToOpenid(String externalUserId) throws WxErrorException { + JsonObject json = new JsonObject(); + json.addProperty("external_userid", externalUserId); + final String url = this.mainService.getWxCpConfigStorage().getApiUrl(CONVERT_TO_OPENID); + String responseContent = this.mainService.post(url, json.toString()); + return GsonParser.parse(responseContent).get("openid").getAsString(); + } + + @Override + public String unionidToExternalUserid(String unionid, String openid) throws WxErrorException { + JsonObject json = new JsonObject(); + json.addProperty("unionid", unionid); + if (StringUtils.isNotEmpty(openid)) { + json.addProperty("openid", openid); + } + final String url = this.mainService.getWxCpConfigStorage().getApiUrl(UNIONID_TO_EXTERNAL_USERID); + String responseContent = this.mainService.post(url, json.toString()); + return GsonParser.parse(responseContent).get("external_userid").getAsString(); + } + + @Override + public String toServiceExternalUserid(String externalUserid) throws WxErrorException { + JsonObject json = new JsonObject(); + json.addProperty("external_userid", externalUserid); + final String url = this.mainService.getWxCpConfigStorage().getApiUrl(TO_SERVICE_EXTERNAL_USERID); + String responseContent = this.mainService.post(url, json.toString()); + return GsonParser.parse(responseContent).get("external_userid").getAsString(); + } + + @Override + public String fromServiceExternalUserid(String externalUserid, String sourceAgentId) throws WxErrorException { + JsonObject json = new JsonObject(); + json.addProperty("external_userid", externalUserid); + json.addProperty("source_agentid", sourceAgentId); + final String url = this.mainService.getWxCpConfigStorage().getApiUrl(FROM_SERVICE_EXTERNAL_USERID); + String responseContent = this.mainService.post(url, json.toString()); + return GsonParser.parse(responseContent).get("external_userid").getAsString(); + } + + @Override + public WxCpExternalUserIdList unionidToExternalUserid3rd(String unionid, String openid, + String corpid) throws WxErrorException { + JsonObject json = new JsonObject(); + json.addProperty("unionid", unionid); + json.addProperty("openid", openid); + if (StringUtils.isNotEmpty(corpid)) { + json.addProperty("corpid", corpid); + } + final String url = this.mainService.getWxCpConfigStorage().getApiUrl(UNIONID_TO_EXTERNAL_USERID_3RD); + return WxCpExternalUserIdList.fromJson(this.mainService.post(url, json.toString())); + } + + @Override + public WxCpNewExternalUserIdList getNewExternalUserId(String[] externalUserIdList) throws WxErrorException { + JsonObject json = new JsonObject(); + if (ArrayUtils.isNotEmpty(externalUserIdList)) { + json.add("external_userid_list", new Gson().toJsonTree(externalUserIdList).getAsJsonArray()); + } + final String url = this.mainService.getWxCpConfigStorage().getApiUrl(GET_NEW_EXTERNAL_USERID); + return WxCpNewExternalUserIdList.fromJson(this.mainService.post(url, json.toString())); + } + + @Override + public WxCpBaseResp finishExternalUserIdMigration(String corpid) throws WxErrorException { + JsonObject json = new JsonObject(); + json.addProperty("corpid", corpid); + final String url = this.mainService.getWxCpConfigStorage().getApiUrl(FINISH_EXTERNAL_USERID_MIGRATION); + return WxCpBaseResp.fromJson(this.mainService.post(url, json.toString())); + } + + @Override + public String opengidToChatid(String opengid) throws WxErrorException { + JsonObject json = new JsonObject(); + json.addProperty("opengid", opengid); + final String url = this.mainService.getWxCpConfigStorage().getApiUrl(OPENID_TO_CHATID); + String responseContent = this.mainService.post(url, json.toString()); + return GsonParser.parse(responseContent).get("chat_id").getAsString(); + } + + @Override + public WxCpExternalContactBatchInfo getContactDetailBatch(String[] userIdList, String cursor, Integer limit) + throws WxErrorException { + final String url = this.mainService.getWxCpConfigStorage().getApiUrl(GET_CONTACT_DETAIL_BATCH); + JsonObject json = new JsonObject(); + json.add("userid_list", new Gson().toJsonTree(userIdList).getAsJsonArray()); + if (StringUtils.isNotBlank(cursor)) { + json.addProperty("cursor", cursor); + } + if (limit != null) { + json.addProperty("limit", limit); + } + String responseContent = this.mainService.post(url, json.toString()); + return WxCpExternalContactBatchInfo.fromJson(responseContent); + } + + @Override + public WxCpExternalContactListInfo getContactList(String cursor, Integer limit) throws WxErrorException { + final String url = this.mainService.getWxCpConfigStorage().getApiUrl(GET_CONTACT_LIST); + JsonObject json = new JsonObject(); + if (StringUtils.isNotBlank(cursor)) { + json.addProperty("cursor", cursor); + } + if (limit != null) { + json.addProperty("limit", limit); + } + String responseContent = this.mainService.post(url, json.toString()); + return WxCpExternalContactListInfo.fromJson(responseContent); + } + + @Override + public void updateRemark(WxCpUpdateRemarkRequest request) throws WxErrorException { + final String url = this.mainService.getWxCpConfigStorage().getApiUrl(UPDATE_REMARK); + this.mainService.post(url, request.toJson()); } @Override public List listExternalContacts(String userId) throws WxErrorException { final String url = this.mainService.getWxCpConfigStorage().getApiUrl(LIST_EXTERNAL_CONTACT + userId); - String responseContent = this.mainService.get(url, null); - return WxCpUserExternalContactList.fromJson(responseContent).getExternalUserId(); + try { + String responseContent = this.mainService.get(url, null); + return WxCpUserExternalContactList.fromJson(responseContent).getExternalUserId(); + } catch (WxErrorException e) { + // not external contact,无客户则返回空列表 + if (e.getError().getErrorCode() == WxCpErrorMsgEnum.CODE_84061.getCode()) { + return Collections.emptyList(); + } + throw e; + } } @Override - public List listFollowUser() throws WxErrorException { + public List listFollowers() throws WxErrorException { final String url = this.mainService.getWxCpConfigStorage().getApiUrl(GET_FOLLOW_USER_LIST); String responseContent = this.mainService.get(url, null); - return WxCpUserWithExternalPermission.fromJson(responseContent).getFollowUser(); + return WxCpUserWithExternalPermission.fromJson(responseContent).getFollowers(); + } + + @Override + public WxCpUserExternalUnassignList listUnassignedList(Integer pageIndex, String cursor, Integer pageSize) throws WxErrorException { + JsonObject json = new JsonObject(); + if (pageIndex != null) { + json.addProperty("page_id", pageIndex); + } + json.addProperty("cursor", StringUtils.isEmpty(cursor) ? "" : cursor); + json.addProperty("page_size", pageSize == null ? 1000 : pageSize); + final String url = this.mainService.getWxCpConfigStorage().getApiUrl(LIST_UNASSIGNED_CONTACT); + return WxCpUserExternalUnassignList.fromJson(this.mainService.post(url, json.toString())); + } + + @Override + public WxCpBaseResp transferExternalContact(String externalUserid, String handOverUserid, String takeOverUserid) throws WxErrorException { + JsonObject json = new JsonObject(); + json.addProperty("external_userid", externalUserid); + json.addProperty("handover_userid", handOverUserid); + json.addProperty("takeover_userid", takeOverUserid); + final String url = this.mainService.getWxCpConfigStorage().getApiUrl(TRANSFER_UNASSIGNED_CONTACT); + return WxCpBaseResp.fromJson(this.mainService.post(url, json.toString())); + } + + @Override + public WxCpUserTransferCustomerResp transferCustomer(WxCpUserTransferCustomerReq req) throws WxErrorException { + BeanUtils.checkRequiredFields(req); + final String url = this.mainService.getWxCpConfigStorage().getApiUrl(TRANSFER_CUSTOMER); + final String result = this.mainService.post(url, req.toJson()); + return WxCpUserTransferCustomerResp.fromJson(result); + } + + @Override + public WxCpUserTransferResultResp transferResult(String handOverUserid, String takeOverUserid, + String cursor) throws WxErrorException { + JsonObject json = new JsonObject(); + json.addProperty("cursor", cursor); + json.addProperty("handover_userid", handOverUserid); + json.addProperty("takeover_userid", takeOverUserid); + final String url = this.mainService.getWxCpConfigStorage().getApiUrl(TRANSFER_RESULT); + return WxCpUserTransferResultResp.fromJson(this.mainService.post(url, json.toString())); + } + + @Override + public WxCpUserTransferCustomerResp resignedTransferCustomer(WxCpUserTransferCustomerReq req) + throws WxErrorException { + BeanUtils.checkRequiredFields(req); + final String url = this.mainService.getWxCpConfigStorage().getApiUrl(RESIGNED_TRANSFER_CUSTOMER); + return WxCpUserTransferCustomerResp.fromJson(this.mainService.post(url, req.toJson())); + } + + @Override + public WxCpUserTransferResultResp resignedTransferResult(String handOverUserid, + String takeOverUserid, String cursor) + throws WxErrorException { + JsonObject json = new JsonObject(); + json.addProperty("cursor", cursor); + json.addProperty("handover_userid", handOverUserid); + json.addProperty("takeover_userid", takeOverUserid); + final String url = this.mainService.getWxCpConfigStorage().getApiUrl(RESIGNED_TRANSFER_RESULT); + return WxCpUserTransferResultResp.fromJson(this.mainService.post(url, json.toString())); + } + + @Override + public WxCpUserExternalGroupChatList listGroupChat(Integer pageIndex, Integer pageSize, int status, + String[] userIds, String[] partyIds) throws WxErrorException { + JsonObject json = new JsonObject(); + json.addProperty("offset", pageIndex == null ? 0 : pageIndex); + json.addProperty("limit", pageSize == null ? 100 : pageSize); + json.addProperty("status_filter", status); + if (ArrayUtils.isNotEmpty(userIds) || ArrayUtils.isNotEmpty(partyIds)) { + JsonObject ownerFilter = new JsonObject(); + if (ArrayUtils.isNotEmpty(userIds)) { + ownerFilter.add("userid_list", new Gson().toJsonTree(userIds).getAsJsonArray()); + } + if (ArrayUtils.isNotEmpty(partyIds)) { + ownerFilter.add("partyid_list", new Gson().toJsonTree(partyIds).getAsJsonArray()); + } + json.add("owner_filter", ownerFilter); + } + final String url = this.mainService.getWxCpConfigStorage().getApiUrl(GROUP_CHAT_LIST); + return WxCpUserExternalGroupChatList.fromJson(this.mainService.post(url, json.toString())); + } + + @Override + public WxCpUserExternalGroupChatList listGroupChat(Integer limit, String cursor, int status, String[] userIds) throws WxErrorException { + JsonObject json = new JsonObject(); + json.addProperty("cursor", cursor == null ? "" : cursor); + json.addProperty("limit", limit == null ? 100 : limit); + json.addProperty("status_filter", status); + if (ArrayUtils.isNotEmpty(userIds)) { + JsonObject ownerFilter = new JsonObject(); + if (ArrayUtils.isNotEmpty(userIds)) { + ownerFilter.add("userid_list", new Gson().toJsonTree(userIds).getAsJsonArray()); + } + json.add("owner_filter", ownerFilter); + } + final String url = this.mainService.getWxCpConfigStorage().getApiUrl(GROUP_CHAT_LIST); + return WxCpUserExternalGroupChatList.fromJson(this.mainService.post(url, json.toString())); + } + + @Override + public WxCpUserExternalGroupChatInfo getGroupChat(String chatId, Integer needName) throws WxErrorException { + JsonObject json = new JsonObject(); + json.addProperty("chat_id", chatId); + json.addProperty("need_name", needName); + final String url = this.mainService.getWxCpConfigStorage().getApiUrl(GROUP_CHAT_INFO); + return WxCpUserExternalGroupChatInfo.fromJson(this.mainService.post(url, json.toString())); + } + + @Override + public WxCpUserExternalGroupChatTransferResp transferGroupChat(String[] chatIds, String newOwner) throws WxErrorException { + JsonObject json = new JsonObject(); + if (ArrayUtils.isNotEmpty(chatIds)) { + json.add("chat_id_list", new Gson().toJsonTree(chatIds).getAsJsonArray()); + } + json.addProperty("new_owner", newOwner); + final String url = this.mainService.getWxCpConfigStorage().getApiUrl(GROUP_CHAT_TRANSFER); + return WxCpUserExternalGroupChatTransferResp.fromJson(this.mainService.post(url, json.toString())); + } + + @Override + public WxCpUserExternalGroupChatTransferResp onjobTransferGroupChat(String[] chatIds, String newOwner) throws WxErrorException { + JsonObject json = new JsonObject(); + if (ArrayUtils.isNotEmpty(chatIds)) { + json.add("chat_id_list", new Gson().toJsonTree(chatIds).getAsJsonArray()); + } + json.addProperty("new_owner", newOwner); + final String url = this.mainService.getWxCpConfigStorage().getApiUrl(GROUP_CHAT_ONJOB_TRANSFER); + return WxCpUserExternalGroupChatTransferResp.fromJson(this.mainService.post(url, json.toString())); + } + + @Override + public WxCpUserExternalUserBehaviorStatistic getUserBehaviorStatistic(Date startTime, Date endTime, + String[] userIds, String[] partyIds) throws WxErrorException { + JsonObject json = new JsonObject(); + json.addProperty("start_time", startTime.getTime() / 1000); + json.addProperty("end_time", endTime.getTime() / 1000); + if (ArrayUtils.isNotEmpty(userIds) || ArrayUtils.isNotEmpty(partyIds)) { + if (ArrayUtils.isNotEmpty(userIds)) { + json.add("userid", new Gson().toJsonTree(userIds).getAsJsonArray()); + } + if (ArrayUtils.isNotEmpty(partyIds)) { + json.add("partyid", new Gson().toJsonTree(partyIds).getAsJsonArray()); + } + } + final String url = this.mainService.getWxCpConfigStorage().getApiUrl(LIST_USER_BEHAVIOR_DATA); + return WxCpUserExternalUserBehaviorStatistic.fromJson(this.mainService.post(url, json.toString())); + } + + @Override + public WxCpUserExternalGroupChatStatistic getGroupChatStatistic(Date startTime, Integer orderBy, Integer orderAsc, + Integer pageIndex, Integer pageSize, + String[] userIds, String[] partyIds) throws WxErrorException { + JsonObject json = new JsonObject(); + json.addProperty("day_begin_time", startTime.getTime() / 1000); + json.addProperty("order_by", orderBy == null ? 1 : orderBy); + json.addProperty("order_asc", orderAsc == null ? 0 : orderAsc); + json.addProperty("offset", pageIndex == null ? 0 : pageIndex); + json.addProperty("limit", pageSize == null ? 500 : pageSize); + if (ArrayUtils.isNotEmpty(userIds) || ArrayUtils.isNotEmpty(partyIds)) { + JsonObject ownerFilter = new JsonObject(); + if (ArrayUtils.isNotEmpty(userIds)) { + ownerFilter.add("userid_list", new Gson().toJsonTree(userIds).getAsJsonArray()); + } + if (ArrayUtils.isNotEmpty(partyIds)) { + ownerFilter.add("partyid_list", new Gson().toJsonTree(partyIds).getAsJsonArray()); + } + json.add("owner_filter", ownerFilter); + } + final String url = this.mainService.getWxCpConfigStorage().getApiUrl(LIST_GROUP_CHAT_DATA); + return WxCpUserExternalGroupChatStatistic.fromJson(this.mainService.post(url, json.toString())); + } + + @Override + public WxCpMsgTemplateAddResult addMsgTemplate(WxCpMsgTemplate wxCpMsgTemplate) throws WxErrorException { + final String url = this.mainService.getWxCpConfigStorage().getApiUrl(ADD_MSG_TEMPLATE); + return WxCpMsgTemplateAddResult.fromJson(this.mainService.post(url, wxCpMsgTemplate.toJson())); + } + + @Override + public WxCpBaseResp remindGroupMsgSend(String msgId) throws WxErrorException { + JsonObject params = new JsonObject(); + params.addProperty("msgid", msgId); + final String url = this.mainService.getWxCpConfigStorage() + .getApiUrl(REMIND_GROUP_MSG_SEND); + return WxCpBaseResp.fromJson(this.mainService.post(url, params.toString())); + } + + @Override + public WxCpBaseResp cancelGroupMsgSend(String msgId) throws WxErrorException { + JsonObject params = new JsonObject(); + params.addProperty("msgid", msgId); + final String url = this.mainService.getWxCpConfigStorage() + .getApiUrl(CANCEL_GROUP_MSG_SEND); + return WxCpBaseResp.fromJson(this.mainService.post(url, params.toString())); + } + + @Override + public void sendWelcomeMsg(WxCpWelcomeMsg msg) throws WxErrorException { + final String url = this.mainService.getWxCpConfigStorage().getApiUrl(SEND_WELCOME_MSG); + this.mainService.post(url, msg.toJson()); + } + + @Override + public WxCpUserExternalTagGroupList getCorpTagList(String[] tagId) throws WxErrorException { + JsonObject json = new JsonObject(); + if (ArrayUtils.isNotEmpty(tagId)) { + json.add("tag_id", new Gson().toJsonTree(tagId).getAsJsonArray()); + } + final String url = this.mainService.getWxCpConfigStorage().getApiUrl(GET_CORP_TAG_LIST); + return WxCpUserExternalTagGroupList.fromJson(this.mainService.post(url, json.toString())); + } + + @Override + public WxCpUserExternalTagGroupList getCorpTagList(String[] tagId, String[] groupId) throws WxErrorException { + JsonObject json = new JsonObject(); + if (ArrayUtils.isNotEmpty(tagId)) { + json.add("tag_id", new Gson().toJsonTree(tagId).getAsJsonArray()); + } + if (ArrayUtils.isNotEmpty(groupId)) { + json.add("group_id", new Gson().toJsonTree(groupId).getAsJsonArray()); + } + final String url = this.mainService.getWxCpConfigStorage().getApiUrl(GET_CORP_TAG_LIST); + return WxCpUserExternalTagGroupList.fromJson(this.mainService.post(url, json.toString())); + } + + @Override + public WxCpUserExternalTagGroupInfo addCorpTag(WxCpUserExternalTagGroupInfo tagGroup) throws WxErrorException { + + final String url = this.mainService.getWxCpConfigStorage().getApiUrl(ADD_CORP_TAG); + return WxCpUserExternalTagGroupInfo.fromJson(this.mainService.post(url, tagGroup.getTagGroup().toJson())); + } + + @Override + public WxCpBaseResp editCorpTag(String id, String name, Integer order) throws WxErrorException { + + JsonObject json = new JsonObject(); + json.addProperty("id", id); + json.addProperty("name", name); + json.addProperty("order", order); + final String url = this.mainService.getWxCpConfigStorage().getApiUrl(EDIT_CORP_TAG); + return WxCpBaseResp.fromJson(this.mainService.post(url, json.toString())); + } + + @Override + public WxCpBaseResp delCorpTag(String[] tagId, String[] groupId) throws WxErrorException { + JsonObject json = new JsonObject(); + if (ArrayUtils.isNotEmpty(tagId)) { + json.add("tag_id", new Gson().toJsonTree(tagId).getAsJsonArray()); + } + if (ArrayUtils.isNotEmpty(groupId)) { + json.add("group_id", new Gson().toJsonTree(groupId).getAsJsonArray()); + } + + final String url = this.mainService.getWxCpConfigStorage().getApiUrl(DEL_CORP_TAG); + return WxCpBaseResp.fromJson(this.mainService.post(url, json.toString())); + } + + @Override + public WxCpBaseResp markTag(String userid, String externalUserid, String[] addTag, String[] removeTag) throws WxErrorException { + JsonObject json = new JsonObject(); + json.addProperty("userid", userid); + json.addProperty("external_userid", externalUserid); + + if (ArrayUtils.isNotEmpty(addTag)) { + json.add("add_tag", new Gson().toJsonTree(addTag).getAsJsonArray()); + } + if (ArrayUtils.isNotEmpty(removeTag)) { + json.add("remove_tag", new Gson().toJsonTree(removeTag).getAsJsonArray()); + } + + final String url = this.mainService.getWxCpConfigStorage().getApiUrl(MARK_TAG); + return WxCpBaseResp.fromJson(this.mainService.post(url, json.toString())); + } + + @Override + public WxCpAddMomentResult addMomentTask(WxCpAddMomentTask task) throws WxErrorException { + final String url = this.mainService.getWxCpConfigStorage().getApiUrl(ADD_MOMENT_TASK); + return WxCpAddMomentResult.fromJson(this.mainService.post(url, task.toJson())); + } + + @Override + public WxCpGetMomentTaskResult getMomentTaskResult(String jobId) throws WxErrorException { + String params = "&jobid=" + jobId; + final String url = this.mainService.getWxCpConfigStorage().getApiUrl(GET_MOMENT_TASK_RESULT); + return WxCpGetMomentTaskResult.fromJson(this.mainService.get(url, params)); + } + + @Override + public WxCpBaseResp cancelMomentTask(String momentId) throws WxErrorException { + final String url = this.mainService.getWxCpConfigStorage().getApiUrl(CANCEL_MOMENT_TASK); + JsonObject json = new JsonObject(); + json.addProperty("moment_id", momentId); + return WxCpBaseResp.fromJson(this.mainService.post(url, json.toString())); + } + + @Override + public WxCpGetMomentList getMomentList(Long startTime, Long endTime, String creator, Integer filterType, + String cursor, Integer limit) throws WxErrorException { + JsonObject json = new JsonObject(); + json.addProperty("start_time", startTime); + json.addProperty("end_time", endTime); + if (!StringUtils.isEmpty(creator)) { + json.addProperty("creator", creator); + } + if (filterType != null) { + json.addProperty("filter_type", filterType); + } + if (!StringUtils.isEmpty(cursor)) { + json.addProperty("cursor", cursor); + } + if (limit != null) { + json.addProperty("limit", limit); + } + final String url = this.mainService.getWxCpConfigStorage().getApiUrl(GET_MOMENT_LIST); + return WxCpGetMomentList.fromJson(this.mainService.post(url, json.toString())); + } + + @Override + public WxCpGetMomentTask getMomentTask(String momentId, String cursor, Integer limit) + throws WxErrorException { + JsonObject json = new JsonObject(); + json.addProperty("moment_id", momentId); + if (!StringUtils.isEmpty(cursor)) { + json.addProperty("cursor", cursor); + } + if (limit != null) { + json.addProperty("limit", limit); + } + final String url = this.mainService.getWxCpConfigStorage().getApiUrl(GET_MOMENT_TASK); + return WxCpGetMomentTask.fromJson(this.mainService.post(url, json.toString())); + } + + @Override + public WxCpGetMomentCustomerList getMomentCustomerList(String momentId, String userId, + String cursor, Integer limit) throws WxErrorException { + JsonObject json = new JsonObject(); + json.addProperty("moment_id", momentId); + json.addProperty("userid", userId); + if (!StringUtils.isEmpty(cursor)) { + json.addProperty("cursor", cursor); + } + if (limit != null) { + json.addProperty("limit", limit); + } + final String url = this.mainService.getWxCpConfigStorage().getApiUrl(GET_MOMENT_CUSTOMER_LIST); + return WxCpGetMomentCustomerList.fromJson(this.mainService.post(url, json.toString())); + } + + @Override + public WxCpGetMomentSendResult getMomentSendResult(String momentId, String userId, + String cursor, Integer limit) throws WxErrorException { + JsonObject json = new JsonObject(); + json.addProperty("moment_id", momentId); + json.addProperty("userid", userId); + if (!StringUtils.isEmpty(cursor)) { + json.addProperty("cursor", cursor); + } + if (limit != null) { + json.addProperty("limit", limit); + } + final String url = this.mainService.getWxCpConfigStorage().getApiUrl(GET_MOMENT_SEND_RESULT); + return WxCpGetMomentSendResult.fromJson(this.mainService.post(url, json.toString())); + } + + @Override + public WxCpGetMomentComments getMomentComments(String momentId, String userId) + throws WxErrorException { + JsonObject json = new JsonObject(); + json.addProperty("moment_id", momentId); + json.addProperty("userid", userId); + final String url = this.mainService.getWxCpConfigStorage().getApiUrl(GET_MOMENT_COMMENTS); + return WxCpGetMomentComments.fromJson(this.mainService.post(url, json.toString())); + } + + @Override + public WxCpGroupMsgListResult getGroupMsgListV2(String chatType, Date startTime, Date endTime, + String creator, Integer filterType, Integer limit, String cursor) throws WxErrorException { + JsonObject json = new JsonObject(); + json.addProperty("chat_type", chatType); + json.addProperty("start_time", startTime.getTime() / 1000); + json.addProperty("end_time", endTime.getTime() / 1000); + json.addProperty("creator", creator); + json.addProperty("filter_type", filterType); + json.addProperty("limit", limit); + json.addProperty("cursor", cursor); + + final String url = this.mainService.getWxCpConfigStorage().getApiUrl(GET_GROUP_MSG_LIST_V2); + return WxCpGroupMsgListResult.fromJson(this.mainService.post(url, json.toString())); + } + + @Override + public WxCpGroupMsgSendResult getGroupMsgSendResult(String msgid, String userid, Integer limit, String cursor) throws WxErrorException { + JsonObject json = new JsonObject(); + json.addProperty("msgid", msgid); + json.addProperty("userid", userid); + json.addProperty("limit", limit); + json.addProperty("cursor", cursor); + + final String url = this.mainService.getWxCpConfigStorage().getApiUrl(GET_GROUP_MSG_SEND_RESULT); + return WxCpGroupMsgSendResult.fromJson(this.mainService.post(url, json.toString())); + } + + @Override + public WxCpGroupMsgResult getGroupMsgResult(String msgid, Integer limit, String cursor) throws WxErrorException { + JsonObject json = new JsonObject(); + json.addProperty("msgid", msgid); + json.addProperty("limit", limit); + json.addProperty("cursor", cursor); + + final String url = this.mainService.getWxCpConfigStorage().getApiUrl(GET_GROUP_MSG_RESULT); + return WxCpGroupMsgResult.fromJson(this.mainService.post(url, json.toString())); + } + + @Override + public WxCpGroupMsgTaskResult getGroupMsgTask(String msgid, Integer limit, String cursor) throws WxErrorException { + + final String url = this.mainService.getWxCpConfigStorage().getApiUrl(GET_GROUP_MSG_TASK); + return WxCpGroupMsgTaskResult.fromJson(this.mainService.post(url, + GsonHelper.buildJsonObject("msgid", msgid, + "limit", limit, + "cursor", cursor))); + } + + @Override + public String addGroupWelcomeTemplate(WxCpGroupWelcomeTemplateResult template) throws WxErrorException { + final String url = this.mainService.getWxCpConfigStorage().getApiUrl(GROUP_WELCOME_TEMPLATE_ADD); + final String responseContent = this.mainService.post(url, template.toJson()); + return GsonParser.parse(responseContent).get("template_id").getAsString(); + } + + @Override + public WxCpBaseResp editGroupWelcomeTemplate(WxCpGroupWelcomeTemplateResult template) throws WxErrorException { + final String url = this.mainService.getWxCpConfigStorage().getApiUrl(GROUP_WELCOME_TEMPLATE_EDIT); + return WxCpGroupWelcomeTemplateResult.fromJson(this.mainService.post(url, template.toJson())); + } + + @Override + public WxCpGroupWelcomeTemplateResult getGroupWelcomeTemplate(String templateId) throws WxErrorException { + JsonObject json = new JsonObject(); + json.addProperty("template_id", templateId); + final String url = this.mainService.getWxCpConfigStorage().getApiUrl(GROUP_WELCOME_TEMPLATE_GET); + return WxCpGroupWelcomeTemplateResult.fromJson(this.mainService.post(url, json.toString())); + } + + @Override + public WxCpBaseResp delGroupWelcomeTemplate(String templateId, String agentId) throws WxErrorException { + JsonObject json = new JsonObject(); + json.addProperty("template_id", templateId); + if (!StringUtils.isEmpty(agentId)) { + json.addProperty("agentid", agentId); + } + final String url = this.mainService.getWxCpConfigStorage().getApiUrl(GROUP_WELCOME_TEMPLATE_DEL); + return WxCpBaseResp.fromJson(this.mainService.post(url, json.toString())); + } + + @Override + public WxCpProductAlbumListResult getProductAlbumList(Integer limit, String cursor) throws WxErrorException { + JsonObject json = new JsonObject(); + json.addProperty("limit", limit); + json.addProperty("cursor", cursor); + final String url = this.mainService.getWxCpConfigStorage().getApiUrl(GET_PRODUCT_ALBUM_LIST); + return WxCpProductAlbumListResult.fromJson(this.mainService.post(url, json.toString())); + } + + @Override + public WxCpProductAlbumResult getProductAlbum(String productId) throws WxErrorException { + JsonObject json = new JsonObject(); + json.addProperty("product_id", productId); + final String url = this.mainService.getWxCpConfigStorage().getApiUrl(GET_PRODUCT_ALBUM); + return WxCpProductAlbumResult.fromJson(this.mainService.post(url, json.toString())); + } + + @Override + public WxMediaUploadResult uploadAttachment(String mediaType, String fileType, Integer attachmentType, + InputStream inputStream) throws WxErrorException, IOException { + return uploadAttachment(mediaType, attachmentType, FileUtils.createTmpFile(inputStream, + UUID.randomUUID().toString(), fileType)); + } + + @Override + public WxMediaUploadResult uploadAttachment(String mediaType, Integer attachmentType, File file) + throws WxErrorException { + String params = "?media_type=" + mediaType + "&attachment_type=" + attachmentType; + final String url = this.mainService.getWxCpConfigStorage().getApiUrl(UPLOAD_ATTACHMENT + params); + return this.mainService.execute(MediaUploadRequestExecutor.create( + this.mainService.getRequestHttp()), url, file); + } + + @Override + public String addInterceptRule(WxCpInterceptRuleAddRequest ruleAddRequest) throws WxErrorException { + String responseContent = this.mainService.post(this.mainService.getWxCpConfigStorage() + .getApiUrl(ADD_INTERCEPT_RULE), ruleAddRequest); + return GsonParser.parse(responseContent).get("rule_id").getAsString(); + } + + @Override + public void updateInterceptRule(WxCpInterceptRule interceptRule) throws WxErrorException { + this.mainService.post(this.mainService.getWxCpConfigStorage().getApiUrl(UPDATE_INTERCEPT_RULE), + interceptRule); + } + + @Override + public void delInterceptRule(String ruleId) throws WxErrorException { + this.mainService.post(this.mainService.getWxCpConfigStorage().getApiUrl(DEL_INTERCEPT_RULE), + GsonHelper.buildJsonObject("rule_id", ruleId)); + } + + @Override + public WxCpInterceptRuleList getInterceptRuleList() throws WxErrorException { + String url = this.mainService.getWxCpConfigStorage().getApiUrl(GET_INTERCEPT_RULE_LIST); + return WxCpInterceptRuleList.fromJson(this.mainService.get(url,null)); + } + + @Override + public WxCpInterceptRuleInfo getInterceptRuleDetail(String ruleId) throws WxErrorException { + String url = this.mainService.getWxCpConfigStorage().getApiUrl(GET_INTERCEPT_RULE); + String json = this.mainService.post(url, GsonHelper.buildJsonObject("rule_id", ruleId)); + return WxCpInterceptRuleInfo.fromJson(json); + } + + @Override + public String addProductAlbum(WxCpProductAlbumInfo wxCpProductAlbumInfo) throws WxErrorException { + String url = this.mainService.getWxCpConfigStorage().getApiUrl(ADD_PRODUCT_ALBUM); + String responseContent = this.mainService.post(url, wxCpProductAlbumInfo.toJson()); + return GsonParser.parse(responseContent).get("product_id").getAsString(); + } + + @Override + public void updateProductAlbum(WxCpProductAlbumInfo wxCpProductAlbumInfo) throws WxErrorException { + String url = this.mainService.getWxCpConfigStorage().getApiUrl(UPDATE_PRODUCT_ALBUM); + this.mainService.post(url, wxCpProductAlbumInfo.toJson()); + } + + @Override + public void deleteProductAlbum(String productId) throws WxErrorException { + JsonObject o = new JsonObject(); + o.addProperty("product_id", productId); + String url = this.mainService.getWxCpConfigStorage().getApiUrl(DELETE_PRODUCT_ALBUM); + this.mainService.post(url, o.toString()); + } + + @Override + public WxCpCustomerAcquisitionList customerAcquisitionLinkList(Integer limit, String cursor) throws WxErrorException { + JsonObject o = new JsonObject(); + o.addProperty("limit", limit); + o.addProperty("cursor", cursor); + + String url = this.mainService.getWxCpConfigStorage().getApiUrl(CUSTOMER_ACQUISITION_LINK_LIST); + return WxCpCustomerAcquisitionList.fromJson(this.mainService.post(url, o)); + } + + @Override + public WxCpCustomerAcquisitionInfo customerAcquisitionLinkGet(String linkId) throws WxErrorException { + JsonObject o = new JsonObject(); + o.addProperty("link_id", linkId); + + String url = this.mainService.getWxCpConfigStorage().getApiUrl(CUSTOMER_ACQUISITION_LINK_GET); + return WxCpCustomerAcquisitionInfo.fromJson(this.mainService.post(url, o)); + } + + @Override + public WxCpCustomerAcquisitionCreateResult customerAcquisitionLinkCreate(WxCpCustomerAcquisitionRequest wxCpCustomerAcquisitionRequest) throws WxErrorException { + String url = this.mainService.getWxCpConfigStorage().getApiUrl(CUSTOMER_ACQUISITION_LINK_CREATE); + return WxCpCustomerAcquisitionCreateResult.fromJson(this.mainService.post(url, wxCpCustomerAcquisitionRequest.toJson())); + } + + @Override + public WxCpBaseResp customerAcquisitionUpdate(WxCpCustomerAcquisitionRequest wxCpCustomerAcquisitionRequest) throws WxErrorException { + String url = this.mainService.getWxCpConfigStorage().getApiUrl(CUSTOMER_ACQUISITION_LINK_UPDATE); + return WxCpBaseResp.fromJson(this.mainService.post(url, wxCpCustomerAcquisitionRequest.toJson())); + } + + @Override + public WxCpBaseResp customerAcquisitionLinkDelete(String linkId) throws WxErrorException { + JsonObject o = new JsonObject(); + o.addProperty("link_id", linkId); + + String url = this.mainService.getWxCpConfigStorage().getApiUrl(CUSTOMER_ACQUISITION_LINK_DELETE); + return WxCpBaseResp.fromJson(this.mainService.post(url, o)); + } + + @Override + public WxCpCustomerAcquisitionCustomerList customerAcquisitionCustomer(String linkId, Integer limit, String cursor) throws WxErrorException { + JsonObject o = new JsonObject(); + o.addProperty("link_id", linkId); + o.addProperty("limit", limit); + o.addProperty("cursor", cursor); + + String url = this.mainService.getWxCpConfigStorage().getApiUrl(CUSTOMER_ACQUISITION_CUSTOMER); + return WxCpCustomerAcquisitionCustomerList.fromJson(this.mainService.post(url, o)); + } + + @Override + public WxCpCustomerAcquisitionQuota customerAcquisitionQuota() throws WxErrorException { + String url = this.mainService.getWxCpConfigStorage().getApiUrl(CUSTOMER_ACQUISITION_QUOTA); + return WxCpCustomerAcquisitionQuota.fromJson(this.mainService.get(url, null)); + } + + @Override + public WxCpCustomerAcquisitionStatistic customerAcquisitionStatistic(String linkId, @NonNull Date startTime, + @NonNull Date endTime) throws WxErrorException { + long endTimestamp = endTime.getTime() / 1000L; + long startTimestamp = startTime.getTime() / 1000L; + + JsonObject o = new JsonObject(); + o.addProperty("link_id", linkId); + o.addProperty("start_time", startTimestamp); + o.addProperty("end_time", endTimestamp); + + String url = this.mainService.getWxCpConfigStorage().getApiUrl(CUSTOMER_ACQUISITION_STATISTIC); + return WxCpCustomerAcquisitionStatistic.fromJson(this.mainService.post(url, o)); + } + + + @Override + public WxCpGroupJoinWayResult addJoinWay(WxCpGroupJoinWayInfo wxCpGroupJoinWayInfo) throws WxErrorException { + if (wxCpGroupJoinWayInfo.getJoinWay().getChatIdList() != null && wxCpGroupJoinWayInfo.getJoinWay().getChatIdList().size() > 5) { + throw new WxRuntimeException("使用该配置的客户群ID列表,支持5个"); + } + final String url = this.mainService.getWxCpConfigStorage().getApiUrl(ADD_JOIN_WAY); + + return WxCpGroupJoinWayResult.fromJson(this.mainService.post(url, wxCpGroupJoinWayInfo.getJoinWay().toJson())); + } + + @Override + public WxCpBaseResp updateJoinWay(WxCpGroupJoinWayInfo wxCpGroupJoinWayInfo) throws WxErrorException { + if (wxCpGroupJoinWayInfo.getJoinWay().getChatIdList() != null && wxCpGroupJoinWayInfo.getJoinWay().getChatIdList().size() > 5) { + throw new WxRuntimeException("使用该配置的客户群ID列表,支持5个"); + } + final String url = this.mainService.getWxCpConfigStorage().getApiUrl(UPDATE_JOIN_WAY); + return WxCpBaseResp.fromJson(this.mainService.post(url, wxCpGroupJoinWayInfo.getJoinWay().toJson())); + } + + @Override + public WxCpGroupJoinWayInfo getJoinWay(String configId) throws WxErrorException { + JsonObject json = new JsonObject(); + json.addProperty("config_id", configId); + final String url = this.mainService.getWxCpConfigStorage().getApiUrl(GET_JOIN_WAY); + + return WxCpGroupJoinWayInfo.fromJson(this.mainService.post(url, json)); + } + + @Override + public WxCpBaseResp delJoinWay(String configId) throws WxErrorException { + JsonObject json = new JsonObject(); + json.addProperty("config_id", configId); + final String url = this.mainService.getWxCpConfigStorage().getApiUrl(DEL_JOIN_WAY); + return WxCpBaseResp.fromJson(this.mainService.post(url, json)); } } 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 new file mode 100644 index 0000000000..8373c6c8ee --- /dev/null +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpGroupRobotServiceImpl.java @@ -0,0 +1,121 @@ +package me.chanjar.weixin.cp.api.impl; + +import lombok.RequiredArgsConstructor; +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.cp.api.WxCpGroupRobotService; +import me.chanjar.weixin.cp.api.WxCpService; +import me.chanjar.weixin.cp.bean.article.NewArticle; +import me.chanjar.weixin.cp.bean.message.WxCpGroupRobotMessage; +import me.chanjar.weixin.cp.config.WxCpConfigStorage; +import me.chanjar.weixin.cp.constant.WxCpApiPathConsts; +import org.apache.commons.lang3.StringUtils; + +import java.util.List; + +import static me.chanjar.weixin.cp.constant.WxCpConsts.GroupRobotMsgType; + +/** + * 企业微信群机器人消息发送api 实现 + * + * @author yr created on 2020-08-20 + */ +@RequiredArgsConstructor +public class WxCpGroupRobotServiceImpl implements WxCpGroupRobotService { + private final WxCpService cpService; + + private String getWebhookUrl() throws WxErrorException { + WxCpConfigStorage wxCpConfigStorage = this.cpService.getWxCpConfigStorage(); + final String webhookKey = wxCpConfigStorage.getWebhookKey(); + if (StringUtils.isEmpty(webhookKey)) { + throw new WxErrorException("请先设置WebhookKey"); + } + return wxCpConfigStorage.getApiUrl(WxCpApiPathConsts.WEBHOOK_SEND) + webhookKey; + } + + @Override + public void sendText(String content, List mentionedList, List mobileList) throws WxErrorException { + this.sendText(this.getWebhookUrl(), content, mentionedList, mobileList); + } + + @Override + 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); + } + + @Override + public void sendNews(List articleList) throws WxErrorException { + this.sendNews(this.getWebhookUrl(), articleList); + } + + @Override + public void sendText(String webhookUrl, String content, List mentionedList, List mobileList) throws WxErrorException { + this.cpService.postWithoutToken(webhookUrl, new WxCpGroupRobotMessage() + .setMsgType(GroupRobotMsgType.TEXT) + .setContent(content) + .setMentionedList(mentionedList) + .setMentionedMobileList(mobileList) + .toJson()); + } + + @Override + public void sendMarkdown(String webhookUrl, String content) throws WxErrorException { + this.cpService.postWithoutToken(webhookUrl, new WxCpGroupRobotMessage() + .setMsgType(GroupRobotMsgType.MARKDOWN) + .setContent(content) + .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() + .setMsgType(GroupRobotMsgType.IMAGE) + .setBase64(base64) + .setMd5(md5).toJson()); + } + + @Override + public void sendNews(String webhookUrl, List articleList) throws WxErrorException { + this.cpService.postWithoutToken(webhookUrl, new WxCpGroupRobotMessage() + .setMsgType(GroupRobotMsgType.NEWS) + .setArticles(articleList).toJson()); + } + + @Override + public void sendFile(String webhookUrl, String mediaId) throws WxErrorException { + this.cpService.postWithoutToken(webhookUrl, new WxCpGroupRobotMessage() + .setMsgType(GroupRobotMsgType.FILE) + .setMediaId(mediaId).toJson()); + } + + + @Override + public void sendVoice(String webhookUrl, String mediaId) throws WxErrorException { + this.cpService.postWithoutToken(webhookUrl, new WxCpGroupRobotMessage() + .setMsgType(GroupRobotMsgType.VOICE) + .setMediaId(mediaId).toJson()); + } + + @Override + public void sendTemplateCardMessage(String webhookUrl, WxCpGroupRobotMessage wxCpGroupRobotMessage) throws WxErrorException { + this.cpService.postWithoutToken(webhookUrl, wxCpGroupRobotMessage.toJson()); + } + +} 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..8a12fa4ff4 --- /dev/null +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpIntelligentRobotServiceImpl.java @@ -0,0 +1,70 @@ +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()); + } + + @Override + public WxCpIntelligentRobotSendMessageResponse sendMessage(WxCpIntelligentRobotSendMessageRequest request) throws WxErrorException { + String responseText = this.cpService.post(SEND_MESSAGE, request.toJson()); + return WxCpIntelligentRobotSendMessageResponse.fromJson(responseText); + } + +} \ 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 new file mode 100644 index 0000000000..be4f2a5850 --- /dev/null +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpKfServiceImpl.java @@ -0,0 +1,320 @@ +package me.chanjar.weixin.cp.api.impl; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; +import lombok.RequiredArgsConstructor; +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.cp.api.WxCpKfService; +import me.chanjar.weixin.cp.api.WxCpService; +import me.chanjar.weixin.cp.bean.WxCpBaseResp; +import me.chanjar.weixin.cp.bean.kf.*; +import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder; + +import java.util.List; + +import static me.chanjar.weixin.cp.constant.WxCpApiPathConsts.Kf.*; + +/** + * 微信客服接口-服务实现 + * + * @author Fu created on 2022/1/19 19:41 + */ +@RequiredArgsConstructor +public class WxCpKfServiceImpl implements WxCpKfService { + private final WxCpService cpService; + private static final Gson GSON = new GsonBuilder().create(); + + @Override + public WxCpKfAccountAddResp addAccount(WxCpKfAccountAdd add) throws WxErrorException { + String url = cpService.getWxCpConfigStorage().getApiUrl(ACCOUNT_ADD); + String responseContent = cpService.post(url, WxCpGsonBuilder.create().toJson(add)); + return WxCpKfAccountAddResp.fromJson(responseContent); + } + + @Override + public WxCpBaseResp updAccount(WxCpKfAccountUpd upd) throws WxErrorException { + String url = cpService.getWxCpConfigStorage().getApiUrl(ACCOUNT_UPD); + String responseContent = cpService.post(url, WxCpGsonBuilder.create().toJson(upd)); + return WxCpBaseResp.fromJson(responseContent); + } + + @Override + public WxCpBaseResp delAccount(WxCpKfAccountDel del) throws WxErrorException { + String url = cpService.getWxCpConfigStorage().getApiUrl(ACCOUNT_DEL); + String responseContent = cpService.post(url, WxCpGsonBuilder.create().toJson(del)); + return WxCpBaseResp.fromJson(responseContent); + } + + @Override + public WxCpKfAccountListResp listAccount(Integer offset, Integer limit) throws WxErrorException { + String url = cpService.getWxCpConfigStorage().getApiUrl(ACCOUNT_LIST); + JsonObject json = new JsonObject(); + if (offset != null) { + json.addProperty("offset", offset); + } + if (limit != null) { + json.addProperty("limit", limit); + } + String responseContent = cpService.post(url, json.toString()); + return WxCpKfAccountListResp.fromJson(responseContent); + } + + @Override + public WxCpKfAccountLinkResp getAccountLink(WxCpKfAccountLink link) throws WxErrorException { + String url = cpService.getWxCpConfigStorage().getApiUrl(ADD_CONTACT_WAY); + String responseContent = cpService.post(url, WxCpGsonBuilder.create().toJson(link)); + return WxCpKfAccountLinkResp.fromJson(responseContent); + } + + @Override + public WxCpKfServicerOpResp addServicer(String openKfid, List userIdList) throws WxErrorException { + 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, null, SERVICER_DEL); + } + + @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); + 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); + } + + @Override + public WxCpKfServicerListResp listServicer(String openKfid) throws WxErrorException { + String url = cpService.getWxCpConfigStorage().getApiUrl(SERVICER_LIST + openKfid); + String responseContent = cpService.get(url, null); + return WxCpKfServicerListResp.fromJson(responseContent); + } + + @Override + public WxCpKfServiceStateResp getServiceState(String openKfid, String externalUserId) + throws WxErrorException { + String url = cpService.getWxCpConfigStorage().getApiUrl(SERVICE_STATE_GET); + + JsonObject json = new JsonObject(); + json.addProperty("open_kfid", openKfid); + json.addProperty("external_userid", externalUserId); + + String responseContent = cpService.post(url, json.toString()); + return WxCpKfServiceStateResp.fromJson(responseContent); + } + + @Override + public WxCpKfServiceStateTransResp transServiceState(String openKfid, String externalUserId, + Integer serviceState, String servicerUserId) throws WxErrorException { + String url = cpService.getWxCpConfigStorage().getApiUrl(SERVICE_STATE_TRANS); + + JsonObject json = new JsonObject(); + json.addProperty("open_kfid", openKfid); + json.addProperty("external_userid", externalUserId); + json.addProperty("service_state", serviceState); + json.addProperty("servicer_userid", servicerUserId); + + String responseContent = cpService.post(url, json.toString()); + return WxCpKfServiceStateTransResp.fromJson(responseContent); + } + + @Override + public WxCpKfMsgListResp syncMsg(String cursor, String token, Integer limit, Integer voiceFormat) + throws WxErrorException { + String url = cpService.getWxCpConfigStorage().getApiUrl(SYNC_MSG); + + JsonObject json = new JsonObject(); + if (cursor != null) { + json.addProperty("cursor", cursor); + } + if (token != null) { + json.addProperty("token", token); + } + if (limit != null) { + json.addProperty("limit", limit); + } + if (voiceFormat != null) { + json.addProperty("voice_format", voiceFormat); + } + + String responseContent = cpService.post(url, json); + return WxCpKfMsgListResp.fromJson(responseContent); + } + + @Override + public WxCpKfMsgListResp syncMsg(String cursor, String token, Integer limit, Integer voiceFormat, String openKfId) throws WxErrorException { + String url = cpService.getWxCpConfigStorage().getApiUrl(SYNC_MSG); + + JsonObject json = new JsonObject(); + if (cursor!=null) { + json.addProperty("cursor", cursor); + } + if (token!=null) { + json.addProperty("token", token); + } + if (limit!=null) { + json.addProperty("limit", limit); + } + if (voiceFormat!=null) { + json.addProperty("voice_format", voiceFormat); + } + if (openKfId != null) { + json.addProperty("open_kfid", openKfId); + } + + String responseContent = cpService.post(url, json); + return WxCpKfMsgListResp.fromJson(responseContent); + } + + @Override + public WxCpKfMsgSendResp sendMsg(WxCpKfMsgSendRequest request) throws WxErrorException { + String url = cpService.getWxCpConfigStorage().getApiUrl(SEND_MSG); + + String responseContent = cpService.post(url, GSON.toJson(request)); + + return WxCpKfMsgSendResp.fromJson(responseContent); + } + + @Override + public WxCpKfMsgSendResp sendMsgOnEvent(WxCpKfMsgSendRequest request) throws WxErrorException { + String url = cpService.getWxCpConfigStorage().getApiUrl(SEND_MSG_ON_EVENT); + + String responseContent = cpService.post(url, GSON.toJson(request)); + + return WxCpKfMsgSendResp.fromJson(responseContent); + } + + @Override + public WxCpKfCustomerBatchGetResp customerBatchGet(List externalUserIdList) + throws WxErrorException { + String url = cpService.getWxCpConfigStorage().getApiUrl(CUSTOMER_BATCH_GET); + + JsonArray array = new JsonArray(); + + externalUserIdList.forEach(array::add); + JsonObject json = new JsonObject(); + json.add("external_userid_list", array); + String responseContent = cpService.post(url, json.toString()); + return WxCpKfCustomerBatchGetResp.fromJson(responseContent); + } + + @Override + public WxCpKfServiceUpgradeConfigResp getUpgradeServiceConfig() throws WxErrorException { + String url = cpService.getWxCpConfigStorage().getApiUrl(CUSTOMER_GET_UPGRADE_SERVICE_CONFIG); + + String response = cpService.get(url, null); + return WxCpKfServiceUpgradeConfigResp.fromJson(response); + } + + @Override + public WxCpBaseResp upgradeMemberService(String openKfid, String externalUserId, + String userid, String wording) throws WxErrorException { + String url = cpService.getWxCpConfigStorage().getApiUrl(CUSTOMER_UPGRADE_SERVICE); + + JsonObject json = new JsonObject(); + json.addProperty("open_kfid", openKfid); + json.addProperty("external_userid", externalUserId); + json.addProperty("type", 1); + + JsonObject memberJson = new JsonObject(); + memberJson.addProperty("userid", userid); + memberJson.addProperty("wording", wording); + json.add("member", memberJson); + + String response = cpService.post(url, json); + return WxCpBaseResp.fromJson(response); + } + + @Override + public WxCpBaseResp upgradeGroupchatService(String openKfid, String externalUserId, + String chatId, String wording) throws WxErrorException { + String url = cpService.getWxCpConfigStorage().getApiUrl(CUSTOMER_UPGRADE_SERVICE); + + JsonObject json = new JsonObject(); + json.addProperty("open_kfid", openKfid); + json.addProperty("external_userid", externalUserId); + json.addProperty("type", 2); + + JsonObject groupchatJson = new JsonObject(); + groupchatJson.addProperty("chat_id", chatId); + groupchatJson.addProperty("wording", wording); + json.add("groupchat", groupchatJson); + + String response = cpService.post(url, json); + return WxCpBaseResp.fromJson(response); + } + + @Override + public WxCpBaseResp cancelUpgradeService(String openKfid, String externalUserId) + throws WxErrorException { + String url = cpService.getWxCpConfigStorage().getApiUrl(CUSTOMER_CANCEL_UPGRADE_SERVICE); + + JsonObject json = new JsonObject(); + json.addProperty("open_kfid", openKfid); + json.addProperty("external_userid", externalUserId); + String response = cpService.post(url, json); + return WxCpBaseResp.fromJson(response); + } + + @Override + public WxCpKfGetCorpStatisticResp getCorpStatistic(WxCpKfGetCorpStatisticRequest request) throws WxErrorException { + String url = cpService.getWxCpConfigStorage().getApiUrl(GET_CORP_STATISTIC); + String responseContent = cpService.post(url, GSON.toJson(request)); + return WxCpKfGetCorpStatisticResp.fromJson(responseContent); + } + + @Override + public WxCpKfGetServicerStatisticResp getServicerStatistic(WxCpKfGetServicerStatisticRequest request) throws WxErrorException { + String url = cpService.getWxCpConfigStorage().getApiUrl(GET_SERVICER_STATISTIC); + String responseContent = cpService.post(url, GSON.toJson(request)); + return WxCpKfGetServicerStatisticResp.fromJson(responseContent); + } + +} diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpLivingServiceImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpLivingServiceImpl.java new file mode 100644 index 0000000000..b3d9e9a36e --- /dev/null +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpLivingServiceImpl.java @@ -0,0 +1,119 @@ +package me.chanjar.weixin.cp.api.impl; + +import com.google.gson.JsonObject; +import com.google.gson.reflect.TypeToken; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.common.util.json.GsonHelper; +import me.chanjar.weixin.common.util.json.GsonParser; +import me.chanjar.weixin.cp.api.WxCpLivingService; +import me.chanjar.weixin.cp.api.WxCpService; +import me.chanjar.weixin.cp.bean.living.*; +import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder; +import org.apache.commons.lang3.StringUtils; + +import static me.chanjar.weixin.cp.constant.WxCpApiPathConsts.Living.*; + +/** + * 企业微信直播接口实现类. + * https://developer.work.weixin.qq.com/document/path/93633 + * + * @author Wang_Wong created on 2021-12-21 + */ +@Slf4j +@RequiredArgsConstructor +public class WxCpLivingServiceImpl implements WxCpLivingService { + private final WxCpService cpService; + + @Override + public String getLivingCode(String openId, String livingId) throws WxErrorException { + final String apiUrl = this.cpService.getWxCpConfigStorage().getApiUrl(GET_LIVING_CODE); + JsonObject jsonObject = new JsonObject(); + jsonObject.addProperty("openid", openId); + jsonObject.addProperty("livingid", livingId); + String responseContent = this.cpService.post(apiUrl, jsonObject.toString()); + return GsonHelper.getString(GsonParser.parse(responseContent), "living_code"); + } + + @Override + public WxCpLivingInfo getLivingInfo(String livingId) throws WxErrorException { + String apiUrl = this.cpService.getWxCpConfigStorage().getApiUrl(GET_LIVING_INFO) + livingId; + String responseContent = this.cpService.get(apiUrl, null); + return WxCpGsonBuilder.create() + .fromJson(GsonParser.parse(responseContent).get("living_info"), + new TypeToken() { + }.getType() + ); + } + + @Override + public WxCpWatchStat getWatchStat(String livingId, String nextKey) throws WxErrorException { + String apiUrl = this.cpService.getWxCpConfigStorage().getApiUrl(GET_WATCH_STAT); + JsonObject jsonObject = new JsonObject(); + if (StringUtils.isNotBlank(nextKey)) { + jsonObject.addProperty("next_key", nextKey); + } + jsonObject.addProperty("livingid", livingId); + String responseContent = this.cpService.post(apiUrl, jsonObject.toString()); + return WxCpWatchStat.fromJson(responseContent); + } + + @Override + public WxCpLivingResult.LivingIdResult getUserAllLivingId(String userId, String cursor, Integer limit) throws WxErrorException { + String apiUrl = this.cpService.getWxCpConfigStorage().getApiUrl(GET_USER_ALL_LIVINGID); + JsonObject jsonObject = new JsonObject(); + if (cursor != null) { + jsonObject.addProperty("cursor", cursor); + } + if (limit != null) { + jsonObject.addProperty("limit", limit); + } + jsonObject.addProperty("userid", userId); + String responseContent = this.cpService.post(apiUrl, jsonObject.toString()); + return WxCpLivingResult.LivingIdResult.fromJson(responseContent); + } + + @Override + public WxCpLivingShareInfo getLivingShareInfo(String wwShareCode) throws WxErrorException { + String apiUrl = this.cpService.getWxCpConfigStorage().getApiUrl(GET_LIVING_SHARE_INFO); + JsonObject jsonObject = new JsonObject(); + jsonObject.addProperty("ww_share_code", wwShareCode); + String responseContent = this.cpService.post(apiUrl, jsonObject.toString()); + return WxCpLivingShareInfo.fromJson(responseContent); + } + + @Override + public String livingCreate(WxCpLivingCreateRequest request) throws WxErrorException { + String apiUrl = this.cpService.getWxCpConfigStorage().getApiUrl(CREATE); + String responseContent = this.cpService.post(apiUrl, request.toJson()); + return GsonHelper.getString(GsonParser.parse(responseContent), "livingid"); + } + + @Override + public WxCpLivingResult livingModify(WxCpLivingModifyRequest request) throws WxErrorException { + String apiUrl = this.cpService.getWxCpConfigStorage().getApiUrl(MODIFY); + String responseContent = this.cpService.post(apiUrl, request.toJson()); + return WxCpLivingResult.fromJson(responseContent); + } + + @Override + public WxCpLivingResult livingCancel(@NonNull String livingId) throws WxErrorException { + String apiUrl = this.cpService.getWxCpConfigStorage().getApiUrl(CANCEL); + JsonObject jsonObject = new JsonObject(); + jsonObject.addProperty("livingid", livingId); + String responseContent = this.cpService.post(apiUrl, jsonObject.toString()); + return WxCpLivingResult.fromJson(responseContent); + } + + @Override + public WxCpLivingResult deleteReplayData(@NonNull String livingId) throws WxErrorException { + String apiUrl = this.cpService.getWxCpConfigStorage().getApiUrl(DELETE_REPLAY_DATA); + JsonObject jsonObject = new JsonObject(); + jsonObject.addProperty("livingid", livingId); + String responseContent = this.cpService.post(apiUrl, jsonObject.toString()); + return WxCpLivingResult.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 55579ff051..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,22 +1,37 @@ 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; +import me.chanjar.weixin.common.error.WxRuntimeException; import me.chanjar.weixin.common.util.fs.FileUtils; import me.chanjar.weixin.common.util.http.BaseMediaDownloadRequestExecutor; +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.http.RequestExecutor; +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.constant.WxCpApiPathConsts; +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; import java.io.InputStream; +import java.net.HttpURLConnection; +import java.net.URL; +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; /** *

@@ -36,6 +51,52 @@ public WxMediaUploadResult upload(String mediaType, String fileType, InputStream
     return this.upload(mediaType, FileUtils.createTmpFile(inputStream, UUID.randomUUID().toString(), fileType));
   }
 
+  @Override
+  public WxMediaUploadResult upload(String mediaType, String filename, String url) throws WxErrorException,
+    IOException {
+    HttpURLConnection conn = null;
+    InputStream inputStream = null;
+    try {
+      URL remote = new URL(url);
+      conn = (HttpURLConnection) remote.openConnection();
+      //设置超时间为3秒
+      conn.setConnectTimeout(60 * 1000);
+      //防止屏蔽程序抓取而返回403错误
+      conn.setRequestProperty("User-Agent", "Mozilla/4.0 (compatible; MSIE 5.0; Windows NT; DigExt)");
+      inputStream = conn.getInputStream();
+      return this.mainService.execute(MediaInputStreamUploadRequestExecutor.create(this.mainService.getRequestHttp())
+        , this.mainService.getWxCpConfigStorage().getApiUrl(MEDIA_UPLOAD + mediaType),
+        new InputStreamData(inputStream, filename));
+    } finally {
+      IOUtils.closeQuietly(inputStream);
+      if (conn != null) {
+        conn.disconnect();
+      }
+    }
+  }
+
+  @Override
+  public WxMediaUploadResult upload(String mediaType, File file, String filename) throws WxErrorException {
+    if(!file.exists()){
+      throw new WxRuntimeException("文件[" + file.getAbsolutePath() + "]不存在");
+    }
+    try (InputStream inputStream = Files.newInputStream(file.toPath())) {
+      return this.mainService.execute(MediaInputStreamUploadRequestExecutor.create(this.mainService.getRequestHttp())
+        , this.mainService.getWxCpConfigStorage().getApiUrl(MEDIA_UPLOAD + mediaType),
+        new InputStreamData(inputStream, filename));
+    } catch (IOException e) {
+      throw new WxRuntimeException(e);
+    }
+  }
+
+  @Override
+  public WxMediaUploadResult upload(String mediaType, InputStream inputStream, String filename) throws WxErrorException{
+    return this.mainService.execute(MediaInputStreamUploadRequestExecutor.create(this.mainService.getRequestHttp())
+      , this.mainService.getWxCpConfigStorage().getApiUrl(MEDIA_UPLOAD + mediaType),
+      new InputStreamData(inputStream, filename));
+  }
+
+
   @Override
   public WxMediaUploadResult upload(String mediaType, File file) throws WxErrorException {
     return this.mainService.execute(MediaUploadRequestExecutor.create(this.mainService.getRequestHttp()),
@@ -64,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
new file mode 100644
index 0000000000..341bc97eab
--- /dev/null
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpMeetingServiceImpl.java
@@ -0,0 +1,77 @@
+package me.chanjar.weixin.cp.api.impl;
+
+import com.google.common.collect.ImmutableMap;
+import lombok.RequiredArgsConstructor;
+import me.chanjar.weixin.common.error.WxErrorException;
+import me.chanjar.weixin.cp.api.WxCpMeetingService;
+import me.chanjar.weixin.cp.api.WxCpService;
+import me.chanjar.weixin.cp.bean.oa.meeting.WxCpMeeting;
+import me.chanjar.weixin.cp.bean.oa.meeting.WxCpMeetingUpdateResult;
+import me.chanjar.weixin.cp.bean.oa.meeting.WxCpUserMeetingIdResult;
+import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import static me.chanjar.weixin.cp.constant.WxCpApiPathConsts.Oa.*;
+
+/**
+ * 企业微信日程接口.
+ * 企业和开发者通过会议接口可以便捷地预定及管理会议,用于小组周会、部门例会等场景。
+ * 调用接口的应用自动成为会议创建者,也可指定成员作为会议管理员辅助管理。
+ * 官方文档:https://developer.work.weixin.qq.com/document/path/93626
+ *
+ * @author wangmeng3486 created on  2023-01-31
+ */
+@RequiredArgsConstructor
+public class WxCpMeetingServiceImpl implements WxCpMeetingService {
+  private final WxCpService cpService;
+
+  @Override
+  public String create(WxCpMeeting meeting) throws WxErrorException {
+    return this.cpService.post(this.cpService.getWxCpConfigStorage().getApiUrl(MEETING_ADD),
+      WxCpGsonBuilder.create().toJson(meeting));
+  }
+
+  @Override
+  public WxCpMeetingUpdateResult update(WxCpMeeting meeting) throws WxErrorException {
+    final String response = this.cpService.post(this.cpService.getWxCpConfigStorage().getApiUrl(MEETING_UPDATE),
+      WxCpGsonBuilder.create().toJson(meeting));
+    return WxCpGsonBuilder.create().fromJson(response, WxCpMeetingUpdateResult.class);
+  }
+
+  @Override
+  public void cancel(String meetingId) throws WxErrorException {
+    this.cpService.post(this.cpService.getWxCpConfigStorage().getApiUrl(MEETING_CANCEL),
+      WxCpGsonBuilder.create().toJson(ImmutableMap.of("meetingid", meetingId)));
+  }
+
+  @Override
+  public WxCpMeeting getDetail(String meetingId) throws WxErrorException {
+    final String response = this.cpService.post(this.cpService.getWxCpConfigStorage().getApiUrl(MEETING_DETAIL),
+      WxCpGsonBuilder.create().toJson(ImmutableMap.of("meetingid", meetingId)));
+    return WxCpGsonBuilder.create().fromJson(response, WxCpMeeting.class);
+  }
+
+  @Override
+  public WxCpUserMeetingIdResult getUserMeetingIds(String userId, String cursor, Integer limit,
+                                                   Long beginTime, Long endTime) throws WxErrorException {
+    final Map param = new HashMap<>(3);
+    param.put("userid", userId);
+    if (cursor != null) {
+      param.put("cursor", cursor);
+    }
+    if (limit != null) {
+      param.put("limit", limit);
+    }
+    if (beginTime != null) {
+      param.put("begin_time", beginTime);
+    }
+    if (endTime != null) {
+      param.put("end_time", endTime);
+    }
+    final String response = this.cpService.post(this.cpService.getWxCpConfigStorage().getApiUrl(GET_USER_MEETING_ID),
+      WxCpGsonBuilder.create().toJson(param));
+    return WxCpGsonBuilder.create().fromJson(response, WxCpUserMeetingIdResult.class);
+  }
+}
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpMenuServiceImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpMenuServiceImpl.java
index 85abe71f45..d008e77083 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpMenuServiceImpl.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpMenuServiceImpl.java
@@ -5,7 +5,6 @@
 import me.chanjar.weixin.common.error.WxErrorException;
 import me.chanjar.weixin.cp.api.WxCpMenuService;
 import me.chanjar.weixin.cp.api.WxCpService;
-import me.chanjar.weixin.cp.constant.WxCpApiPathConsts;
 import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder;
 
 import static me.chanjar.weixin.cp.constant.WxCpApiPathConsts.Menu.*;
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpMessageServiceImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpMessageServiceImpl.java
new file mode 100644
index 0000000000..6daea8ef2f
--- /dev/null
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpMessageServiceImpl.java
@@ -0,0 +1,68 @@
+package me.chanjar.weixin.cp.api.impl;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.gson.JsonObject;
+import lombok.RequiredArgsConstructor;
+import me.chanjar.weixin.common.error.WxErrorException;
+import me.chanjar.weixin.cp.api.WxCpMessageService;
+import me.chanjar.weixin.cp.api.WxCpService;
+import me.chanjar.weixin.cp.bean.message.*;
+import me.chanjar.weixin.cp.constant.WxCpApiPathConsts.Message;
+import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder;
+
+/**
+ * 消息推送接口实现类.
+ *
+ * @author Binary Wang created on  2020-08-30
+ */
+@RequiredArgsConstructor
+public class WxCpMessageServiceImpl implements WxCpMessageService {
+  private final WxCpService cpService;
+
+  @Override
+  public WxCpMessageSendResult send(WxCpMessage message) throws WxErrorException {
+    Integer agentId = message.getAgentId();
+    if (null == agentId) {
+      message.setAgentId(this.cpService.getWxCpConfigStorage().getAgentId());
+    }
+
+    return WxCpMessageSendResult.fromJson(this.cpService.post(this.cpService.getWxCpConfigStorage()
+      .getApiUrl(Message.MESSAGE_SEND), message.toJson()));
+  }
+
+  @Override
+  public WxCpMessageSendStatistics getStatistics(int timeType) throws WxErrorException {
+    return WxCpMessageSendStatistics.fromJson(this.cpService.post(this.cpService.getWxCpConfigStorage().getApiUrl(Message.GET_STATISTICS),
+      WxCpGsonBuilder.create().toJson(ImmutableMap.of("time_type", timeType))));
+  }
+
+  @Override
+  public WxCpLinkedCorpMessageSendResult sendLinkedCorpMessage(WxCpLinkedCorpMessage message) throws WxErrorException {
+    Integer agentId = message.getAgentId();
+    if (null == agentId) {
+      message.setAgentId(this.cpService.getWxCpConfigStorage().getAgentId());
+    }
+
+    return WxCpLinkedCorpMessageSendResult.fromJson(this.cpService.post(this.cpService.getWxCpConfigStorage()
+      .getApiUrl(Message.LINKEDCORP_MESSAGE_SEND), message.toJson()));
+  }
+
+  @Override
+  public WxCpSchoolContactMessageSendResult sendSchoolContactMessage(WxCpSchoolContactMessage message) throws WxErrorException {
+    if (null == message.getAgentId()) {
+      message.setAgentId(this.cpService.getWxCpConfigStorage().getAgentId());
+    }
+
+    return WxCpSchoolContactMessageSendResult.fromJson(this.cpService.post(this.cpService.getWxCpConfigStorage()
+      .getApiUrl(Message.EXTERNAL_CONTACT_MESSAGE_SEND), message.toJson()));
+  }
+
+  @Override
+  public void recall(String msgId) throws WxErrorException {
+    JsonObject jsonObject = new JsonObject();
+    jsonObject.addProperty("msgid", msgId);
+    String apiUrl = this.cpService.getWxCpConfigStorage().getApiUrl(Message.MESSAGE_RECALL);
+    this.cpService.post(apiUrl, jsonObject.toString());
+  }
+
+}
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
new file mode 100644
index 0000000000..cdf559ad7a
--- /dev/null
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpMsgAuditServiceImpl.java
@@ -0,0 +1,283 @@
+package me.chanjar.weixin.cp.api.impl;
+
+import com.google.gson.JsonObject;
+import com.google.gson.reflect.TypeToken;
+import com.tencent.wework.Finance;
+import lombok.NonNull;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import me.chanjar.weixin.common.error.WxErrorException;
+import me.chanjar.weixin.common.util.json.GsonParser;
+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;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.function.Consumer;
+
+import static me.chanjar.weixin.cp.constant.WxCpApiPathConsts.MsgAudit.*;
+
+/**
+ * 会话内容存档接口实现类.
+ *
+ * @author Wang_Wong  created on  2022-01-17
+ */
+@Slf4j
+@RequiredArgsConstructor
+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 {
+    // 获取或初始化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文件的路径,不要配错了!!");
+    }
+
+    // 替换斜杠
+    String replacePath = configPath.replace("\\", "/");
+    // 获取最后一个斜杠的下标,用作分割路径
+    int lastIndex = replacePath.lastIndexOf("/") + 1;
+    // 获取完整路径的前缀路径
+    String prefixPath = replacePath.substring(0, lastIndex);
+    // 获取后缀的所有文件,目的遍历所有文件
+    String suffixFiles = replacePath.substring(lastIndex);
+
+    // 包含so文件
+    String[] libFiles = suffixFiles.split(",");
+    if (libFiles.length <= 0) {
+      throw new WxErrorException("请仔细配置会话存档文件路径!!");
+    }
+
+    List libList = Arrays.asList(libFiles);
+    // 判断windows系统会话存档sdk中dll的加载,因为会互相依赖所以是有顺序的,否则会导致无法加载sdk #2598
+    List osLib = new LinkedList<>();
+    List fileLib = new ArrayList<>();
+    libList.forEach(s -> {
+      if (s.contains("lib")) {
+        osLib.add(s);
+      } else {
+        fileLib.add(s);
+      }
+    });
+    osLib.addAll(fileLib);
+
+    Finance.loadingLibraries(osLib, prefixPath);
+    long sdk = Finance.NewSdk();
+    // 因为会话存档单独有个secret,优先使用会话存档的secret
+    String msgAuditSecret = configStorage.getMsgAuditSecret();
+    if (StringUtils.isEmpty(msgAuditSecret)) {
+      msgAuditSecret = configStorage.getCorpSecret();
+    }
+    long ret = Finance.Init(sdk, configStorage.getCorpId(), msgAuditSecret);
+    if (ret != 0) {
+      Finance.DestroySdk(sdk);
+      throw new WxErrorException("init sdk err ret " + ret);
+    }
+
+    // 缓存SDK
+    configStorage.updateMsgAuditSdk(sdk, SDK_EXPIRES_TIME);
+    log.debug("初始化会话存档SDK成功,sdk={}", sdk);
+
+    return sdk;
+  }
+
+  @Override
+  public WxCpChatModel getDecryptData(@NonNull long sdk, @NonNull WxCpChatDatas.WxCpChatData chatData,
+                                      @NonNull Integer pkcs1) throws Exception {
+    String plainText = this.decryptChatData(sdk, chatData, pkcs1);
+    return WxCpChatModel.fromJson(plainText);
+  }
+
+  /**
+   * Decrypt chat data string.
+   *
+   * @param sdk      the sdk
+   * @param chatData the chat data
+   * @param pkcs1    the pkcs 1
+   * @return the string
+   * @throws Exception the exception
+   */
+  public String decryptChatData(long sdk, WxCpChatDatas.WxCpChatData chatData, Integer pkcs1) throws Exception {
+    // 企业获取的会话内容,使用企业自行配置的消息加密公钥进行加密,企业可用自行保存的私钥解开会话内容数据。
+    // 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释放。
+    long msg = Finance.NewSlice();
+
+    // 解密会话存档内容
+    // 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);
+      throw new WxErrorException("msg err ret " + ret);
+    }
+
+    // 明文
+    String plainText = Finance.GetContentFromSlice(msg);
+    Finance.FreeSlice(msg);
+    return plainText;
+  }
+
+  @Override
+  public String getChatPlainText(@NonNull long sdk, WxCpChatDatas.@NonNull WxCpChatData chatData,
+                                 @NonNull Integer pkcs1) throws Exception {
+    return this.decryptChatData(sdk, chatData, pkcs1);
+  }
+
+  @Override
+  public void getMediaFile(@NonNull long sdk, @NonNull String sdkfileid, String proxy, String passwd,
+                           @NonNull long timeout, @NonNull String targetFilePath) throws WxErrorException {
+    /**
+     * 1、媒体文件每次拉取的最大size为512k,因此超过512k的文件需要分片拉取。
+     * 2、若该文件未拉取完整,sdk的IsMediaDataFinish接口会返回0,同时通过GetOutIndexBuf接口返回下次拉取需要传入GetMediaData的indexbuf。
+     * 3、indexbuf一般格式如右侧所示,”Range:bytes=524288-1048575“:表示这次拉取的是从524288到1048575的分片。单个文件首次拉取填写的indexbuf
+     * 为空字符串,拉取后续分片时直接填入上次返回的indexbuf即可。
+     */
+    File targetFile = new File(targetFilePath);
+    if (!targetFile.getParentFile().exists()) {
+      targetFile.getParentFile().mkdirs();
+    }
+    this.getMediaFile(sdk, sdkfileid, proxy, passwd, timeout, i -> {
+      try {
+        // 大于512k的文件会分片拉取,此处需要使用追加写,避免后面的分片覆盖之前的数据。
+        FileOutputStream outputStream = new FileOutputStream(targetFile, true);
+        outputStream.write(i);
+        outputStream.close();
+      } catch (Exception e) {
+        e.printStackTrace();
+      }
+    });
+  }
+
+  @Override
+  public void getMediaFile(@NonNull long sdk, @NonNull String sdkfileid, String proxy, String passwd, @NonNull long timeout, @NonNull Consumer action) throws WxErrorException {
+    /**
+     * 1、媒体文件每次拉取的最大size为512k,因此超过512k的文件需要分片拉取。
+     * 2、若该文件未拉取完整,sdk的IsMediaDataFinish接口会返回0,同时通过GetOutIndexBuf接口返回下次拉取需要传入GetMediaData的indexbuf。
+     * 3、indexbuf一般格式如右侧所示,”Range:bytes=524288-1048575“:表示这次拉取的是从524288到1048575的分片。单个文件首次拉取填写的indexbuf为空字符串,拉取后续分片时直接填入上次返回的indexbuf即可。
+     */
+    String indexbuf = "";
+    int ret, data_len = 0;
+    log.debug("正在分片拉取媒体文件 sdkFileId为{}", sdkfileid);
+    while (true) {
+      long mediaData = Finance.NewMediaData();
+      ret = Finance.GetMediaData(sdk, indexbuf, sdkfileid, proxy, passwd, timeout, mediaData);
+      if (ret != 0) {
+        Finance.FreeMediaData(mediaData);
+        throw new WxErrorException("getmediadata err ret " + ret);
+      }
+
+      data_len += Finance.GetDataLen(mediaData);
+      log.debug("正在分片拉取媒体文件 len:{}, data_len:{}, is_finish:{} \n", Finance.GetIndexLen(mediaData), data_len, Finance.IsMediaDataFinish(mediaData));
+
+      try {
+        // 大于512k的文件会分片拉取,此处需要使用追加写,避免后面的分片覆盖之前的数据。
+        action.accept(Finance.GetData(mediaData));
+      } catch (Exception e) {
+        e.printStackTrace();
+      }
+
+      if (Finance.IsMediaDataFinish(mediaData) == 1) {
+        // 已经拉取完成最后一个分片
+        Finance.FreeMediaData(mediaData);
+        break;
+      } else {
+        // 获取下次拉取需要使用的indexbuf
+        indexbuf = Finance.GetOutIndexBuf(mediaData);
+        Finance.FreeMediaData(mediaData);
+      }
+    }
+  }
+
+  @Override
+  public List getPermitUserList(Integer type) throws WxErrorException {
+    final String apiUrl = this.cpService.getWxCpConfigStorage().getApiUrl(GET_PERMIT_USER_LIST);
+    JsonObject jsonObject = new JsonObject();
+    if (type != null) {
+      jsonObject.addProperty("type", type);
+    }
+    String responseContent = this.cpService.post(apiUrl, jsonObject.toString());
+    return WxCpGsonBuilder.create().fromJson(GsonParser.parse(responseContent).getAsJsonArray("ids"),
+      new TypeToken>() {
+      }.getType());
+  }
+
+  @Override
+  public WxCpGroupChat getGroupChat(@NonNull String roomid) throws WxErrorException {
+    final String apiUrl = this.cpService.getWxCpConfigStorage().getApiUrl(GET_GROUP_CHAT);
+    JsonObject jsonObject = new JsonObject();
+    jsonObject.addProperty("roomid", roomid);
+    String responseContent = this.cpService.post(apiUrl, jsonObject.toString());
+    return WxCpGroupChat.fromJson(responseContent);
+  }
+
+  @Override
+  public WxCpAgreeInfo checkSingleAgree(@NonNull WxCpCheckAgreeRequest checkAgreeRequest) throws WxErrorException {
+    String apiUrl = this.cpService.getWxCpConfigStorage().getApiUrl(CHECK_SINGLE_AGREE);
+    String responseContent = this.cpService.post(apiUrl, checkAgreeRequest.toJson());
+    return WxCpAgreeInfo.fromJson(responseContent);
+  }
+
+}
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 2005bc36eb..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
@@ -1,24 +1,21 @@
 package me.chanjar.weixin.cp.api.impl;
 
-import com.google.gson.JsonElement;
 import com.google.gson.JsonObject;
-import com.google.gson.JsonParser;
-import lombok.AllArgsConstructor;
 import lombok.RequiredArgsConstructor;
-import me.chanjar.weixin.common.api.WxConsts;
 import me.chanjar.weixin.common.error.WxErrorException;
 import me.chanjar.weixin.common.util.http.URIUtil;
 import me.chanjar.weixin.common.util.json.GsonHelper;
+import me.chanjar.weixin.common.util.json.GsonParser;
 import me.chanjar.weixin.cp.api.WxCpOAuth2Service;
 import me.chanjar.weixin.cp.api.WxCpService;
 import me.chanjar.weixin.cp.bean.WxCpOauth2UserInfo;
 import me.chanjar.weixin.cp.bean.WxCpUserDetail;
-import me.chanjar.weixin.cp.constant.WxCpApiPathConsts;
+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.common.api.WxConsts.OAuth2Scope.SNSAPI_PRIVATEINFO;
-import static me.chanjar.weixin.common.api.WxConsts.OAuth2Scope.SNSAPI_USERINFO;
 import static me.chanjar.weixin.cp.constant.WxCpApiPathConsts.OAuth2.*;
 
 /**
@@ -73,16 +70,34 @@ public WxCpOauth2UserInfo getUserInfo(String code) throws WxErrorException {
 
   @Override
   public WxCpOauth2UserInfo getUserInfo(Integer agentId, String code) throws WxErrorException {
-    String responseText = this.mainService.get(String.format(this.mainService.getWxCpConfigStorage().getApiUrl(GET_USER_INFO), code, agentId), null);
-    JsonElement je = new JsonParser().parse(responseText);
-    JsonObject jo = je.getAsJsonObject();
+    String responseText =
+      this.mainService.get(String.format(this.mainService.getWxCpConfigStorage().getApiUrl(GET_USER_INFO), code,
+        agentId), null);
+    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"))
+      .parentUserId(GsonHelper.getString(jo, "parent_userid"))
+      .studentUserId(GsonHelper.getString(jo, "student_userid"))
+      .build();
+  }
+
+  @Override
+  public WxCpOauth2UserInfo getSchoolUserInfo(String code) throws WxErrorException {
+    String responseText =
+      this.mainService.get(String.format(this.mainService.getWxCpConfigStorage().getApiUrl(GET_SCHOOL_USER_INFO),
+        code), null);
+    JsonObject jo = GsonParser.parse(responseText);
+
+    return WxCpOauth2UserInfo.builder()
+      .deviceId(GsonHelper.getString(jo, "DeviceId"))
+      .parentUserId(GsonHelper.getString(jo, "parent_userid"))
+      .studentUserId(GsonHelper.getString(jo, "student_userid"))
       .build();
   }
 
@@ -90,7 +105,29 @@ public WxCpOauth2UserInfo getUserInfo(Integer agentId, String code) throws WxErr
   public WxCpUserDetail getUserDetail(String userTicket) throws WxErrorException {
     JsonObject param = new JsonObject();
     param.addProperty("user_ticket", userTicket);
-    String responseText = this.mainService.post(this.mainService.getWxCpConfigStorage().getApiUrl(GET_USER_DETAIL), param.toString());
+    String responseText = this.mainService.post(this.mainService.getWxCpConfigStorage().getApiUrl(GET_USER_DETAIL),
+      param.toString());
     return WxCpGsonBuilder.create().fromJson(responseText, WxCpUserDetail.class);
   }
+
+  @Override
+  public WxCpOauth2UserInfo getAuthUserInfo(String code) throws WxErrorException {
+    String responseText =
+      this.mainService.get(String.format(this.mainService.getWxCpConfigStorage().getApiUrl(GET_USER_AUTH_INFO), code), null);
+    JsonObject jo = GsonParser.parse(responseText);
+
+    return WxCpOauth2UserInfo.builder()
+      .userId(GsonHelper.getString(jo, "userid"))
+      .openId(GsonHelper.getString(jo, "openid"))
+      .userTicket(GsonHelper.getString(jo, "user_ticket"))
+      .externalUserId(GsonHelper.getString(jo, "external_userid"))
+      .build();
+  }
+
+  @Override
+  public WxCpSecondVerificationInfo getTfaInfo(String code) throws WxErrorException {
+    String responseText = this.mainService.post(this.mainService.getWxCpConfigStorage().getApiUrl(GET_TFA_INFO),
+      GsonHelper.buildJsonObject("code", code));
+    return WxCpGsonBuilder.create().fromJson(responseText, WxCpSecondVerificationInfo.class);
+  }
 }
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpOMailServiceImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpOMailServiceImpl.java
new file mode 100644
index 0000000000..c3844464e0
--- /dev/null
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpOMailServiceImpl.java
@@ -0,0 +1,80 @@
+package me.chanjar.weixin.cp.api.impl;
+
+import lombok.NonNull;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import me.chanjar.weixin.common.error.WxErrorException;
+import me.chanjar.weixin.cp.api.WxCpOaMailService;
+import me.chanjar.weixin.cp.api.WxCpService;
+import me.chanjar.weixin.cp.bean.WxCpBaseResp;
+import me.chanjar.weixin.cp.bean.oa.mail.WxCpMailCommonSendRequest;
+import me.chanjar.weixin.cp.bean.oa.mail.WxCpMailMeetingSendRequest;
+import me.chanjar.weixin.cp.bean.oa.mail.WxCpMailScheduleSendRequest;
+
+import static me.chanjar.weixin.cp.constant.WxCpApiPathConsts.Oa.EXMAIL_APP_COMPOSE_SEND;
+
+/**
+ * 企业微信邮件接口实现类.
+ *
+ * @author Hugo
+ */
+@Slf4j
+@RequiredArgsConstructor
+public class WxCpOMailServiceImpl implements WxCpOaMailService {
+  private final WxCpService cpService;
+
+  /**
+   * 发送普通邮件
+   * 应用可以通过该接口发送普通邮件,支持附件能力。
+   * 

+ * 请求方式:POST(HTTPS) + * 请求地址: ... + * + * @param request 发送普通邮件请求参数 + * @return wx cp base resp + * @throws WxErrorException the wx error exception + */ + @Override + public WxCpBaseResp mailCommonSend(@NonNull WxCpMailCommonSendRequest request) throws WxErrorException { + return this.mailSend(request.toJson()); + } + + /** + * 发送日程邮件 + * 应用可以通过该接口发送日程邮件。 + *

+ * 请求方式:POST(HTTPS) + * 请求地址: ... + * + * @param request 发送日程邮件请求参数 + * @return wx cp base resp + * @throws WxErrorException the wx error exception + */ + @Override + public WxCpBaseResp mailScheduleSend(@NonNull WxCpMailScheduleSendRequest request) throws WxErrorException { + return this.mailSend(request.toJson()); + } + + /** + * 发送会议邮件 + * 应用可以通过该接口发送会议邮件。 + *

+ * 请求方式:POST(HTTPS) + * 请求地址: ... + * + * @param request 发送会议邮件请求参数 + * @return wx cp base resp + * @throws WxErrorException the wx error exception + */ + @Override + public WxCpBaseResp mailMeetingSend(@NonNull WxCpMailMeetingSendRequest request) throws WxErrorException { + + return this.mailSend(request.toJson()); + } + + private WxCpBaseResp mailSend(String request) throws WxErrorException { + String apiUrl = this.cpService.getWxCpConfigStorage().getApiUrl(EXMAIL_APP_COMPOSE_SEND); + String responseContent = this.cpService.post(apiUrl, request); + return WxCpBaseResp.fromJson(responseContent); + } +} diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpOaAgentServiceImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpOaAgentServiceImpl.java new file mode 100644 index 0000000000..250ee0cb24 --- /dev/null +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpOaAgentServiceImpl.java @@ -0,0 +1,40 @@ +package me.chanjar.weixin.cp.api.impl; + +import com.google.gson.JsonObject; +import com.google.gson.reflect.TypeToken; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.common.util.json.GsonParser; +import me.chanjar.weixin.cp.api.WxCpOaAgentService; +import me.chanjar.weixin.cp.api.WxCpService; +import me.chanjar.weixin.cp.bean.oa.selfagent.WxCpOpenApprovalData; +import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder; + +import static me.chanjar.weixin.cp.constant.WxCpApiPathConsts.Oa.GET_OPEN_APPROVAL_DATA; + +/** + * 企业微信自建应用接口实现类. + * + * @author Wang_Wong created on 2022-04-06 + */ +@Slf4j +@RequiredArgsConstructor +public class WxCpOaAgentServiceImpl implements WxCpOaAgentService { + private final WxCpService cpService; + + @Override + public WxCpOpenApprovalData getOpenApprovalData(@NonNull String thirdNo) throws WxErrorException { + JsonObject jsonObject = new JsonObject(); + jsonObject.addProperty("thirdNo", thirdNo); + String apiUrl = this.cpService.getWxCpConfigStorage().getApiUrl(GET_OPEN_APPROVAL_DATA); + String responseContent = this.cpService.post(apiUrl, jsonObject.toString()); + return WxCpGsonBuilder.create() + .fromJson(GsonParser.parse(responseContent).get("data"), + new TypeToken() { + }.getType() + ); + } + +} diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpOaCalendarServiceImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpOaCalendarServiceImpl.java new file mode 100644 index 0000000000..ef24204493 --- /dev/null +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpOaCalendarServiceImpl.java @@ -0,0 +1,50 @@ +package me.chanjar.weixin.cp.api.impl; + +import com.google.gson.reflect.TypeToken; +import lombok.RequiredArgsConstructor; +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.common.util.json.GsonHelper; +import me.chanjar.weixin.common.util.json.GsonParser; +import me.chanjar.weixin.cp.api.WxCpOaCalendarService; +import me.chanjar.weixin.cp.api.WxCpService; +import me.chanjar.weixin.cp.bean.oa.calendar.WxCpOaCalendar; +import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder; + +import java.util.List; + +import static me.chanjar.weixin.cp.constant.WxCpApiPathConsts.Oa.*; + +/** + * . + * + * @author Binary Wang created on 2020-09-20 + */ +@RequiredArgsConstructor +public class WxCpOaCalendarServiceImpl implements WxCpOaCalendarService { + private final WxCpService wxCpService; + + @Override + public String add(WxCpOaCalendar calendar) throws WxErrorException { + return this.wxCpService.post(this.wxCpService.getWxCpConfigStorage().getApiUrl(CALENDAR_ADD), calendar); + } + + @Override + public void update(WxCpOaCalendar calendar) throws WxErrorException { + this.wxCpService.post(this.wxCpService.getWxCpConfigStorage().getApiUrl(CALENDAR_UPDATE), calendar); + } + + @Override + public List get(List calIds) throws WxErrorException { + String response = this.wxCpService.post(this.wxCpService.getWxCpConfigStorage().getApiUrl(CALENDAR_GET), + GsonHelper.buildJsonObject("cal_id_list", calIds)); + return WxCpGsonBuilder.create().fromJson(GsonParser.parse(response).get("calendar_list").getAsJsonArray().toString(), + new TypeToken>() { + }.getType()); + } + + @Override + public void delete(String calId) throws WxErrorException { + this.wxCpService.post(this.wxCpService.getWxCpConfigStorage().getApiUrl(CALENDAR_DEL), + GsonHelper.buildJsonObject("cal_id", calId)); + } +} diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpOaMeetingRoomServiceImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpOaMeetingRoomServiceImpl.java new file mode 100644 index 0000000000..9c32a45235 --- /dev/null +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpOaMeetingRoomServiceImpl.java @@ -0,0 +1,87 @@ +package me.chanjar.weixin.cp.api.impl; + +import com.google.gson.reflect.TypeToken; +import lombok.RequiredArgsConstructor; +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.common.util.json.GsonHelper; +import me.chanjar.weixin.common.util.json.GsonParser; +import me.chanjar.weixin.cp.api.WxCpOaMeetingRoomService; +import me.chanjar.weixin.cp.api.WxCpService; +import me.chanjar.weixin.cp.bean.oa.meetingroom.*; +import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder; + +import java.util.List; + +import static me.chanjar.weixin.cp.constant.WxCpApiPathConsts.Oa.*; + +/** + * The type Wx cp oa meeting room service. + * + * @author fcat + * @version 1.0 Create by 2022/8/12 23:49 + */ +@RequiredArgsConstructor +public class WxCpOaMeetingRoomServiceImpl implements WxCpOaMeetingRoomService { + private final WxCpService wxCpService; + + @Override + public String addMeetingRoom(WxCpOaMeetingRoom meetingRoom) throws WxErrorException { + return this.wxCpService.post(this.wxCpService.getWxCpConfigStorage().getApiUrl(MEETINGROOM_ADD), meetingRoom); + } + + @Override + public List listMeetingRoom(WxCpOaMeetingRoom meetingRoomRequest) throws WxErrorException { + String response = this.wxCpService.post(this.wxCpService.getWxCpConfigStorage().getApiUrl(MEETINGROOM_LIST), + meetingRoomRequest); + return WxCpGsonBuilder.create().fromJson(GsonParser.parse(response).get("meetingroom_list").getAsJsonArray().toString(), + new TypeToken>() { + }.getType()); + } + + @Override + public void editMeetingRoom(WxCpOaMeetingRoom meetingRoom) throws WxErrorException { + this.wxCpService.post(this.wxCpService.getWxCpConfigStorage().getApiUrl(MEETINGROOM_EDIT), meetingRoom); + } + + @Override + public void deleteMeetingRoom(Integer meetingRoomId) throws WxErrorException { + this.wxCpService.post(this.wxCpService.getWxCpConfigStorage().getApiUrl(MEETINGROOM_DEL), + GsonHelper.buildJsonObject("meetingroom_id", meetingRoomId)); + } + + @Override + public WxCpOaMeetingRoomBookingInfoResult getMeetingRoomBookingInfo(WxCpOaMeetingRoomBookingInfoRequest wxCpOaMeetingRoomBookingInfoRequest) throws WxErrorException { + String response = this.wxCpService.post(this.wxCpService.getWxCpConfigStorage().getApiUrl(MEETINGROOM_GET_BOOKING_INFO), wxCpOaMeetingRoomBookingInfoRequest); + return WxCpOaMeetingRoomBookingInfoResult.fromJson(response); + } + + @Override + public WxCpOaMeetingRoomBookResult bookingMeetingRoom(WxCpOaMeetingRoomBookRequest wxCpOaMeetingRoomBookRequest) throws WxErrorException { + String response = this.wxCpService.post(this.wxCpService.getWxCpConfigStorage().getApiUrl(MEETINGROOM_BOOK), wxCpOaMeetingRoomBookRequest); + return WxCpOaMeetingRoomBookResult.fromJson(response); + } + + @Override + public WxCpOaMeetingRoomBookResult bookingMeetingRoomBySchedule(WxCpOaMeetingRoomBookByScheduleRequest wxCpOaMeetingRoomBookByScheduleRequest) throws WxErrorException { + String response = this.wxCpService.post(this.wxCpService.getWxCpConfigStorage().getApiUrl(MEETINGROOM_BOOK_BY_SCHEDULE), wxCpOaMeetingRoomBookByScheduleRequest); + return WxCpOaMeetingRoomBookResult.fromJson(response); + } + + @Override + public WxCpOaMeetingRoomBookResult bookingMeetingRoomByMeeting(WxCpOaMeetingRoomBookByMeetingRequest wxCpOaMeetingRoomBookByMeetingRequest) throws WxErrorException { + String response = this.wxCpService.post(this.wxCpService.getWxCpConfigStorage().getApiUrl(MEETINGROOM_BOOK_BY_MEETING), wxCpOaMeetingRoomBookByMeetingRequest); + return WxCpOaMeetingRoomBookResult.fromJson(response); + } + + @Override + public void cancelBookMeetingRoom(WxCpOaMeetingRoomCancelBookRequest wxCpOaMeetingRoomCancelBookRequest) throws WxErrorException { + this.wxCpService.post(this.wxCpService.getWxCpConfigStorage().getApiUrl(MEETINGROOM_CANCEL_BOOK), wxCpOaMeetingRoomCancelBookRequest); + + } + + @Override + public WxCpOaMeetingRoomBookingInfoByBookingIdResult getBookingInfoByBookingId(WxCpOaMeetingRoomBookingInfoByBookingIdRequest wxCpOaMeetingRoomBookingInfoByBookingIdRequest) throws WxErrorException { + String response = this.wxCpService.post(this.wxCpService.getWxCpConfigStorage().getApiUrl(MEETINGROOM_BOOKINFO_GET), wxCpOaMeetingRoomBookingInfoByBookingIdRequest); + return WxCpOaMeetingRoomBookingInfoByBookingIdResult.fromJson(response); + } +} diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpOaOaScheduleServiceImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpOaOaScheduleServiceImpl.java new file mode 100644 index 0000000000..c9a6161b2e --- /dev/null +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpOaOaScheduleServiceImpl.java @@ -0,0 +1,81 @@ +package me.chanjar.weixin.cp.api.impl; + +import com.google.common.collect.ImmutableMap; +import com.google.gson.reflect.TypeToken; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.common.util.json.GsonParser; +import me.chanjar.weixin.cp.api.WxCpOaScheduleService; +import me.chanjar.weixin.cp.api.WxCpService; +import me.chanjar.weixin.cp.bean.oa.WxCpOaSchedule; +import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder; + +import java.io.Serializable; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static me.chanjar.weixin.cp.constant.WxCpApiPathConsts.Oa.*; + +/** + * 企业微信日程接口实现类. + * + * @author Binary Wang created on 2020-12-25 + */ +@Slf4j +@RequiredArgsConstructor +public class WxCpOaOaScheduleServiceImpl implements WxCpOaScheduleService { + private final WxCpService cpService; + + @Override + public String add(WxCpOaSchedule schedule, Integer agentId) throws WxErrorException { + Map param; + if (agentId == null) { + param = ImmutableMap.of("schedule", schedule); + } else { + param = ImmutableMap.of("schedule", schedule, "agentid", agentId); + } + + return this.cpService.post(this.cpService.getWxCpConfigStorage().getApiUrl(SCHEDULE_ADD), + WxCpGsonBuilder.create().toJson(param)); + } + + @Override + public void update(WxCpOaSchedule schedule) throws WxErrorException { + this.cpService.post(this.cpService.getWxCpConfigStorage().getApiUrl(SCHEDULE_UPDATE), + WxCpGsonBuilder.create().toJson(ImmutableMap.of("schedule", schedule))); + } + + @Override + public List getDetails(List scheduleIds) throws WxErrorException { + final String response = this.cpService.post(this.cpService.getWxCpConfigStorage().getApiUrl(SCHEDULE_GET), + WxCpGsonBuilder.create().toJson(ImmutableMap.of("schedule_id_list", scheduleIds))); + return WxCpGsonBuilder.create().fromJson(GsonParser.parse(response).get("schedule_list"), + new TypeToken>() { + }.getType()); + } + + @Override + public void delete(String scheduleId) throws WxErrorException { + this.cpService.post(this.cpService.getWxCpConfigStorage().getApiUrl(SCHEDULE_DEL), + WxCpGsonBuilder.create().toJson(ImmutableMap.of("schedule_id", scheduleId))); + } + + @Override + public List listByCalendar(String calId, Integer offset, Integer limit) throws WxErrorException { + final Map param = new HashMap<>(3); + param.put("cal_id", calId); + if (offset != null) { + param.put("offset", offset); + } + if (limit != null) { + param.put("limit", limit); + } + final String response = this.cpService.post(this.cpService.getWxCpConfigStorage().getApiUrl(SCHEDULE_LIST), + WxCpGsonBuilder.create().toJson(param)); + return WxCpGsonBuilder.create().fromJson(GsonParser.parse(response).get("schedule_list"), + new TypeToken>() { + }.getType()); + } +} 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 b8449ced13..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 @@ -1,20 +1,20 @@ package me.chanjar.weixin.cp.api.impl; import com.google.gson.JsonArray; -import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonParser; import com.google.gson.reflect.TypeToken; +import lombok.NonNull; import lombok.RequiredArgsConstructor; import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.common.error.WxRuntimeException; +import me.chanjar.weixin.common.util.json.GsonParser; import me.chanjar.weixin.cp.api.WxCpOaService; import me.chanjar.weixin.cp.api.WxCpService; -import me.chanjar.weixin.cp.bean.WxCpApprovalDataResult; -import me.chanjar.weixin.cp.bean.WxCpCheckinData; -import me.chanjar.weixin.cp.bean.WxCpCheckinOption; -import me.chanjar.weixin.cp.bean.WxCpDialRecord; -import me.chanjar.weixin.cp.constant.WxCpApiPathConsts; +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.StringUtils; import java.util.Date; import java.util.List; @@ -22,39 +22,45 @@ import static me.chanjar.weixin.cp.constant.WxCpApiPathConsts.Oa.*; /** - * . + * 企业微信 OA 接口实现 * - * @author Element - * @date 2019-04-06 11:20 + * @author Element created on 2019-04-06 11:20 */ @RequiredArgsConstructor public class WxCpOaServiceImpl implements WxCpOaService { private final WxCpService mainService; + private static final int MONTH_SECONDS = 31 * 24 * 60 * 60; + private static final int USER_IDS_LIMIT = 100; + @Override - public List getCheckinData(Integer openCheckinDataType, Date startTime, Date endTime, - List userIdList) throws WxErrorException { - if (startTime == null || endTime == null) { - throw new RuntimeException("starttime and endtime can't be null"); - } + public String apply(WxCpOaApplyEventRequest request) throws WxErrorException { + String responseContent = this.mainService.post(this.mainService.getWxCpConfigStorage().getApiUrl(APPLY_EVENT), + request.toJson()); + return GsonParser.parse(responseContent).get("sp_no").getAsString(); + } - if (userIdList == null || userIdList.size() > 100) { - throw new RuntimeException("用户列表不能为空,不超过100个,若用户超过100个,请分批获取"); + @Override + public List getCheckinData(Integer openCheckinDataType, @NonNull Date startTime, + @NonNull Date endTime, + List userIdList) throws WxErrorException { + if (userIdList == null || userIdList.size() > USER_IDS_LIMIT) { + throw new WxRuntimeException("用户列表不能为空,不超过 " + USER_IDS_LIMIT + " 个,若用户超过 " + USER_IDS_LIMIT + " 个,请分批获取"); } - long endtimestamp = endTime.getTime() / 1000L; - long starttimestamp = startTime.getTime() / 1000L; + long endTimestamp = endTime.getTime() / 1000L; + long startTimestamp = startTime.getTime() / 1000L; - if (endtimestamp - starttimestamp < 0 || endtimestamp - starttimestamp >= 30 * 24 * 60 * 60) { - throw new RuntimeException("获取记录时间跨度不超过一个月"); + if (endTimestamp - startTimestamp < 0 || endTimestamp - startTimestamp > MONTH_SECONDS) { + throw new WxRuntimeException("获取记录时间跨度不超过一个月"); } JsonObject jsonObject = new JsonObject(); JsonArray jsonArray = new JsonArray(); jsonObject.addProperty("opencheckindatatype", openCheckinDataType); - jsonObject.addProperty("starttime", starttimestamp); - jsonObject.addProperty("endtime", endtimestamp); + jsonObject.addProperty("starttime", startTimestamp); + jsonObject.addProperty("endtime", endTimestamp); for (String userid : userIdList) { jsonArray.add(userid); @@ -64,23 +70,17 @@ public List getCheckinData(Integer openCheckinDataType, Date st final String url = this.mainService.getWxCpConfigStorage().getApiUrl(GET_CHECKIN_DATA); String responseContent = this.mainService.post(url, jsonObject.toString()); - JsonElement tmpJsonElement = new JsonParser().parse(responseContent); - return WxCpGsonBuilder.create() - .fromJson( - tmpJsonElement.getAsJsonObject().get("checkindata"), - new TypeToken>() { - }.getType() - ); + JsonObject tmpJson = GsonParser.parse(responseContent); + return WxCpGsonBuilder.create().fromJson(tmpJson.get("checkindata"), + new TypeToken>() { + }.getType() + ); } @Override - public List getCheckinOption(Date datetime, List userIdList) throws WxErrorException { - if (datetime == null) { - throw new RuntimeException("datetime can't be null"); - } - - if (userIdList == null || userIdList.size() > 100) { - throw new RuntimeException("用户列表不能为空,不超过100个,若用户超过100个,请分批获取"); + public List getCheckinOption(@NonNull Date datetime, List userIdList) throws WxErrorException { + if (userIdList == null || userIdList.size() > USER_IDS_LIMIT) { + throw new WxRuntimeException("用户列表不能为空,不超过 " + USER_IDS_LIMIT + " 个,若用户超过 " + USER_IDS_LIMIT + " 个,请分批获取"); } JsonArray jsonArray = new JsonArray(); @@ -94,28 +94,158 @@ public List getCheckinOption(Date datetime, List user final String url = this.mainService.getWxCpConfigStorage().getApiUrl(GET_CHECKIN_OPTION); String responseContent = this.mainService.post(url, jsonObject.toString()); - JsonElement tmpJsonElement = new JsonParser().parse(responseContent); + JsonObject tmpJson = GsonParser.parse(responseContent); + + return WxCpGsonBuilder.create().fromJson(tmpJson.get("info"), + new TypeToken>() { + }.getType() + ); + } + + @Override + public List getCropCheckinOption() throws WxErrorException { + JsonObject jsonObject = new JsonObject(); + final String url = this.mainService.getWxCpConfigStorage().getApiUrl(GET_CORP_CHECKIN_OPTION); + String responseContent = this.mainService.post(url, jsonObject.toString()); + JsonObject tmpJson = GsonParser.parse(responseContent); + + return WxCpGsonBuilder.create().fromJson(tmpJson.get("group"), + new TypeToken>() { + }.getType() + ); + } + + @Override + public WxCpApprovalInfo getApprovalInfo(@NonNull Date startTime, @NonNull Date endTime, + Integer cursor, Integer size, List filters) + throws WxErrorException { + if (cursor == null) { + cursor = 0; + } + + if (size == null) { + size = 100; + } + + if (size < 0 || size > 100) { + throw new IllegalArgumentException("size参数错误,请使用[1-100]填充,默认100"); + } + + JsonObject jsonObject = new JsonObject(); + jsonObject.addProperty("starttime", startTime.getTime() / 1000L); + jsonObject.addProperty("endtime", endTime.getTime() / 1000L); + jsonObject.addProperty("size", size); + jsonObject.addProperty("cursor", cursor); + + if (filters != null && !filters.isEmpty()) { + JsonArray filterJsonArray = new JsonArray(); + for (WxCpApprovalInfoQueryFilter filter : filters) { + filterJsonArray.add(JsonParser.parseString(filter.toJson())); + } + jsonObject.add("filters", filterJsonArray); + } + + final String url = this.mainService.getWxCpConfigStorage().getApiUrl(GET_APPROVAL_INFO); + String responseContent = this.mainService.post(url, jsonObject.toString()); - return WxCpGsonBuilder.create() - .fromJson( - tmpJsonElement.getAsJsonObject().get("info"), - new TypeToken>() { - }.getType() - ); + return WxCpGsonBuilder.create().fromJson(responseContent, WxCpApprovalInfo.class); } @Override - public WxCpApprovalDataResult getApprovalData(Date startTime, Date endTime, Long nextSpnum) throws WxErrorException { + public WxCpApprovalInfo getApprovalInfo(@NonNull Date startTime, @NonNull Date endTime) throws WxErrorException { + return this.getApprovalInfo(startTime, endTime, 0, null, null); + } + + @Override + public WxCpApprovalInfo getApprovalInfo(@NonNull Date startTime, @NonNull Date endTime, String newCursor, + Integer size, List filters) + throws WxErrorException { + if (newCursor == null) { + newCursor = ""; + } + + if (size == null) { + size = 100; + } + + if (size < 0 || size > 100) { + throw new IllegalArgumentException("size参数错误,请使用[1-100]填充,默认100"); + } + JsonObject jsonObject = new JsonObject(); jsonObject.addProperty("starttime", startTime.getTime() / 1000L); jsonObject.addProperty("endtime", endTime.getTime() / 1000L); - if (nextSpnum != null) { - jsonObject.addProperty("next_spnum", nextSpnum); + jsonObject.addProperty("size", size); + jsonObject.addProperty("new_cursor", newCursor); + + if (filters != null && !filters.isEmpty()) { + JsonArray filterJsonArray = new JsonArray(); + for (WxCpApprovalInfoQueryFilter filter : filters) { + filterJsonArray.add(JsonParser.parseString(filter.toJson())); + } + jsonObject.add("filters", filterJsonArray); } + final String url = this.mainService.getWxCpConfigStorage().getApiUrl(GET_APPROVAL_INFO); + String responseContent = this.mainService.post(url, jsonObject.toString()); + + return WxCpGsonBuilder.create().fromJson(responseContent, WxCpApprovalInfo.class); + } + + @Override + public WxCpApprovalDetailResult getApprovalDetail(@NonNull String spNo) throws WxErrorException { + JsonObject jsonObject = new JsonObject(); + jsonObject.addProperty("sp_no", spNo); + + final String url = this.mainService.getWxCpConfigStorage().getApiUrl(GET_APPROVAL_DETAIL); + String responseContent = this.mainService.post(url, jsonObject.toString()); + + return WxCpGsonBuilder.create().fromJson(responseContent, WxCpApprovalDetailResult.class); + } + + @Override + public WxCpCorpConfInfo getCorpConf() throws WxErrorException { + final String url = this.mainService.getWxCpConfigStorage().getApiUrl(GET_CORP_CONF); + String responseContent = this.mainService.get(url, null); + return WxCpCorpConfInfo.fromJson(responseContent); + } + + @Override + public WxCpUserVacationQuota getUserVacationQuota(@NonNull String userId) throws WxErrorException { + final String url = this.mainService.getWxCpConfigStorage().getApiUrl(GET_USER_VACATION_QUOTA); + JsonObject jsonObject = new JsonObject(); + jsonObject.addProperty("userid", userId); + String responseContent = this.mainService.post(url, jsonObject.toString()); + return WxCpUserVacationQuota.fromJson(responseContent); + } + + @Override + public WxCpGetApprovalData getApprovalData(@NonNull Long startTime, @NonNull Long endTime, Long nextSpNum) throws WxErrorException { final String url = this.mainService.getWxCpConfigStorage().getApiUrl(GET_APPROVAL_DATA); + JsonObject jsonObject = new JsonObject(); + jsonObject.addProperty("starttime", startTime); + jsonObject.addProperty("endtime", endTime); + if (nextSpNum != null) { + jsonObject.addProperty("next_spnum", nextSpNum); + } + String responseContent = this.mainService.post(url, jsonObject.toString()); + return WxCpGetApprovalData.fromJson(responseContent); + } + + @Override + public WxCpBaseResp setOneUserQuota(@NonNull String userId, @NonNull Integer vacationId, + @NonNull Integer leftDuration, @NonNull Integer timeAttr, String remarks) throws WxErrorException { + final String url = this.mainService.getWxCpConfigStorage().getApiUrl(SET_ONE_USER_QUOTA); + JsonObject jsonObject = new JsonObject(); + jsonObject.addProperty("userid", userId); + jsonObject.addProperty("vacation_id", vacationId); + jsonObject.addProperty("leftduration", leftDuration); + jsonObject.addProperty("time_attr", timeAttr); + if (StringUtils.isNotEmpty(remarks)) { + jsonObject.addProperty("remarks", remarks); + } String responseContent = this.mainService.post(url, jsonObject.toString()); - return WxCpGsonBuilder.create().fromJson(responseContent, WxCpApprovalDataResult.class); + return WxCpBaseResp.fromJson(responseContent); } @Override @@ -135,12 +265,11 @@ public List getDialRecord(Date startTime, Date endTime, Integer jsonObject.addProperty("limit", limit); if (startTime != null && endTime != null) { - long endtimestamp = endTime.getTime() / 1000L; long starttimestamp = startTime.getTime() / 1000L; - if (endtimestamp - starttimestamp < 0 || endtimestamp - starttimestamp >= 30 * 24 * 60 * 60) { - throw new RuntimeException("受限于网络传输,起止时间的最大跨度为30天,如超过30天,则以结束时间为基准向前取30天进行查询"); + if (endtimestamp - starttimestamp < 0 || endtimestamp - starttimestamp >= MONTH_SECONDS) { + throw new WxRuntimeException("受限于网络传输,起止时间的最大跨度为30天,如超过30天,则以结束时间为基准向前取30天进行查询"); } jsonObject.addProperty("start_time", starttimestamp); @@ -149,11 +278,143 @@ public List getDialRecord(Date startTime, Date endTime, Integer final String url = this.mainService.getWxCpConfigStorage().getApiUrl(GET_DIAL_RECORD); String responseContent = this.mainService.post(url, jsonObject.toString()); - JsonElement tmpJsonElement = new JsonParser().parse(responseContent); + JsonObject tmpJson = GsonParser.parse(responseContent); - return WxCpGsonBuilder.create().fromJson(tmpJsonElement.getAsJsonObject().get("record"), + return WxCpGsonBuilder.create().fromJson(tmpJson.get("record"), new TypeToken>() { }.getType() ); } + + @Override + public WxCpOaApprovalTemplateResult getTemplateDetail(@NonNull String templateId) throws WxErrorException { + JsonObject jsonObject = new JsonObject(); + jsonObject.addProperty("template_id", templateId); + final String url = this.mainService.getWxCpConfigStorage().getApiUrl(GET_TEMPLATE_DETAIL); + String responseContent = this.mainService.post(url, jsonObject.toString()); + return WxCpGsonBuilder.create().fromJson(responseContent, WxCpOaApprovalTemplateResult.class); + } + + @Override + public String createOaApprovalTemplate(WxCpOaApprovalTemplate cpTemplate) throws WxErrorException { + final String url = this.mainService.getWxCpConfigStorage().getApiUrl(CREATE_TEMPLATE); + String responseContent = this.mainService.post(url, WxCpGsonBuilder.create().toJson(cpTemplate)); + JsonObject tmpJson = GsonParser.parse(responseContent); + return tmpJson.get("template_id").getAsString(); + } + + @Override + public void updateOaApprovalTemplate(WxCpOaApprovalTemplate wxCpTemplate) throws WxErrorException { + final String url = this.mainService.getWxCpConfigStorage().getApiUrl(UPDATE_TEMPLATE); + this.mainService.post(url, WxCpGsonBuilder.create().toJson(wxCpTemplate)); + } + + @Override + public List getCheckinDayData(@NonNull Date startTime, @NonNull Date endTime, + List userIdList) + throws WxErrorException { + if (userIdList == null || userIdList.size() > USER_IDS_LIMIT) { + throw new WxRuntimeException("用户列表不能为空,不超过 " + USER_IDS_LIMIT + " 个,若用户超过 " + USER_IDS_LIMIT + " 个,请分批获取"); + } + + long endTimestamp = endTime.getTime() / 1000L; + long startTimestamp = startTime.getTime() / 1000L; + + JsonObject jsonObject = new JsonObject(); + JsonArray jsonArray = new JsonArray(); + + jsonObject.addProperty("starttime", startTimestamp); + jsonObject.addProperty("endtime", endTimestamp); + + for (String userid : userIdList) { + jsonArray.add(userid); + } + jsonObject.add("useridlist", jsonArray); + + final String url = this.mainService.getWxCpConfigStorage().getApiUrl(GET_CHECKIN_DAY_DATA); + String responseContent = this.mainService.post(url, jsonObject.toString()); + JsonObject tmpJson = GsonParser.parse(responseContent); + return WxCpGsonBuilder.create().fromJson(tmpJson.get("datas"), + new TypeToken>() { + }.getType() + ); + } + + @Override + public List getCheckinMonthData(@NonNull Date startTime, @NonNull Date endTime, + List userIdList) + throws WxErrorException { + if (userIdList == null || userIdList.size() > USER_IDS_LIMIT) { + throw new WxRuntimeException("用户列表不能为空,不超过 " + USER_IDS_LIMIT + " 个,若用户超过 " + USER_IDS_LIMIT + " 个,请分批获取"); + } + + long endTimestamp = endTime.getTime() / 1000L; + long startTimestamp = startTime.getTime() / 1000L; + + JsonObject jsonObject = new JsonObject(); + JsonArray jsonArray = new JsonArray(); + + jsonObject.addProperty("starttime", startTimestamp); + jsonObject.addProperty("endtime", endTimestamp); + + for (String userid : userIdList) { + jsonArray.add(userid); + } + jsonObject.add("useridlist", jsonArray); + + final String url = this.mainService.getWxCpConfigStorage().getApiUrl(GET_CHECKIN_MONTH_DATA); + String responseContent = this.mainService.post(url, jsonObject.toString()); + JsonObject tmpJson = GsonParser.parse(responseContent); + return WxCpGsonBuilder.create().fromJson(tmpJson.get("datas"), + new TypeToken>() { + }.getType() + ); + } + + @Override + public List getCheckinScheduleList(@NonNull Date startTime, @NonNull Date endTime, + List userIdList) + throws WxErrorException { + if (userIdList == null || userIdList.size() > USER_IDS_LIMIT) { + throw new WxRuntimeException("用户列表不能为空,不超过 " + USER_IDS_LIMIT + " 个,若用户超过 " + USER_IDS_LIMIT + " 个,请分批获取"); + } + + long endTimestamp = endTime.getTime() / 1000L; + long startTimestamp = startTime.getTime() / 1000L; + + + JsonObject jsonObject = new JsonObject(); + JsonArray jsonArray = new JsonArray(); + + jsonObject.addProperty("starttime", startTimestamp); + jsonObject.addProperty("endtime", endTimestamp); + + for (String userid : userIdList) { + jsonArray.add(userid); + } + jsonObject.add("useridlist", jsonArray); + + final String url = this.mainService.getWxCpConfigStorage().getApiUrl(GET_CHECKIN_SCHEDULE_DATA); + String responseContent = this.mainService.post(url, jsonObject.toString()); + JsonObject tmpJson = GsonParser.parse(responseContent); + return WxCpGsonBuilder.create().fromJson(tmpJson.get("schedule_list"), + new TypeToken>() { + }.getType() + ); + } + + @Override + public void setCheckinScheduleList(WxCpSetCheckinSchedule wxCpSetCheckinSchedule) throws WxErrorException { + final String url = this.mainService.getWxCpConfigStorage().getApiUrl(SET_CHECKIN_SCHEDULE_DATA); + this.mainService.post(url, WxCpGsonBuilder.create().toJson(wxCpSetCheckinSchedule)); + } + + @Override + public void addCheckInUserFace(String userId, String userFace) throws WxErrorException { + JsonObject jsonObject = new JsonObject(); + jsonObject.addProperty("userid", userId); + jsonObject.addProperty("userface", userFace); + String url = this.mainService.getWxCpConfigStorage().getApiUrl(ADD_CHECK_IN_USER_FACE); + this.mainService.post(url, jsonObject.toString()); + } } diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpOaWeDocServiceImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpOaWeDocServiceImpl.java new file mode 100644 index 0000000000..fc5379dc73 --- /dev/null +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpOaWeDocServiceImpl.java @@ -0,0 +1,89 @@ +package me.chanjar.weixin.cp.api.impl; + +import com.google.gson.JsonObject; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.cp.api.WxCpOaWeDocService; +import me.chanjar.weixin.cp.api.WxCpService; +import me.chanjar.weixin.cp.bean.WxCpBaseResp; +import me.chanjar.weixin.cp.bean.oa.doc.*; + +import static me.chanjar.weixin.cp.constant.WxCpApiPathConsts.Oa.*; + +/** + * 企业微信微盘接口实现类. + * + * @author Wang_Wong created on 2022-04-22 + */ +@Slf4j +@RequiredArgsConstructor +public class WxCpOaWeDocServiceImpl implements WxCpOaWeDocService { + private final WxCpService cpService; + + @Override + public WxCpDocCreateData docCreate(@NonNull WxCpDocCreateRequest request) throws WxErrorException { + String apiUrl = this.cpService.getWxCpConfigStorage().getApiUrl(WEDOC_CREATE_DOC); + String responseContent = this.cpService.post(apiUrl, request.toJson()); + return WxCpDocCreateData.fromJson(responseContent); + } + + @Override + public WxCpBaseResp docRename(@NonNull WxCpDocRenameRequest request) throws WxErrorException { + String apiUrl = this.cpService.getWxCpConfigStorage().getApiUrl(WEDOC_RENAME_DOC); + String responseContent = this.cpService.post(apiUrl, request.toJson()); + return WxCpBaseResp.fromJson(responseContent); + } + + @Override + public WxCpBaseResp docDelete(String docId, String formId) throws WxErrorException { + String apiUrl = this.cpService.getWxCpConfigStorage().getApiUrl(WEDOC_DEL_DOC); + JsonObject jsonObject = new JsonObject(); + jsonObject.addProperty("docid", docId); + jsonObject.addProperty("formid", formId); + String responseContent = this.cpService.post(apiUrl, jsonObject.toString()); + return WxCpBaseResp.fromJson(responseContent); + } + + @Override + public WxCpDocInfo docInfo(@NonNull String docId) throws WxErrorException { + String apiUrl = this.cpService.getWxCpConfigStorage().getApiUrl(WEDOC_GET_DOC_BASE_INFO); + JsonObject jsonObject = new JsonObject(); + jsonObject.addProperty("docid", docId); + String responseContent = this.cpService.post(apiUrl, jsonObject.toString()); + return WxCpDocInfo.fromJson(responseContent); + } + + @Override + public WxCpDocShare docShare(@NonNull String docId) throws WxErrorException { + String apiUrl = this.cpService.getWxCpConfigStorage().getApiUrl(WEDOC_DOC_SHARE); + JsonObject jsonObject = new JsonObject(); + jsonObject.addProperty("docid", docId); + String responseContent = this.cpService.post(apiUrl, jsonObject.toString()); + return WxCpDocShare.fromJson(responseContent); + } + + @Override + public WxCpDocSheetBatchUpdateResponse docBatchUpdate(@NonNull WxCpDocSheetBatchUpdateRequest request) throws WxErrorException { + String apiUrl = this.cpService.getWxCpConfigStorage().getApiUrl(WEDOC_SPREADSHEET_BATCH_UPDATE); + String responseContent = this.cpService.post(apiUrl, request.toJson()); + return WxCpDocSheetBatchUpdateResponse.fromJson(responseContent); + } + + @Override + public WxCpDocSheetProperties getSheetProperties(@NonNull String docId) throws WxErrorException { + String apiUrl = this.cpService.getWxCpConfigStorage().getApiUrl(WEDOC_SPREADSHEET_GET_SHEET_PROPERTIES); + JsonObject jsonObject = new JsonObject(); + jsonObject.addProperty("docid", docId); + String responseContent = this.cpService.post(apiUrl, jsonObject.toString()); + return WxCpDocSheetProperties.fromJson(responseContent); + } + + @Override + public WxCpDocSheetData getSheetRangeData(@NonNull WxCpDocSheetGetDataRequest request) throws WxErrorException { + String apiUrl = this.cpService.getWxCpConfigStorage().getApiUrl(WEDOC_SPREADSHEET_GET_SHEET_RANGE_DATA); + String responseContent = this.cpService.post(apiUrl, request.toJson()); + return WxCpDocSheetData.fromJson(responseContent); + } +} diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpOaWeDriveServiceImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpOaWeDriveServiceImpl.java new file mode 100644 index 0000000000..a41195ae84 --- /dev/null +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpOaWeDriveServiceImpl.java @@ -0,0 +1,196 @@ +package me.chanjar.weixin.cp.api.impl; + +import com.google.gson.JsonObject; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.cp.api.WxCpOaWeDriveService; +import me.chanjar.weixin.cp.api.WxCpService; +import me.chanjar.weixin.cp.bean.WxCpBaseResp; +import me.chanjar.weixin.cp.bean.oa.wedrive.*; + +import java.util.List; + +import static me.chanjar.weixin.cp.constant.WxCpApiPathConsts.Oa.*; + +/** + * 企业微信微盘接口实现类. + * + * @author Wang_Wong created on 2022-04-22 + */ +@Slf4j +@RequiredArgsConstructor +public class WxCpOaWeDriveServiceImpl implements WxCpOaWeDriveService { + private final WxCpService cpService; + + @Override + public WxCpSpaceCreateData spaceCreate(@NonNull WxCpSpaceCreateRequest request) throws WxErrorException { + String apiUrl = this.cpService.getWxCpConfigStorage().getApiUrl(SPACE_CREATE); + String responseContent = this.cpService.post(apiUrl, request.toJson()); + return WxCpSpaceCreateData.fromJson(responseContent); + } + + @Override + public WxCpBaseResp spaceRename(@NonNull WxCpSpaceRenameRequest request) throws WxErrorException { + String apiUrl = this.cpService.getWxCpConfigStorage().getApiUrl(SPACE_RENAME); + String responseContent = this.cpService.post(apiUrl, request.toJson()); + return WxCpBaseResp.fromJson(responseContent); + } + + @Override + public WxCpBaseResp spaceDismiss(@NonNull String spaceId) throws WxErrorException { + String apiUrl = this.cpService.getWxCpConfigStorage().getApiUrl(SPACE_DISMISS); + JsonObject jsonObject = new JsonObject(); + jsonObject.addProperty("spaceid", spaceId); + String responseContent = this.cpService.post(apiUrl, jsonObject.toString()); + return WxCpBaseResp.fromJson(responseContent); + } + + @Override + public WxCpSpaceInfo spaceInfo(@NonNull String spaceId) throws WxErrorException { + String apiUrl = this.cpService.getWxCpConfigStorage().getApiUrl(SPACE_INFO); + JsonObject jsonObject = new JsonObject(); + jsonObject.addProperty("spaceid", spaceId); + String responseContent = this.cpService.post(apiUrl, jsonObject.toString()); + return WxCpSpaceInfo.fromJson(responseContent); + } + + @Override + public WxCpBaseResp spaceAclAdd(@NonNull WxCpSpaceAclAddRequest request) throws WxErrorException { + String apiUrl = this.cpService.getWxCpConfigStorage().getApiUrl(SPACE_ACL_ADD); + String responseContent = this.cpService.post(apiUrl, request.toJson()); + return WxCpBaseResp.fromJson(responseContent); + } + + @Override + public WxCpBaseResp spaceAclDel(@NonNull WxCpSpaceAclDelRequest request) throws WxErrorException { + String apiUrl = this.cpService.getWxCpConfigStorage().getApiUrl(SPACE_ACL_DEL); + String responseContent = this.cpService.post(apiUrl, request.toJson()); + return WxCpBaseResp.fromJson(responseContent); + } + + @Override + public WxCpBaseResp spaceSetting(@NonNull WxCpSpaceSettingRequest request) throws WxErrorException { + String apiUrl = this.cpService.getWxCpConfigStorage().getApiUrl(SPACE_SETTING); + String responseContent = this.cpService.post(apiUrl, request.toJson()); + return WxCpBaseResp.fromJson(responseContent); + } + + @Override + public WxCpSpaceShare spaceShare(@NonNull String spaceId) throws WxErrorException { + String apiUrl = this.cpService.getWxCpConfigStorage().getApiUrl(SPACE_SHARE); + JsonObject jsonObject = new JsonObject(); + jsonObject.addProperty("spaceid", spaceId); + String responseContent = this.cpService.post(apiUrl, jsonObject.toString()); + return WxCpSpaceShare.fromJson(responseContent); + } + + @Override + public WxCpFileList fileList(@NonNull WxCpFileListRequest request) throws WxErrorException { + String apiUrl = this.cpService.getWxCpConfigStorage().getApiUrl(FILE_LIST); + String responseContent = this.cpService.post(apiUrl, request.toJson()); + return WxCpFileList.fromJson(responseContent); + } + + @Override + public WxCpFileUpload fileUpload(@NonNull WxCpFileUploadRequest request) throws WxErrorException { + String apiUrl = this.cpService.getWxCpConfigStorage().getApiUrl(FILE_UPLOAD); + String responseContent = this.cpService.post(apiUrl, request.toJson()); + return WxCpFileUpload.fromJson(responseContent); + } + + @Override + public WxCpFileDownload fileDownload(@NonNull String userId, @NonNull String fileId) throws WxErrorException { + String apiUrl = this.cpService.getWxCpConfigStorage().getApiUrl(FILE_DOWNLOAD); + JsonObject jsonObject = new JsonObject(); + jsonObject.addProperty("userid", userId); + jsonObject.addProperty("fileid", fileId); + String responseContent = this.cpService.post(apiUrl, jsonObject.toString()); + return WxCpFileDownload.fromJson(responseContent); + } + + @Override + public WxCpFileRename fileRename(@NonNull String fileId, @NonNull String newName) throws WxErrorException { + String apiUrl = this.cpService.getWxCpConfigStorage().getApiUrl(FILE_RENAME); + JsonObject jsonObject = new JsonObject(); + jsonObject.addProperty("fileid", fileId); + jsonObject.addProperty("new_name", newName); + String responseContent = this.cpService.post(apiUrl, jsonObject.toString()); + return WxCpFileRename.fromJson(responseContent); + } + + @Override + public WxCpFileCreate fileCreate(@NonNull String spaceId, @NonNull String fatherId, + @NonNull Integer fileType, @NonNull String fileName) throws WxErrorException { + String apiUrl = this.cpService.getWxCpConfigStorage().getApiUrl(FILE_CREATE); + JsonObject jsonObject = new JsonObject(); + jsonObject.addProperty("spaceid", spaceId); + jsonObject.addProperty("fatherid", fatherId); + jsonObject.addProperty("file_type", fileType); + jsonObject.addProperty("file_name", fileName); + String responseContent = this.cpService.post(apiUrl, jsonObject.toString()); + return WxCpFileCreate.fromJson(responseContent); + } + + @Override + public WxCpFileMove fileMove(@NonNull WxCpFileMoveRequest request) throws WxErrorException { + String apiUrl = this.cpService.getWxCpConfigStorage().getApiUrl(FILE_MOVE); + String responseContent = this.cpService.post(apiUrl, request.toJson()); + return WxCpFileMove.fromJson(responseContent); + } + + @Override + public WxCpBaseResp fileDelete(@NonNull List fileIds) throws WxErrorException { + String apiUrl = this.cpService.getWxCpConfigStorage().getApiUrl(FILE_DELETE); + WxCpFileDeleteRequest request = new WxCpFileDeleteRequest(fileIds); + String responseContent = this.cpService.post(apiUrl, request.toJson()); + return WxCpBaseResp.fromJson(responseContent); + } + + @Override + public WxCpBaseResp fileAclAdd(@NonNull WxCpFileAclAddRequest request) throws WxErrorException { + String apiUrl = this.cpService.getWxCpConfigStorage().getApiUrl(FILE_ACL_ADD); + String responseContent = this.cpService.post(apiUrl, request.toJson()); + return WxCpBaseResp.fromJson(responseContent); + } + + @Override + public WxCpBaseResp fileAclDel(@NonNull WxCpFileAclDelRequest request) throws WxErrorException { + String apiUrl = this.cpService.getWxCpConfigStorage().getApiUrl(FILE_ACL_DEL); + String responseContent = this.cpService.post(apiUrl, request.toJson()); + return WxCpBaseResp.fromJson(responseContent); + } + + @Override + 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("fileid", fileId); + jsonObject.addProperty("auth_scope", authScope); + if (auth != null) { + jsonObject.addProperty("auth", auth); + } + String responseContent = this.cpService.post(apiUrl, jsonObject.toString()); + return WxCpBaseResp.fromJson(responseContent); + } + + @Override + public WxCpFileShare fileShare(@NonNull String fileId) throws WxErrorException { + String apiUrl = this.cpService.getWxCpConfigStorage().getApiUrl(FILE_SHARE); + JsonObject jsonObject = new JsonObject(); + jsonObject.addProperty("fileid", fileId); + String responseContent = this.cpService.post(apiUrl, jsonObject.toString()); + return WxCpFileShare.fromJson(responseContent); + } + + @Override + public WxCpFileInfo fileInfo(@NonNull String fileId) throws WxErrorException { + String apiUrl = this.cpService.getWxCpConfigStorage().getApiUrl(FILE_INFO); + JsonObject jsonObject = new JsonObject(); + jsonObject.addProperty("fileid", fileId); + String responseContent = this.cpService.post(apiUrl, jsonObject.toString()); + return WxCpFileInfo.fromJson(responseContent); + } + +} diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpSchoolHealthServiceImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpSchoolHealthServiceImpl.java new file mode 100644 index 0000000000..60f379da81 --- /dev/null +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpSchoolHealthServiceImpl.java @@ -0,0 +1,76 @@ +package me.chanjar.weixin.cp.api.impl; + +import com.google.gson.JsonObject; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.cp.api.WxCpSchoolHealthService; +import me.chanjar.weixin.cp.api.WxCpService; +import me.chanjar.weixin.cp.bean.school.health.WxCpGetHealthReportStat; +import me.chanjar.weixin.cp.bean.school.health.WxCpGetReportAnswer; +import me.chanjar.weixin.cp.bean.school.health.WxCpGetReportJobIds; +import me.chanjar.weixin.cp.bean.school.health.WxCpGetReportJobInfo; + +import java.util.Optional; + +import static me.chanjar.weixin.cp.constant.WxCpApiPathConsts.School.*; + +/** + * 企业微信家校应用 健康上报接口实现类. + * + * @author Wang_Wong created on : 2022/5/31 9:16 + */ +@Slf4j +@RequiredArgsConstructor +public class WxCpSchoolHealthServiceImpl implements WxCpSchoolHealthService { + + private final WxCpService cpService; + + @Override + public WxCpGetHealthReportStat getHealthReportStat(@NonNull String date) throws WxErrorException { + String apiUrl = this.cpService.getWxCpConfigStorage().getApiUrl(GET_HEALTH_REPORT_STAT); + JsonObject jsonObject = new JsonObject(); + jsonObject.addProperty("date", date); + String responseContent = this.cpService.post(apiUrl, jsonObject.toString()); + return WxCpGetHealthReportStat.fromJson(responseContent); + } + + @Override + public WxCpGetReportJobIds getReportJobIds(Integer offset, Integer limit) throws WxErrorException { + String apiUrl = this.cpService.getWxCpConfigStorage().getApiUrl(GET_REPORT_JOBIDS); + JsonObject jsonObject = new JsonObject(); + jsonObject.addProperty("offset", Optional.ofNullable(offset).orElse(0)); + jsonObject.addProperty("limit", Optional.ofNullable(limit).orElse(100)); + String responseContent = this.cpService.post(apiUrl, jsonObject.toString()); + return WxCpGetReportJobIds.fromJson(responseContent); + } + + @Override + public WxCpGetReportJobInfo getReportJobInfo(@NonNull String jobId, @NonNull String date) throws WxErrorException { + String apiUrl = this.cpService.getWxCpConfigStorage().getApiUrl(GET_REPORT_JOB_INFO); + JsonObject jsonObject = new JsonObject(); + jsonObject.addProperty("jobid", jobId); + jsonObject.addProperty("date", date); + String responseContent = this.cpService.post(apiUrl, jsonObject.toString()); + return WxCpGetReportJobInfo.fromJson(responseContent); + } + + @Override + public WxCpGetReportAnswer getReportAnswer(@NonNull String jobId, @NonNull String date, Integer offset, + Integer limit) throws WxErrorException { + String apiUrl = this.cpService.getWxCpConfigStorage().getApiUrl(GET_REPORT_ANSWER); + JsonObject jsonObject = new JsonObject(); + jsonObject.addProperty("jobid", jobId); + jsonObject.addProperty("date", date); + if (offset != null) { + jsonObject.addProperty("offset", offset); + } + if (limit != null) { + jsonObject.addProperty("limit", limit); + } + String responseContent = this.cpService.post(apiUrl, jsonObject.toString()); + return WxCpGetReportAnswer.fromJson(responseContent); + } + +} diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpSchoolServiceImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpSchoolServiceImpl.java new file mode 100644 index 0000000000..c503aebdfd --- /dev/null +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpSchoolServiceImpl.java @@ -0,0 +1,126 @@ +package me.chanjar.weixin.cp.api.impl; + +import com.google.gson.JsonObject; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.cp.api.WxCpSchoolService; +import me.chanjar.weixin.cp.api.WxCpService; +import me.chanjar.weixin.cp.bean.living.WxCpLivingResult; +import me.chanjar.weixin.cp.bean.school.*; +import org.apache.commons.lang3.StringUtils; + +import java.util.List; +import java.util.Optional; + +import static me.chanjar.weixin.cp.constant.WxCpApiPathConsts.School.*; + +/** + * 企业微信家校应用 复学码相关接口实现类. + * https://developer.work.weixin.qq.com/document/path/93744 + * + * @author Wang_Wong created on : 2022/6/1 14:05 + */ +@Slf4j +@RequiredArgsConstructor +public class WxCpSchoolServiceImpl implements WxCpSchoolService { + + private final WxCpService cpService; + + @Override + public WxCpCustomizeHealthInfo getTeacherCustomizeHealthInfo(String date, String nextKey, Integer limit) throws WxErrorException { + String apiUrl = this.cpService.getWxCpConfigStorage().getApiUrl(GET_TEACHER_CUSTOMIZE_HEALTH_INFO); + JsonObject jsonObject = new JsonObject(); + jsonObject.addProperty("date", date); + jsonObject.addProperty("limit", Optional.ofNullable(limit).orElse(100)); + if (nextKey != null) { + jsonObject.addProperty("next_key", nextKey); + } + String responseContent = this.cpService.post(apiUrl, jsonObject.toString()); + return WxCpCustomizeHealthInfo.fromJson(responseContent); + } + + @Override + public WxCpCustomizeHealthInfo getStudentCustomizeHealthInfo(String date, String nextKey, Integer limit) throws WxErrorException { + String apiUrl = this.cpService.getWxCpConfigStorage().getApiUrl(GET_STUDENT_CUSTOMIZE_HEALTH_INFO); + JsonObject jsonObject = new JsonObject(); + jsonObject.addProperty("date", date); + jsonObject.addProperty("limit", Optional.ofNullable(limit).orElse(100)); + if (nextKey != null) { + jsonObject.addProperty("next_key", nextKey); + } + String responseContent = this.cpService.post(apiUrl, jsonObject.toString()); + return WxCpCustomizeHealthInfo.fromJson(responseContent); + } + + @Override + public WxCpResultList getHealthQrCode(List userIds, Integer type) throws WxErrorException { + String apiUrl = this.cpService.getWxCpConfigStorage().getApiUrl(GET_HEALTH_QRCODE); + JsonObject jsonObject = new JsonObject(); + jsonObject.addProperty("type", type); + jsonObject.addProperty("userids", userIds.toString()); + String responseContent = this.cpService.post(apiUrl, jsonObject.toString()); + return WxCpResultList.fromJson(responseContent); + } + + @Override + public WxCpPaymentResult getPaymentResult(String paymentId) throws WxErrorException { + String apiUrl = this.cpService.getWxCpConfigStorage().getApiUrl(GET_PAYMENT_RESULT); + JsonObject jsonObject = new JsonObject(); + jsonObject.addProperty("payment_id", paymentId); + String responseContent = this.cpService.post(apiUrl, jsonObject.toString()); + return WxCpPaymentResult.fromJson(responseContent); + } + + @Override + public WxCpTrade getTrade(String paymentId, String tradeNo) throws WxErrorException { + String apiUrl = this.cpService.getWxCpConfigStorage().getApiUrl(GET_TRADE); + JsonObject jsonObject = new JsonObject(); + jsonObject.addProperty("payment_id", paymentId); + jsonObject.addProperty("trade_no", tradeNo); + String responseContent = this.cpService.post(apiUrl, jsonObject.toString()); + return WxCpTrade.fromJson(responseContent); + } + + @Override + public WxCpSchoolLivingInfo getLivingInfo(String livingId) throws WxErrorException { + String apiUrl = this.cpService.getWxCpConfigStorage().getApiUrl(GET_LIVING_INFO) + livingId; + String responseContent = this.cpService.get(apiUrl, null); + return WxCpSchoolLivingInfo.fromJson(responseContent); + } + + @Override + public WxCpLivingResult.LivingIdResult getUserAllLivingId(String userId, String cursor, Integer limit) throws WxErrorException { + return this.cpService.getLivingService().getUserAllLivingId(userId, cursor, limit); + } + + @Override + public WxCpSchoolWatchStat getWatchStat(String livingId, String nextKey) throws WxErrorException { + String apiUrl = this.cpService.getWxCpConfigStorage().getApiUrl(GET_WATCH_STAT); + JsonObject jsonObject = new JsonObject(); + if (StringUtils.isNotBlank(nextKey)) { + jsonObject.addProperty("next_key", nextKey); + } + jsonObject.addProperty("livingid", livingId); + String responseContent = this.cpService.post(apiUrl, jsonObject.toString()); + return WxCpSchoolWatchStat.fromJson(responseContent); + } + + @Override + public WxCpSchoolUnwatchStat getUnwatchStat(String livingId, String nextKey) throws WxErrorException { + String apiUrl = this.cpService.getWxCpConfigStorage().getApiUrl(GET_UNWATCH_STAT); + JsonObject jsonObject = new JsonObject(); + if (StringUtils.isNotBlank(nextKey)) { + jsonObject.addProperty("next_key", nextKey); + } + jsonObject.addProperty("livingid", livingId); + String responseContent = this.cpService.post(apiUrl, jsonObject.toString()); + return WxCpSchoolUnwatchStat.fromJson(responseContent); + } + + @Override + public WxCpLivingResult deleteReplayData(String livingId) throws WxErrorException { + return cpService.getLivingService().deleteReplayData(livingId); + } + +} 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 new file mode 100644 index 0000000000..bdb067f923 --- /dev/null +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpSchoolUserServiceImpl.java @@ -0,0 +1,276 @@ +package me.chanjar.weixin.cp.api.impl; + +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; +import com.google.gson.JsonPrimitive; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.common.util.json.GsonParser; +import me.chanjar.weixin.cp.api.WxCpSchoolUserService; +import me.chanjar.weixin.cp.api.WxCpService; +import me.chanjar.weixin.cp.bean.WxCpBaseResp; +import me.chanjar.weixin.cp.bean.WxCpOauth2UserInfo; +import me.chanjar.weixin.cp.bean.school.user.*; +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.*; + +/** + * 企业微信家校沟通相关接口. + * https://developer.work.weixin.qq.com/document/path/91638 + * + * @author Wang_Wong created on : 2022/6/18 9:10 + */ +@Slf4j +@RequiredArgsConstructor +public class WxCpSchoolUserServiceImpl implements WxCpSchoolUserService { + + private final WxCpService cpService; + + @Override + public WxCpOauth2UserInfo getUserInfo(@NonNull String code) throws WxErrorException { + return cpService.getOauth2Service().getUserInfo(code); + } + + @Override + public WxCpOauth2UserInfo getSchoolUserInfo(@NonNull String code) throws WxErrorException { + return cpService.getOauth2Service().getSchoolUserInfo(code); + } + + @Override + public WxCpBaseResp createStudent(@NonNull String studentUserId, @NonNull String name, + @NonNull List departments) throws WxErrorException { + String apiUrl = this.cpService.getWxCpConfigStorage().getApiUrl(CREATE_STUDENT); + JsonObject jsonObject = new JsonObject(); + jsonObject.addProperty("student_userid", studentUserId); + jsonObject.addProperty("name", name); + JsonArray jsonArray = new JsonArray(); + for (Integer depart : departments) { + jsonArray.add(new JsonPrimitive(depart)); + } + jsonObject.add("department", jsonArray); + String responseContent = this.cpService.post(apiUrl, jsonObject.toString()); + return WxCpBaseResp.fromJson(responseContent); + } + + @Override + public WxCpBatchResultList batchCreateStudent(@NonNull WxCpBatchCreateStudentRequest request) throws WxErrorException { + String apiUrl = this.cpService.getWxCpConfigStorage().getApiUrl(BATCH_CREATE_STUDENT); + String responseContent = this.cpService.post(apiUrl, request.toJson()); + return WxCpBatchResultList.fromJson(responseContent); + } + + @Override + public WxCpBatchResultList batchDeleteStudent(@NonNull WxCpBatchDeleteStudentRequest request) throws WxErrorException { + String apiUrl = this.cpService.getWxCpConfigStorage().getApiUrl(BATCH_DELETE_STUDENT); + String responseContent = this.cpService.post(apiUrl, request.toJson()); + return WxCpBatchResultList.fromJson(responseContent); + } + + @Override + public WxCpBatchResultList batchUpdateStudent(@NonNull WxCpBatchUpdateStudentRequest request) throws WxErrorException { + String apiUrl = this.cpService.getWxCpConfigStorage().getApiUrl(BATCH_UPDATE_STUDENT); + String responseContent = this.cpService.post(apiUrl, request.toJson()); + return WxCpBatchResultList.fromJson(responseContent); + } + + @Override + public WxCpBaseResp deleteStudent(@NonNull String studentUserId) throws WxErrorException { + String apiUrl = this.cpService.getWxCpConfigStorage().getApiUrl(DELETE_STUDENT) + studentUserId; + String responseContent = this.cpService.get(apiUrl, null); + return WxCpBaseResp.fromJson(responseContent); + } + + @Override + public WxCpBaseResp updateStudent(@NonNull String studentUserId, String newStudentUserId, String name, + List departments) throws WxErrorException { + String apiUrl = this.cpService.getWxCpConfigStorage().getApiUrl(UPDATE_STUDENT); + JsonObject jsonObject = new JsonObject(); + jsonObject.addProperty("student_userid", studentUserId); + if (StringUtils.isNotEmpty(newStudentUserId)) { + jsonObject.addProperty("new_student_userid", newStudentUserId); + } + if (StringUtils.isNotEmpty(name)) { + jsonObject.addProperty("name", name); + } + if (departments != null && !departments.isEmpty()) { + JsonArray jsonArray = new JsonArray(); + for (Integer depart : departments) { + jsonArray.add(new JsonPrimitive(depart)); + } + jsonObject.add("department", jsonArray); + } + String responseContent = this.cpService.post(apiUrl, jsonObject.toString()); + return WxCpBaseResp.fromJson(responseContent); + } + + @Override + public WxCpBaseResp createParent(@NonNull WxCpCreateParentRequest request) throws WxErrorException { + String apiUrl = this.cpService.getWxCpConfigStorage().getApiUrl(CREATE_PARENT); + String responseContent = this.cpService.post(apiUrl, request.toJson()); + return WxCpBaseResp.fromJson(responseContent); + } + + @Override + public WxCpBatchResultList batchCreateParent(@NonNull WxCpBatchCreateParentRequest request) throws WxErrorException { + String apiUrl = this.cpService.getWxCpConfigStorage().getApiUrl(BATCH_CREATE_PARENT); + String responseContent = this.cpService.post(apiUrl, request.toJson()); + return WxCpBatchResultList.fromJson(responseContent); + } + + @Override + public WxCpBatchResultList batchDeleteParent(@NonNull String... userIdList) throws WxErrorException { + String apiUrl = this.cpService.getWxCpConfigStorage().getApiUrl(BATCH_DELETE_PARENT); + JsonObject jsonObject = new JsonObject(); + JsonArray jsonArray = new JsonArray(); + for (String userId : userIdList) { + jsonArray.add(new JsonPrimitive(userId)); + } + jsonObject.add("useridlist", jsonArray); + String responseContent = this.cpService.post(apiUrl, jsonObject.toString()); + return WxCpBatchResultList.fromJson(responseContent); + } + + @Override + public WxCpBatchResultList batchUpdateParent(@NonNull WxCpBatchUpdateParentRequest request) throws WxErrorException { + String apiUrl = this.cpService.getWxCpConfigStorage().getApiUrl(BATCH_UPDATE_PARENT); + String responseContent = this.cpService.post(apiUrl, request.toJson()); + return WxCpBatchResultList.fromJson(responseContent); + } + + @Override + public WxCpUserResult getUser(@NonNull String userId) throws WxErrorException { + String apiUrl = this.cpService.getWxCpConfigStorage().getApiUrl(GET_USER) + userId; + String responseContent = this.cpService.get(apiUrl, null); + return WxCpUserResult.fromJson(responseContent); + } + + @Override + public WxCpUserListResult getUserList(@NonNull Integer departmentId, Integer fetchChild) throws WxErrorException { + String apiUrl = String.format(this.cpService.getWxCpConfigStorage().getApiUrl(GET_USER_LIST), departmentId, + fetchChild); + String responseContent = this.cpService.get(apiUrl, null); + return WxCpUserListResult.fromJson(responseContent); + } + + @Override + public WxCpListParentResult getUserListParent(@NonNull Integer departmentId) throws WxErrorException { + String apiUrl = this.cpService.getWxCpConfigStorage().getApiUrl(GET_USER_LIST_PARENT) + departmentId; + String responseContent = this.cpService.get(apiUrl, null); + return WxCpListParentResult.fromJson(responseContent); + } + + @Override + public WxCpBaseResp updateParent(@NonNull WxCpUpdateParentRequest request) throws WxErrorException { + String apiUrl = this.cpService.getWxCpConfigStorage().getApiUrl(UPDATE_PARENT); + String responseContent = this.cpService.post(apiUrl, request.toJson()); + return WxCpBaseResp.fromJson(responseContent); + } + + @Override + public WxCpBaseResp deleteParent(@NonNull String userId) throws WxErrorException { + String apiUrl = this.cpService.getWxCpConfigStorage().getApiUrl(DELETE_PARENT) + userId; + String responseContent = this.cpService.get(apiUrl, null); + return WxCpBaseResp.fromJson(responseContent); + } + + @Override + public WxCpBaseResp setArchSyncMode(@NonNull Integer archSyncMode) throws WxErrorException { + String apiUrl = this.cpService.getWxCpConfigStorage().getApiUrl(SET_ARCH_SYNC_MODE); + JsonObject jsonObject = new JsonObject(); + jsonObject.addProperty("arch_sync_mode", archSyncMode); + String responseContent = this.cpService.post(apiUrl, jsonObject.toString()); + return WxCpBaseResp.fromJson(responseContent); + } + + @Override + public WxCpCreateDepartment createDepartment(@NonNull WxCpCreateDepartmentRequest request) throws WxErrorException { + String apiUrl = this.cpService.getWxCpConfigStorage().getApiUrl(DEPARTMENT_CREATE); + String responseContent = this.cpService.post(apiUrl, request.toJson()); + return WxCpCreateDepartment.fromJson(responseContent); + } + + @Override + public WxCpBaseResp updateDepartment(@NonNull WxCpUpdateDepartmentRequest request) throws WxErrorException { + String apiUrl = this.cpService.getWxCpConfigStorage().getApiUrl(DEPARTMENT_UPDATE); + String responseContent = this.cpService.post(apiUrl, request.toJson()); + return WxCpBaseResp.fromJson(responseContent); + } + + @Override + public WxCpBaseResp deleteDepartment(Integer id) throws WxErrorException { + String apiUrl = this.cpService.getWxCpConfigStorage().getApiUrl(DEPARTMENT_DELETE) + id; + String responseContent = this.cpService.get(apiUrl, null); + return WxCpBaseResp.fromJson(responseContent); + } + + @Override + public WxCpBaseResp setSubscribeMode(@NonNull Integer subscribeMode) throws WxErrorException { + String apiUrl = this.cpService.getWxCpConfigStorage().getApiUrl(SET_SUBSCRIBE_MODE); + JsonObject jsonObject = new JsonObject(); + jsonObject.addProperty("subscribe_mode", subscribeMode); + String responseContent = this.cpService.post(apiUrl, jsonObject.toString()); + return WxCpBaseResp.fromJson(responseContent); + } + + @Override + public Integer getSubscribeMode() throws WxErrorException { + String apiUrl = this.cpService.getWxCpConfigStorage().getApiUrl(GET_SUBSCRIBE_MODE); + String responseContent = this.cpService.get(apiUrl, null); + return GsonParser.parse(responseContent).get("subscribe_mode").getAsInt(); + } + + @Override + public WxCpExternalContact getExternalContact(@NonNull String externalUserId) throws WxErrorException { + String apiUrl = this.cpService.getWxCpConfigStorage().getApiUrl(EXTERNAL_CONTACT_GET) + externalUserId; + String responseContent = this.cpService.get(apiUrl, null); + return WxCpExternalContact.fromJson(responseContent); + } + + @Override + public WxCpAllowScope getAllowScope(@NonNull Integer agentId) throws WxErrorException { + String apiUrl = this.cpService.getWxCpConfigStorage().getApiUrl(GET_ALLOW_SCOPE) + agentId; + String responseContent = this.cpService.get(apiUrl, null); + return WxCpAllowScope.fromJson(responseContent); + } + + @Override + public String convertToOpenId(@NonNull String externalUserId) throws WxErrorException { + return cpService.getExternalContactService().convertToOpenid(externalUserId); + } + + @Override + public WxCpDepartmentList listDepartment(Integer id) throws WxErrorException { + 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); + } + + @Override + public WxCpSubscribeQrCode getSubscribeQrCode() throws WxErrorException { + String apiUrl = this.cpService.getWxCpConfigStorage().getApiUrl(GET_SUBSCRIBE_QR_CODE); + String responseContent = this.cpService.get(apiUrl, null); + return WxCpSubscribeQrCode.fromJson(responseContent); + } + + @Override + public WxCpSetUpgradeInfo setUpgradeInfo(Long upgradeTime, Integer upgradeSwitch) throws WxErrorException { + String apiUrl = this.cpService.getWxCpConfigStorage().getApiUrl(SET_UPGRADE_INFO); + JsonObject jsonObject = new JsonObject(); + if (upgradeTime != null) { + jsonObject.addProperty("upgrade_time", upgradeTime); + } + if (upgradeSwitch != null) { + jsonObject.addProperty("upgrade_switch", upgradeSwitch); + } + String responseContent = this.cpService.post(apiUrl, jsonObject.toString()); + return WxCpSetUpgradeInfo.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 d181af6749..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,25 +1,26 @@ package me.chanjar.weixin.cp.api.impl; - -import me.chanjar.weixin.common.WxType; 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.HttpType; +import me.chanjar.weixin.common.error.WxRuntimeException; +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; /** + * The type Wx cp service apache http client. + * * @author someone */ public class WxCpServiceApacheHttpClientImpl extends BaseWxCpServiceImpl { @@ -37,8 +38,8 @@ public HttpHost getRequestHttpProxy() { } @Override - public HttpType getRequestType() { - return HttpType.APACHE_HTTP; + public HttpClientType getRequestType() { + return HttpClientType.APACHE_HTTP; } @Override @@ -48,7 +49,8 @@ public String getAccessToken(boolean forceRefresh) throws WxErrorException { } synchronized (this.globalAccessTokenRefreshLock) { - String url = String.format(this.configStorage.getApiUrl(WxCpApiPathConsts.GET_TOKEN), this.configStorage.getCorpId(), this.configStorage.getCorpSecret()); + String url = String.format(this.configStorage.getApiUrl(WxCpApiPathConsts.GET_TOKEN), + this.configStorage.getCorpId(), this.configStorage.getCorpSecret()); try { HttpGet httpGet = new HttpGet(url); @@ -57,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); @@ -72,7 +68,7 @@ public String getAccessToken(boolean forceRefresh) throws WxErrorException { WxAccessToken accessToken = WxAccessToken.fromJson(resultContent); this.configStorage.updateAccessToken(accessToken.getAccessToken(), accessToken.getExpiresIn()); } catch (IOException e) { - throw new RuntimeException(e); + throw new WxRuntimeException(e); } } return this.configStorage.getAccessToken(); 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..4b6a1e36ff --- /dev/null +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpServiceHttpComponentsImpl.java @@ -0,0 +1,100 @@ +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() == null ? null : + this.configStorage.getHttpProxyPassword().toCharArray()); + + if (this.configStorage.getHttpProxyHost() != null && this.configStorage.getHttpProxyPort() > 0) { + this.httpProxy = new HttpHost(this.configStorage.getHttpProxyHost(), this.configStorage.getHttpProxyPort()); + } + + this.httpClient = apacheHttpClientBuilder.build(); + } + + @Override + public 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 a7538c6ead..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 @@ -1,12 +1,121 @@ package me.chanjar.weixin.cp.api.impl; +import com.google.gson.JsonObject; +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.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.HttpGet; + +import java.io.IOException; +import java.util.concurrent.locks.Lock; + +import static me.chanjar.weixin.cp.constant.WxCpApiPathConsts.GET_AGENT_CONFIG_TICKET; +import static me.chanjar.weixin.cp.constant.WxCpApiPathConsts.GET_JSAPI_TICKET; + /** *

  *  默认接口实现类,使用apache httpclient实现
  * Created by Binary Wang on 2017-5-27.
  * 
+ *
+ * 增加分布式锁(基于WxCpConfigStorage实现)的支持
+ * Updated by yuanqixun on 2020-05-13
+ * 
* * @author Binary Wang */ public class WxCpServiceImpl extends WxCpServiceApacheHttpClientImpl { + @Override + public String getAccessToken(boolean forceRefresh) throws WxErrorException { + final WxCpConfigStorage configStorage = getWxCpConfigStorage(); + if (!configStorage.isAccessTokenExpired() && !forceRefresh) { + return configStorage.getAccessToken(); + } + Lock lock = configStorage.getAccessTokenLock(); + lock.lock(); + try { + // 拿到锁之后,再次判断一下最新的token是否过期,避免重刷 + if (!configStorage.isAccessTokenExpired() && !forceRefresh) { + return configStorage.getAccessToken(); + } + String url = String.format(configStorage.getApiUrl(WxCpApiPathConsts.GET_TOKEN), + this.configStorage.getCorpId(), this.configStorage.getCorpSecret()); + try { + HttpGet httpGet = new HttpGet(url); + if (getRequestHttpProxy() != null) { + RequestConfig config = RequestConfig.custom().setProxy(getRequestHttpProxy()).build(); + httpGet.setConfig(config); + } + String resultContent = getRequestHttpClient().execute(httpGet, ApacheBasicResponseHandler.INSTANCE); + WxError error = WxError.fromJson(resultContent, WxType.CP); + if (error.getErrorCode() != 0) { + throw new WxErrorException(error); + } + + WxAccessToken accessToken = WxAccessToken.fromJson(resultContent); + configStorage.updateAccessToken(accessToken.getAccessToken(), accessToken.getExpiresIn()); + } catch (IOException e) { + throw new WxRuntimeException(e); + } + } finally { + lock.unlock(); + } + return configStorage.getAccessToken(); + } + + @Override + public String getAgentJsapiTicket(boolean forceRefresh) throws WxErrorException { + final WxCpConfigStorage configStorage = getWxCpConfigStorage(); + if (forceRefresh) { + configStorage.expireAgentJsapiTicket(); + } + if (configStorage.isAgentJsapiTicketExpired()) { + Lock lock = configStorage.getAgentJsapiTicketLock(); + lock.lock(); + try { + // 拿到锁之后,再次判断一下最新的token是否过期,避免重刷 + if (configStorage.isAgentJsapiTicketExpired()) { + String responseContent = this.get(configStorage.getApiUrl(GET_AGENT_CONFIG_TICKET), null); + JsonObject jsonObject = GsonParser.parse(responseContent); + configStorage.updateAgentJsapiTicket(jsonObject.get("ticket").getAsString(), + jsonObject.get("expires_in").getAsInt()); + } + } finally { + lock.unlock(); + } + } + return configStorage.getAgentJsapiTicket(); + } + + @Override + public String getJsapiTicket(boolean forceRefresh) throws WxErrorException { + final WxCpConfigStorage configStorage = getWxCpConfigStorage(); + if (forceRefresh) { + configStorage.expireJsapiTicket(); + } + + if (configStorage.isJsapiTicketExpired()) { + Lock lock = configStorage.getJsapiTicketLock(); + lock.lock(); + try { + // 拿到锁之后,再次判断一下最新的token是否过期,避免重刷 + if (configStorage.isJsapiTicketExpired()) { + String responseContent = this.get(configStorage.getApiUrl(GET_JSAPI_TICKET), null); + JsonObject tmpJsonObject = GsonParser.parse(responseContent); + configStorage.updateJsapiTicket(tmpJsonObject.get("ticket").getAsString(), + tmpJsonObject.get("expires_in").getAsInt()); + } + } finally { + lock.unlock(); + } + } + return configStorage.getJsapiTicket(); + } } 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 cc76e9cf4c..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 @@ -3,17 +3,19 @@ import jodd.http.HttpConnectionProvider; import jodd.http.HttpRequest; import jodd.http.HttpResponse; -import jodd.http.JoddHttp; import jodd.http.ProxyInfo; -import me.chanjar.weixin.common.WxType; +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.HttpType; +import me.chanjar.weixin.common.util.http.HttpClientType; 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 WxCpServiceJoddHttpImpl extends BaseWxCpServiceImpl { @@ -31,8 +33,8 @@ public ProxyInfo getRequestHttpProxy() { } @Override - public HttpType getRequestType() { - return HttpType.JODD_HTTP; + public HttpClientType getRequestType() { + return HttpClientType.JODD_HTTP; } @Override @@ -68,7 +70,7 @@ public void initHttp() { configStorage.getHttpProxyPort(), configStorage.getHttpProxyUsername(), configStorage.getHttpProxyPassword()); } - httpClient = JoddHttp.httpConnectionProvider; + httpClient = new SocketHttpConnectionProvider(); } @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 596dc0608e..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 @@ -1,11 +1,12 @@ package me.chanjar.weixin.cp.api.impl; import lombok.extern.slf4j.Slf4j; -import me.chanjar.weixin.common.WxType; 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.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; import okhttp3.*; @@ -15,6 +16,8 @@ import static me.chanjar.weixin.cp.constant.WxCpApiPathConsts.GET_TOKEN; /** + * The type Wx cp service ok http. + * * @author someone */ @Slf4j @@ -33,8 +36,8 @@ public OkHttpProxyInfo getRequestHttpProxy() { } @Override - public HttpType getRequestType() { - return HttpType.OK_HTTP; + public HttpClientType getRequestType() { + return HttpClientType.OK_HTTP; } @Override @@ -48,7 +51,8 @@ public String getAccessToken(boolean forceRefresh) throws WxErrorException { OkHttpClient client = getRequestHttpClient(); //请求的request Request request = new Request.Builder() - .url(String.format(this.configStorage.getApiUrl(GET_TOKEN), this.configStorage.getCorpId(), this.configStorage.getCorpSecret())) + .url(String.format(this.configStorage.getApiUrl(GET_TOKEN), this.configStorage.getCorpId(), + this.configStorage.getCorpSecret())) .get() .build(); String resultContent = null; @@ -79,24 +83,22 @@ public void initHttp() { configStorage.getHttpProxyPort(), configStorage.getHttpProxyUsername(), configStorage.getHttpProxyPassword()); - } - - OkHttpClient.Builder clientBuilder = new OkHttpClient.Builder(); - if (httpProxy != null) { + 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(); } }); + httpClient = clientBuilder.build(); + } else { + httpClient = DefaultOkHttpClientBuilder.get().build(); } - httpClient = clientBuilder.build(); } @Override diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpServiceOnTpImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpServiceOnTpImpl.java index 35eab626a7..207681c7ae 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpServiceOnTpImpl.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpServiceOnTpImpl.java @@ -3,7 +3,7 @@ import lombok.RequiredArgsConstructor; import me.chanjar.weixin.common.bean.WxAccessToken; import me.chanjar.weixin.common.error.WxErrorException; -import me.chanjar.weixin.cp.api.WxCpTpService; +import me.chanjar.weixin.cp.tp.service.WxCpTpService; /** *
@@ -24,7 +24,8 @@ public String getAccessToken(boolean forceRefresh) throws WxErrorException {
     }
     //access token通过第三方应用service获取
     //corpSecret对应企业永久授权码
-    WxAccessToken accessToken = wxCpTpService.getCorpToken(this.configStorage.getCorpId(), this.configStorage.getCorpSecret());
+    WxAccessToken accessToken = wxCpTpService.getCorpToken(this.configStorage.getCorpId(),
+      this.configStorage.getCorpSecret());
 
     this.configStorage.updateAccessToken(accessToken.getAccessToken(), accessToken.getExpiresIn());
     return this.configStorage.getAccessToken();
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpTagServiceImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpTagServiceImpl.java
index 753f2493a1..e73ef98a98 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpTagServiceImpl.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpTagServiceImpl.java
@@ -1,9 +1,12 @@
 package me.chanjar.weixin.cp.api.impl;
 
-import com.google.gson.*;
+import com.google.gson.JsonArray;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonPrimitive;
 import com.google.gson.reflect.TypeToken;
 import lombok.RequiredArgsConstructor;
 import me.chanjar.weixin.common.error.WxErrorException;
+import me.chanjar.weixin.common.util.json.GsonParser;
 import me.chanjar.weixin.cp.api.WxCpService;
 import me.chanjar.weixin.cp.api.WxCpTagService;
 import me.chanjar.weixin.cp.bean.WxCpTag;
@@ -39,18 +42,11 @@ public String create(String name, Integer id) throws WxErrorException {
     return this.create(o);
   }
 
-  @Override
-  public String create(String tagName) throws WxErrorException {
-    JsonObject o = new JsonObject();
-    o.addProperty("tagname", tagName);
-    return this.create(o);
-  }
-
   private String create(JsonObject param) throws WxErrorException {
     String url = this.mainService.getWxCpConfigStorage().getApiUrl(TAG_CREATE);
     String responseContent = this.mainService.post(url, param.toString());
-    JsonElement tmpJsonElement = new JsonParser().parse(responseContent);
-    return tmpJsonElement.getAsJsonObject().get("tagid").getAsString();
+    JsonObject jsonObject = GsonParser.parse(responseContent);
+    return jsonObject.get("tagid").getAsString();
   }
 
   @Override
@@ -72,10 +68,10 @@ public void delete(String tagId) throws WxErrorException {
   public List listAll() throws WxErrorException {
     String url = this.mainService.getWxCpConfigStorage().getApiUrl(TAG_LIST);
     String responseContent = this.mainService.get(url, null);
-    JsonElement tmpJsonElement = new JsonParser().parse(responseContent);
+    JsonObject tmpJson = GsonParser.parse(responseContent);
     return WxCpGsonBuilder.create()
       .fromJson(
-        tmpJsonElement.getAsJsonObject().get("taglist"),
+        tmpJson.get("taglist"),
         new TypeToken>() {
         }.getType()
       );
@@ -85,10 +81,10 @@ public List listAll() throws WxErrorException {
   public List listUsersByTagId(String tagId) throws WxErrorException {
     String url = String.format(this.mainService.getWxCpConfigStorage().getApiUrl(TAG_GET), tagId);
     String responseContent = this.mainService.get(url, null);
-    JsonElement tmpJsonElement = new JsonParser().parse(responseContent);
+    JsonObject tmpJson = GsonParser.parse(responseContent);
     return WxCpGsonBuilder.create()
       .fromJson(
-        tmpJsonElement.getAsJsonObject().get("userlist"),
+        tmpJson.get("userlist"),
         new TypeToken>() {
         }.getType()
       );
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpTaskCardServiceImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpTaskCardServiceImpl.java
index 97530a6e9d..8469451428 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpTaskCardServiceImpl.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpTaskCardServiceImpl.java
@@ -5,13 +5,14 @@
 import me.chanjar.weixin.common.util.json.WxGsonBuilder;
 import me.chanjar.weixin.cp.api.WxCpService;
 import me.chanjar.weixin.cp.api.WxCpTaskCardService;
-import me.chanjar.weixin.cp.constant.WxCpApiPathConsts;
+import me.chanjar.weixin.cp.bean.message.TemplateCardMessage;
 
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 
-import static me.chanjar.weixin.cp.constant.WxCpApiPathConsts.TaskCard.*;
+import static me.chanjar.weixin.cp.constant.WxCpApiPathConsts.TaskCard.UPDATE_TASK_CARD;
+import static me.chanjar.weixin.cp.constant.WxCpApiPathConsts.TaskCard.UPDATE_TEMPLATE_CARD;
 
 /**
  * 
@@ -19,24 +20,51 @@
  *  Created by Jeff on 2019-05-16.
  * 
* - * @author Jeff - * @date 2019-05-16 + * @author Jeff created on 2019-05-16 */ @RequiredArgsConstructor public class WxCpTaskCardServiceImpl implements WxCpTaskCardService { private final WxCpService mainService; @Override - public void update(List userIds, String taskId, String clickedKey) throws WxErrorException { + public void update(List userIds, String taskId, String replaceName) throws WxErrorException { Integer agentId = this.mainService.getWxCpConfigStorage().getAgentId(); Map data = new HashMap<>(4); data.put("userids", userIds); data.put("agentid", agentId); data.put("task_id", taskId); - data.put("clicked_key", clickedKey); + // 文档地址:https://open.work.weixin.qq.com/wwopen/devtool/interface?doc_id=16386 + data.put("clicked_key", replaceName); String url = this.mainService.getWxCpConfigStorage().getApiUrl(UPDATE_TASK_CARD); this.mainService.post(url, WxGsonBuilder.create().toJson(data)); } + + @Override + public void updateTemplateCardButton(List userIds, List partyIds, + List tagIds, Integer atAll, + String responseCode, String replaceName) throws WxErrorException { + Integer agentId = this.mainService.getWxCpConfigStorage().getAgentId(); + Map data = new HashMap<>(7); + data.put("userids", userIds); + data.put("partyids", partyIds); + data.put("tagids", tagIds); + data.put("atall", atAll); + data.put("agentid", agentId); + data.put("response_code", responseCode); + Map btnMap = new HashMap<>(); + btnMap.put("replace_name", replaceName); + data.put("button", btnMap); + + String url = this.mainService.getWxCpConfigStorage().getApiUrl(UPDATE_TEMPLATE_CARD); + this.mainService.post(url, WxGsonBuilder.create().toJson(data)); + + } + + @Override + public void updateTemplateCardButton(TemplateCardMessage templateCardMessage) throws WxErrorException { + String url = this.mainService.getWxCpConfigStorage().getApiUrl(UPDATE_TEMPLATE_CARD); + this.mainService.post(url, templateCardMessage.toJson()); + } } diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpUserServiceImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpUserServiceImpl.java index b9844d39b9..f1556d4e31 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpUserServiceImpl.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpUserServiceImpl.java @@ -1,17 +1,27 @@ package me.chanjar.weixin.cp.api.impl; import com.google.common.collect.Maps; -import com.google.gson.*; +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; +import com.google.gson.JsonPrimitive; import com.google.gson.reflect.TypeToken; import lombok.RequiredArgsConstructor; import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.common.util.json.GsonParser; import me.chanjar.weixin.cp.api.WxCpService; import me.chanjar.weixin.cp.api.WxCpUserService; import me.chanjar.weixin.cp.bean.WxCpInviteResult; +import me.chanjar.weixin.cp.bean.WxCpOpenUseridToUseridResult; import me.chanjar.weixin.cp.bean.WxCpUser; -import me.chanjar.weixin.cp.bean.WxCpUserExternalContactInfo; +import me.chanjar.weixin.cp.bean.WxCpUseridToOpenUseridResult; +import me.chanjar.weixin.cp.bean.external.contact.WxCpExternalContactInfo; +import me.chanjar.weixin.cp.bean.user.WxCpDeptUserResult; import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder; +import org.apache.commons.lang3.time.FastDateFormat; +import java.text.Format; +import java.util.ArrayList; +import java.util.Date; import java.util.List; import java.util.Map; @@ -26,6 +36,8 @@ */ @RequiredArgsConstructor public class WxCpUserServiceImpl implements WxCpUserService { + private final Format dateFormat = FastDateFormat.getInstance("yyyy-MM-dd"); + private final WxCpService mainService; @Override @@ -84,9 +96,9 @@ public List listByDepartment(Long departId, Boolean fetchChild, Intege String url = this.mainService.getWxCpConfigStorage().getApiUrl(USER_LIST + departId); String responseContent = this.mainService.get(url, params); - JsonElement tmpJsonElement = new JsonParser().parse(responseContent); + JsonObject jsonObject = GsonParser.parse(responseContent); return WxCpGsonBuilder.create() - .fromJson(tmpJsonElement.getAsJsonObject().get("userlist"), + .fromJson(jsonObject.get("userlist"), new TypeToken>() { }.getType() ); @@ -107,10 +119,10 @@ public List listSimpleByDepartment(Long departId, Boolean fetchChild, String url = this.mainService.getWxCpConfigStorage().getApiUrl(USER_SIMPLE_LIST + departId); String responseContent = this.mainService.get(url, params); - JsonElement tmpJsonElement = new JsonParser().parse(responseContent); + JsonObject tmpJson = GsonParser.parse(responseContent); return WxCpGsonBuilder.create() .fromJson( - tmpJsonElement.getAsJsonObject().get("userlist"), + tmpJson.get("userlist"), new TypeToken>() { }.getType() ); @@ -158,14 +170,14 @@ public Map userId2Openid(String userId, Integer agentId) throws } String responseContent = this.mainService.post(url, jsonObject.toString()); - JsonElement tmpJsonElement = new JsonParser().parse(responseContent); + JsonObject tmpJson = GsonParser.parse(responseContent); Map result = Maps.newHashMap(); - if (tmpJsonElement.getAsJsonObject().get("openid") != null) { - result.put("openid", tmpJsonElement.getAsJsonObject().get("openid").getAsString()); + if (tmpJson.get("openid") != null) { + result.put("openid", tmpJson.get("openid").getAsString()); } - if (tmpJsonElement.getAsJsonObject().get("appid") != null) { - result.put("appid", tmpJsonElement.getAsJsonObject().get("appid").getAsString()); + if (tmpJson.get("appid") != null) { + result.put("appid", tmpJson.get("appid").getAsString()); } return result; @@ -177,8 +189,8 @@ public String openid2UserId(String openid) throws WxErrorException { jsonObject.addProperty("openid", openid); String url = this.mainService.getWxCpConfigStorage().getApiUrl(USER_CONVERT_TO_USERID); String responseContent = this.mainService.post(url, jsonObject.toString()); - JsonElement tmpJsonElement = new JsonParser().parse(responseContent); - return tmpJsonElement.getAsJsonObject().get("userid").getAsString(); + JsonObject tmpJson = GsonParser.parse(responseContent); + return tmpJson.get("userid").getAsString(); } @Override @@ -187,14 +199,86 @@ public String getUserId(String mobile) throws WxErrorException { jsonObject.addProperty("mobile", mobile); String url = this.mainService.getWxCpConfigStorage().getApiUrl(GET_USER_ID); String responseContent = this.mainService.post(url, jsonObject.toString()); - JsonElement tmpJsonElement = new JsonParser().parse(responseContent); - return tmpJsonElement.getAsJsonObject().get("userid").getAsString(); + JsonObject tmpJson = GsonParser.parse(responseContent); + return tmpJson.get("userid").getAsString(); + } + + @Override + public String getUserIdByEmail(String email, int emailType) throws WxErrorException { + JsonObject jsonObject = new JsonObject(); + jsonObject.addProperty("email", email); + jsonObject.addProperty("email_type", emailType); + String url = this.mainService.getWxCpConfigStorage().getApiUrl(GET_USER_ID_BY_EMAIL); + String responseContent = this.mainService.post(url, jsonObject.toString()); + JsonObject tmpJson = GsonParser.parse(responseContent); + return tmpJson.get("userid").getAsString(); } @Override - public WxCpUserExternalContactInfo getExternalContact(String userId) throws WxErrorException { + public WxCpExternalContactInfo getExternalContact(String userId) throws WxErrorException { String url = this.mainService.getWxCpConfigStorage().getApiUrl(GET_EXTERNAL_CONTACT + userId); String responseContent = this.mainService.get(url, null); - return WxCpUserExternalContactInfo.fromJson(responseContent); + return WxCpExternalContactInfo.fromJson(responseContent); + } + + @Override + public String getJoinQrCode(int sizeType) throws WxErrorException { + String url = this.mainService.getWxCpConfigStorage().getApiUrl(GET_JOIN_QR_CODE + sizeType); + String responseContent = this.mainService.get(url, null); + JsonObject tmpJson = GsonParser.parse(responseContent); + return tmpJson.get("join_qrcode").getAsString(); } + + @Override + public Integer getActiveStat(Date date) throws WxErrorException { + JsonObject jsonObject = new JsonObject(); + jsonObject.addProperty("date", this.dateFormat.format(date)); + String url = this.mainService.getWxCpConfigStorage().getApiUrl(GET_ACTIVE_STAT); + String responseContent = this.mainService.post(url, jsonObject.toString()); + JsonObject tmpJson = GsonParser.parse(responseContent); + return tmpJson.get("active_cnt").getAsInt(); + } + + @Override + public WxCpUseridToOpenUseridResult useridToOpenUserid(ArrayList useridList) throws WxErrorException { + JsonObject jsonObject = new JsonObject(); + JsonArray jsonArray = new JsonArray(); + for (String userid : useridList) { + jsonArray.add(userid); + } + jsonObject.add("userid_list", jsonArray); + String url = this.mainService.getWxCpConfigStorage().getApiUrl(USERID_TO_OPEN_USERID); + String responseContent = this.mainService.post(url, jsonObject.toString()); + return WxCpUseridToOpenUseridResult.fromJson(responseContent); + } + + @Override + public WxCpOpenUseridToUseridResult openUseridToUserid(List openUseridList, String sourceAgentId) throws WxErrorException { + JsonObject jsonObject = new JsonObject(); + JsonArray jsonArray = new JsonArray(); + for (String openUserid : openUseridList) { + jsonArray.add(openUserid); + } + jsonObject.add("open_userid_list", jsonArray); + jsonObject.addProperty("source_agentid", sourceAgentId); + String url = this.mainService.getWxCpConfigStorage().getApiUrl(OPEN_USERID_TO_USERID); + String responseContent = this.mainService.post(url, jsonObject.toString()); + return WxCpOpenUseridToUseridResult.fromJson(responseContent); + } + + @Override + public WxCpDeptUserResult getUserListId(String cursor, Integer limit) throws WxErrorException { + String apiUrl = this.mainService.getWxCpConfigStorage().getApiUrl(USER_LIST_ID); + JsonObject jsonObject = new JsonObject(); + if (cursor != null) { + jsonObject.addProperty("cursor", cursor); + } + if (limit != null) { + jsonObject.addProperty("limit", limit); + } + String responseContent = this.mainService.post(apiUrl, jsonObject.toString()); + return WxCpDeptUserResult.fromJson(responseContent); + } + + } diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/Gender.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/Gender.java index 2b6e26efde..b47697ffac 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/Gender.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/Gender.java @@ -1,5 +1,8 @@ package me.chanjar.weixin.cp.bean; +import lombok.AllArgsConstructor; +import lombok.Getter; + /** *
  *  性别枚举
@@ -8,7 +11,13 @@
  *
  * @author Binary Wang
  */
+@Getter
+@AllArgsConstructor
 public enum Gender {
+  /**
+   * 未定义
+   */
+  UNDEFINED("未定义", "0"),
   /**
    * 男
    */
@@ -18,28 +27,20 @@ public enum Gender {
    */
   FEMALE("女", "2");
 
-  private String genderName;
-  private String code;
-
-  Gender(String genderName, String code) {
-    this.genderName = genderName;
-    this.code = code;
-  }
-
-  public String getGenderName() {
-    return this.genderName;
-  }
-
-  public String getCode() {
-    return this.code;
-  }
+  private final String genderName;
+  private final String code;
 
+  /**
+   * From code gender.
+   *
+   * @param code the code
+   * @return the gender
+   */
   public static Gender fromCode(String code) {
-    if ("1".equals(code)) {
-      return Gender.MALE;
-    }
-    if ("2".equals(code)) {
-      return Gender.FEMALE;
+    for (Gender a : Gender.values()) {
+      if (a.code.equals(code)) {
+        return a;
+      }
     }
 
     return null;
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpAgent.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpAgent.java
index 18dfb346ce..5d61b3a199 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpAgent.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpAgent.java
@@ -1,8 +1,5 @@
 package me.chanjar.weixin.cp.bean;
 
-import java.io.Serializable;
-import java.util.List;
-
 import com.google.gson.annotations.SerializedName;
 import lombok.AllArgsConstructor;
 import lombok.Builder;
@@ -10,6 +7,9 @@
 import lombok.NoArgsConstructor;
 import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder;
 
+import java.io.Serializable;
+import java.util.List;
+
 /**
  * 
  * 企业号应用信息.
@@ -70,36 +70,64 @@ public class WxCpAgent implements Serializable {
   @SerializedName("home_url")
   private String homeUrl;
 
+  @SerializedName("customized_publish_status")
+  private Integer customizedPublishStatus;
+
+  /**
+   * From json wx cp agent.
+   *
+   * @param json the json
+   * @return the wx cp agent
+   */
   public static WxCpAgent fromJson(String json) {
     return WxCpGsonBuilder.create().fromJson(json, WxCpAgent.class);
   }
 
+  /**
+   * To json string.
+   *
+   * @return the string
+   */
   public String toJson() {
     return WxCpGsonBuilder.create().toJson(this);
   }
 
+  /**
+   * The type Users.
+   */
   @Data
   public static class Users implements Serializable {
     private static final long serialVersionUID = 8801100463558788565L;
+
     @SerializedName("user")
     private List users;
   }
 
+  /**
+   * The type User.
+   */
   @Data
-  public class User implements Serializable {
+  public static class User implements Serializable {
     private static final long serialVersionUID = 7287632514385508024L;
+
     @SerializedName("userid")
     private String userId;
   }
 
+  /**
+   * The type Parties.
+   */
   @Data
-  public class Parties {
+  public static class Parties {
     @SerializedName("partyid")
-    private List partyIds = null;
+    private List partyIds = null;
   }
 
+  /**
+   * The type Tags.
+   */
   @Data
-  public class Tags {
+  public static class Tags {
     @SerializedName("tagid")
     private List tagIds = null;
   }
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpAgentJsapiSignature.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpAgentJsapiSignature.java
new file mode 100644
index 0000000000..4562d9b9b0
--- /dev/null
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpAgentJsapiSignature.java
@@ -0,0 +1,31 @@
+package me.chanjar.weixin.cp.bean;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+
+/**
+ * 调用wx.agentConfig时所需要的签名信息
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class WxCpAgentJsapiSignature implements Serializable {
+  private static final long serialVersionUID = 2650119900835832545L;
+
+  private String url;
+
+  private String corpid;
+
+  private Integer agentid;
+
+  private long timestamp;
+
+  private String nonceStr;
+
+  private String signature;
+}
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
new file mode 100644
index 0000000000..4c17397ecd
--- /dev/null
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpAgentWorkBench.java
@@ -0,0 +1,262 @@
+package me.chanjar.weixin.cp.bean;
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonObject;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import me.chanjar.weixin.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;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * The type Wx cp agent work bench.
+ *
+ * @author songshiyu  created on  : create in 16:09 2020/9/27 工作台自定义展示
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class WxCpAgentWorkBench implements Serializable {
+  private static final long serialVersionUID = -4136604790232843229L;
+
+  /**
+   * 展示类型,目前支持 “keydata”、 “image”、 “list” 、”webview”
+   */
+  private String type;
+  /**
+   * 用户的userid
+   */
+  private String userId;
+  /**
+   * 用户的userIds
+   */
+  private List useridList;
+  /**
+   * 应用id
+   */
+  private Long agentId;
+  /**
+   * 点击跳转url,若不填且应用设置了主页url,则跳转到主页url,否则跳到应用会话窗口
+   */
+  private String jumpUrl;
+  /**
+   * 若应用为小程序类型,该字段填小程序pagepath,若未设置,跳到小程序主页
+   */
+  private String pagePath;
+  /**
+   * 图片url:图片的最佳比例为3.35:1;webview:渲染展示的url
+   */
+  private String url;
+  /**
+   * 是否覆盖用户工作台的数据。设置为true的时候,会覆盖企业所有用户当前设置的数据。若设置为false,则不会覆盖用户当前设置的所有数据
+   */
+  private Boolean replaceUserData;
+  /**
+   * 是否开启webview内的链接跳转能力,默认值为false。注意:开启之后,会使jump_url失效。 链接跳转仅支持以下schema方式:wxwork://openurl?url=xxxx,注意url需要进行编码。
+   * 参考示例:今日要闻
+   */
+  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;
+
+  private List lists;
+
+  /**
+   * 生成模板Json字符串
+   *
+   * @return the string
+   */
+  public String toTemplateString() {
+    JsonObject templateObject = new JsonObject();
+    templateObject.addProperty("agentid", this.agentId);
+    templateObject.addProperty("type", this.type);
+    if (this.replaceUserData != null) {
+      templateObject.addProperty("replace_user_data", this.replaceUserData);
+    }
+    this.handle(templateObject);
+    return templateObject.toString();
+  }
+
+  /**
+   * 生成用户数据Json字符串
+   *
+   * @return the string
+   */
+  public String toUserDataString() {
+    JsonObject userDataObject = new JsonObject();
+    userDataObject.addProperty("agentid", this.agentId);
+    userDataObject.addProperty("userid", this.userId);
+    userDataObject.addProperty("type", this.type);
+    this.handle(userDataObject);
+    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();
+  }
+
+  /**
+   * 处理不用类型的工作台数据
+   */
+  private void handle(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);
+        templateObject.add("keydata", itemsObject);
+        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);
+        templateObject.add("image", image);
+        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);
+        templateObject.add("list", itemsObject);
+        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);
+        }
+        templateObject.add("webview", webview);
+        break;
+      }
+      default: {
+        //do nothing
+      }
+    }
+  }
+
+  /**
+   * 处理不用类型的工作台数据
+   */
+  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/WxCpApprovalDataResult.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpApprovalDataResult.java
deleted file mode 100644
index 383c526568..0000000000
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpApprovalDataResult.java
+++ /dev/null
@@ -1,68 +0,0 @@
-package me.chanjar.weixin.cp.bean;
-
-import com.google.gson.annotations.SerializedName;
-import lombok.Data;
-
-import java.io.Serializable;
-import java.util.Map;
-
-/**
- * 企业微信 OA 审批数据.
- *
- * @author Element
- * @date 2019-04-06 14:36
- */
-@Data
-public class WxCpApprovalDataResult implements Serializable {
-  private static final long serialVersionUID = -1046940445840716590L;
-
-  @SerializedName("errcode")
-  private Integer errCode;
-
-  @SerializedName("errmsg")
-  private String errMsg;
-
-  private Integer count;
-
-  private Integer total;
-
-  @SerializedName("next_spnum")
-  private Long nextSpNum;
-
-  private WxCpApprovalData[] data;
-
-
-  @Data
-  public static class WxCpApprovalData implements Serializable {
-    private static final long serialVersionUID = -3051785319608491640L;
-    @SerializedName("spname")
-    private String spName;
-
-    @SerializedName("apply_name")
-    private String applyName;
-
-    @SerializedName("apply_org")
-    private String applyOrg;
-
-    @SerializedName("approval_name")
-    private String[] approvalName;
-
-    @SerializedName("notify_name")
-    private String[] notifyName;
-
-    @SerializedName("sp_status")
-    private Integer spStatus;
-
-    @SerializedName("sp_num")
-    private Long spNum;
-
-    @SerializedName("apply_time")
-    private Long applyTime;
-
-    @SerializedName("apply_user_id")
-    private String applyUserId;
-
-    @SerializedName("comm")
-    private Map comm;
-  }
-}
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpBaseResp.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpBaseResp.java
new file mode 100644
index 0000000000..a895c38a8f
--- /dev/null
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpBaseResp.java
@@ -0,0 +1,61 @@
+package me.chanjar.weixin.cp.bean;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Getter;
+import lombok.Setter;
+import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder;
+
+import java.io.Serializable;
+
+/**
+ * 返回结果
+ *
+ * @author yqx, WangWong
+ * @since 2020/3/16
+ */
+@Getter
+@Setter
+public class WxCpBaseResp implements Serializable {
+  private static final long serialVersionUID = -4301684507150486556L;
+
+  /**
+   * The Errcode.
+   */
+  @SerializedName("errcode")
+  protected Long errcode;
+
+  /**
+   * The Errmsg.
+   */
+  @SerializedName("errmsg")
+  protected String errmsg;
+
+  /**
+   * Success boolean.
+   *
+   * @return the boolean
+   */
+  public boolean success() {
+    return getErrcode() == 0;
+  }
+
+  /**
+   * From json wx cp base resp.
+   *
+   * @param json the json
+   * @return the wx cp base resp
+   */
+  public static WxCpBaseResp fromJson(String json) {
+    return WxCpGsonBuilder.create().fromJson(json, WxCpBaseResp.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/WxCpChat.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpChat.java
index 1f593e4746..eb014c595a 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpChat.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpChat.java
@@ -1,20 +1,22 @@
-package me.chanjar.weixin.cp.bean;
-
-import java.util.List;
-
-import lombok.Data;
-
-/**
- * 群聊
- *
- * @author gaigeshen
- */
-@Data
-public class WxCpChat {
-  
-  private String id;
-  private String name;
-  private String owner;
-  private List users;
-
-}
+package me.chanjar.weixin.cp.bean;
+
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * 群聊
+ *
+ * @author gaigeshen
+ */
+@Data
+public class WxCpChat implements Serializable {
+  private static final long serialVersionUID = -4301684507150486556L;
+
+  private String id;
+  private String name;
+  private String owner;
+  private List users;
+
+}
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpCheckinData.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpCheckinData.java
deleted file mode 100644
index d2fbf0f9d7..0000000000
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpCheckinData.java
+++ /dev/null
@@ -1,50 +0,0 @@
-package me.chanjar.weixin.cp.bean;
-
-import com.google.gson.annotations.SerializedName;
-import lombok.Data;
-
-import java.io.Serializable;
-import java.util.List;
-
-/**
- * 企业微信打卡数据.
- *
- * @author Element
- * @date 2019-04-06 11:01
- */
-@Data
-public class WxCpCheckinData implements Serializable {
-  private static final long serialVersionUID = 1915820330847799605L;
-
-  @SerializedName("userid")
-  private String userId;
-
-  @SerializedName("groupname")
-  private String groupName;
-
-  @SerializedName("checkin_type")
-  private String checkinType;
-
-  @SerializedName("exception_type")
-  private String exceptionType;
-
-  @SerializedName("checkin_time")
-  private Long checkinTime;
-
-  @SerializedName("location_title")
-  private String locationTitle;
-
-  @SerializedName("location_detail")
-  private String locationDetail;
-
-  @SerializedName("wifiname")
-  private String wifiName;
-
-  @SerializedName("wifimac")
-  private String wifiMac;
-
-  private String notes;
-
-  @SerializedName("mediaids")
-  private List mediaIds;
-}
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpCheckinOption.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpCheckinOption.java
deleted file mode 100644
index c554e3d706..0000000000
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpCheckinOption.java
+++ /dev/null
@@ -1,145 +0,0 @@
-package me.chanjar.weixin.cp.bean;
-
-import com.google.gson.annotations.SerializedName;
-import lombok.Data;
-
-import java.io.Serializable;
-import java.util.List;
-
-/**
- * 企业微信打卡规则.
- *
- * @author Element
- * @date 2019-04-06 13:22
- */
-@Data
-public class WxCpCheckinOption implements Serializable {
-  private static final long serialVersionUID = -1964233697990417482L;
-
-  @SerializedName("userid")
-  private String userId;
-
-  private Group group;
-
-  @Data
-  public static class CheckinDate implements Serializable {
-    private static final long serialVersionUID = -5601722383347110974L;
-
-    private List workdays;
-
-    @SerializedName("checkintime")
-    private CheckinTime[] checkinTime;
-
-    @SerializedName("flex_time")
-    private Long flexTime;
-
-    @SerializedName("noneed_offwork")
-    private Boolean noNeedOffwork;
-
-    @SerializedName("limit_aheadtime")
-    private Long limitAheadTime;
-  }
-
-  @Data
-  public static class CheckinTime implements Serializable {
-    private static final long serialVersionUID = -8579954143265336276L;
-
-    @SerializedName("work_sec")
-    private Long workSec;
-
-    @SerializedName("off_work_sec")
-    private Long offWorkSec;
-
-    @SerializedName("remind_work_sec")
-    private Long remindWorkSec;
-
-    @SerializedName("remind_off_work_sec")
-    private Long remindOffWorkSec;
-  }
-
-  @Data
-  public static class Group implements Serializable {
-
-    private static final long serialVersionUID = -5888406969613403044L;
-
-    @SerializedName("groupid")
-    private Long id;
-
-    @SerializedName("groupname")
-    private String name;
-
-    @SerializedName("grouptype")
-    private Integer type;
-
-    @SerializedName("checkindate")
-    private List checkinDate;
-
-    @SerializedName("spe_workdays")
-    private List speWorkdays;
-
-    @SerializedName("spe_offdays")
-    private List speOffdays;
-
-    @SerializedName("sync_holidays")
-    private Boolean syncHolidays;
-
-    @SerializedName("need_photo")
-    private Boolean needPhoto;
-
-    @SerializedName("note_can_use_local_pic")
-    private Boolean noteCanUseLocalPic;
-
-    @SerializedName("allow_checkin_offworkday")
-    private Boolean allowCheckinOffWorkday;
-
-    @SerializedName("allow_apply_offworkday")
-    private Boolean allowApplyOffWorkday;
-
-    @SerializedName("wifimac_infos")
-    private List wifiMacInfos;
-
-    @SerializedName("loc_infos")
-    private List locInfos;
-
-  }
-
-  @Data
-  public static class WifiMacInfo implements Serializable {
-    private static final long serialVersionUID = -4657809185716627368L;
-
-    @SerializedName("wifiname")
-    private String name;
-
-    @SerializedName("wifimac")
-    private String mac;
-  }
-
-  @Data
-  public static class LocInfo implements Serializable {
-    private static final long serialVersionUID = -618965280668099608L;
-
-    private Long lat;
-    private Long lng;
-
-    @SerializedName("loc_title")
-    private String title;
-
-    @SerializedName("loc_detail")
-    private String detail;
-
-    private Long distance;
-  }
-
-  @Data
-  public static class SpeDay implements Serializable {
-    private static final long serialVersionUID = -3538818921359212748L;
-
-    private Long timestamp;
-    private String notes;
-
-    @SerializedName("checkintime")
-    private List checkinTime;
-
-  }
-
-}
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpDepart.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpDepart.java
index dc71e027e3..bc54e7e806 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpDepart.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpDepart.java
@@ -1,28 +1,41 @@
 package me.chanjar.weixin.cp.bean;
 
-import java.io.Serializable;
-
 import lombok.Data;
 import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder;
 
+import java.io.Serializable;
+
 /**
- * 微信部门.
+ * 企业微信的部门.
  *
  * @author Daniel Qian
  */
 @Data
 public class WxCpDepart implements Serializable {
-
   private static final long serialVersionUID = -5028321625140879571L;
+
   private Long id;
   private String name;
+  private String enName;
+  private String[] departmentLeader;
   private Long parentId;
   private Long order;
 
+  /**
+   * From json wx cp depart.
+   *
+   * @param json the json
+   * @return the wx cp depart
+   */
   public static WxCpDepart fromJson(String json) {
     return WxCpGsonBuilder.create().fromJson(json, WxCpDepart.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/WxCpInviteResult.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpInviteResult.java
index ccf6fc94db..3cbeb7ce7b 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpInviteResult.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpInviteResult.java
@@ -1,16 +1,11 @@
 package me.chanjar.weixin.cp.bean;
 
-import java.io.Serializable;
-import java.util.Collections;
-import java.util.List;
-
-import org.apache.commons.lang3.StringUtils;
-
-import com.google.common.base.Splitter;
 import com.google.gson.annotations.SerializedName;
 import lombok.Data;
 import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder;
 
+import java.io.Serializable;
+
 /**
  * 邀请成员的结果对象类.
  * Created by Binary Wang on 2018-5-13.
@@ -26,6 +21,12 @@ public String toString() {
     return WxCpGsonBuilder.create().toJson(this);
   }
 
+  /**
+   * From json wx cp invite result.
+   *
+   * @param json the json
+   * @return the wx cp invite result
+   */
   public static WxCpInviteResult fromJson(String json) {
     return WxCpGsonBuilder.create().fromJson(json, WxCpInviteResult.class);
   }
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpMaJsCode2SessionResult.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpMaJsCode2SessionResult.java
index 90f1ae840c..f4de0b988a 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpMaJsCode2SessionResult.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpMaJsCode2SessionResult.java
@@ -11,6 +11,7 @@
  * 小程序登录凭证校验
  * 文档地址:https://work.weixin.qq.com/api/doc#90000/90136/90289/wx.qy.login
  * 
+ * * @author Binary Wang */ @Data @@ -26,6 +27,12 @@ public class WxCpMaJsCode2SessionResult implements Serializable { @SerializedName("corpid") private String corpId; + /** + * From json wx cp ma js code 2 session result. + * + * @param json the json + * @return the wx cp ma js code 2 session result + */ public static WxCpMaJsCode2SessionResult fromJson(String json) { return WxCpGsonBuilder.create().fromJson(json, WxCpMaJsCode2SessionResult.class); } diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpMessage.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpMessage.java deleted file mode 100644 index b39c2229e0..0000000000 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpMessage.java +++ /dev/null @@ -1,333 +0,0 @@ -package me.chanjar.weixin.cp.bean; - -import com.google.gson.JsonArray; -import com.google.gson.JsonObject; -import lombok.Data; -import me.chanjar.weixin.common.api.WxConsts.KefuMsgType; -import me.chanjar.weixin.cp.bean.article.MpnewsArticle; -import me.chanjar.weixin.cp.bean.article.NewArticle; -import me.chanjar.weixin.cp.bean.messagebuilder.*; -import me.chanjar.weixin.cp.bean.taskcard.TaskCardButton; -import org.apache.commons.lang3.StringUtils; - -import java.io.Serializable; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; - -import static me.chanjar.weixin.common.api.WxConsts.KefuMsgType.*; - -/** - * 消息. - * - * @author Daniel Qian - */ -@Data -public class WxCpMessage implements Serializable { - private static final long serialVersionUID = -2082278303476631708L; - - private String toUser; - private String toParty; - private String toTag; - private Integer agentId; - private String msgType; - private String content; - private String mediaId; - private String thumbMediaId; - private String title; - private String description; - private String musicUrl; - private String hqMusicUrl; - private String safe; - private String url; - private String btnTxt; - private List articles = new ArrayList<>(); - private List mpnewsArticles = new ArrayList<>(); - private String appId; - private String page; - private Boolean emphasisFirstItem; - private Map contentItems; - - /** - * 任务卡片特有的属性. - */ - private String taskId; - private List taskButtons = new ArrayList<>(); - - /** - * 获得文本消息builder. - */ - public static TextBuilder TEXT() { - return new TextBuilder(); - } - - /** - * 获得文本卡片消息builder. - */ - public static TextCardBuilder TEXTCARD() { - return new TextCardBuilder(); - } - - /** - * 获得图片消息builder. - */ - public static ImageBuilder IMAGE() { - return new ImageBuilder(); - } - - /** - * 获得语音消息builder. - */ - public static VoiceBuilder VOICE() { - return new VoiceBuilder(); - } - - /** - * 获得视频消息builder. - */ - public static VideoBuilder VIDEO() { - return new VideoBuilder(); - } - - /** - * 获得图文消息builder. - */ - public static NewsBuilder NEWS() { - return new NewsBuilder(); - } - - /** - * 获得mpnews图文消息builder. - */ - public static MpnewsBuilder MPNEWS() { - return new MpnewsBuilder(); - } - - /** - * 获得markdown消息builder. - */ - public static MarkdownMsgBuilder MARKDOWN() { - return new MarkdownMsgBuilder(); - } - - /** - * 获得文件消息builder. - */ - public static FileBuilder FILE() { - return new FileBuilder(); - } - - /** - * 获得任务卡片消息builder. - */ - public static TaskCardBuilder TASKCARD() { - return new TaskCardBuilder(); - } - - /** - * 获得小程序通知消息builder. - */ - public static MiniProgramNoticeMsgBuilder newMiniProgramNoticeBuilder() { - return new MiniProgramNoticeMsgBuilder(); - } - - /** - *
-   * 请使用.
-   * {@link KefuMsgType#TEXT}
-   * {@link KefuMsgType#IMAGE}
-   * {@link KefuMsgType#VOICE}
-   * {@link KefuMsgType#MUSIC}
-   * {@link KefuMsgType#VIDEO}
-   * {@link KefuMsgType#NEWS}
-   * {@link KefuMsgType#MPNEWS}
-   * {@link KefuMsgType#MARKDOWN}
-   * {@link KefuMsgType#TASKCARD}
-   * {@link KefuMsgType#MINIPROGRAM_NOTICE}
-   * 
- * - * @param msgType 消息类型 - */ - public void setMsgType(String msgType) { - this.msgType = msgType; - } - - public String toJson() { - JsonObject messageJson = new JsonObject(); - if (this.getAgentId() != null) { - messageJson.addProperty("agentid", this.getAgentId()); - } - - if (StringUtils.isNotBlank(this.getToUser())) { - messageJson.addProperty("touser", this.getToUser()); - } - - messageJson.addProperty("msgtype", this.getMsgType()); - - if (StringUtils.isNotBlank(this.getToParty())) { - messageJson.addProperty("toparty", this.getToParty()); - } - - if (StringUtils.isNotBlank(this.getToTag())) { - messageJson.addProperty("totag", this.getToTag()); - } - - this.handleMsgType(messageJson); - - if (StringUtils.isNotBlank(this.getSafe())) { - messageJson.addProperty("safe", this.getSafe()); - } - - return messageJson.toString(); - } - - private void handleMsgType(JsonObject messageJson) { - switch (this.getMsgType()) { - case TEXT: { - JsonObject text = new JsonObject(); - text.addProperty("content", this.getContent()); - messageJson.add("text", text); - break; - } - case MARKDOWN: { - JsonObject text = new JsonObject(); - text.addProperty("content", this.getContent()); - messageJson.add("markdown", text); - break; - } - case TEXTCARD: { - JsonObject text = new JsonObject(); - text.addProperty("title", this.getTitle()); - text.addProperty("description", this.getDescription()); - text.addProperty("url", this.getUrl()); - text.addProperty("btntxt", this.getBtnTxt()); - messageJson.add("textcard", text); - break; - } - case IMAGE: { - JsonObject image = new JsonObject(); - image.addProperty("media_id", this.getMediaId()); - messageJson.add("image", image); - break; - } - case FILE: { - JsonObject image = new JsonObject(); - image.addProperty("media_id", this.getMediaId()); - messageJson.add("file", image); - break; - } - case VOICE: { - JsonObject voice = new JsonObject(); - voice.addProperty("media_id", this.getMediaId()); - messageJson.add("voice", voice); - break; - } - case VIDEO: { - JsonObject video = new JsonObject(); - video.addProperty("media_id", this.getMediaId()); - video.addProperty("thumb_media_id", this.getThumbMediaId()); - video.addProperty("title", this.getTitle()); - video.addProperty("description", this.getDescription()); - messageJson.add("video", video); - break; - } - case NEWS: { - JsonObject newsJsonObject = new JsonObject(); - JsonArray articleJsonArray = new JsonArray(); - for (NewArticle article : this.getArticles()) { - JsonObject articleJson = new JsonObject(); - articleJson.addProperty("title", article.getTitle()); - articleJson.addProperty("description", article.getDescription()); - articleJson.addProperty("url", article.getUrl()); - articleJson.addProperty("picurl", article.getPicUrl()); - articleJsonArray.add(articleJson); - } - newsJsonObject.add("articles", articleJsonArray); - messageJson.add("news", newsJsonObject); - break; - } - case MPNEWS: { - JsonObject newsJsonObject = new JsonObject(); - if (this.getMediaId() != null) { - newsJsonObject.addProperty("media_id", this.getMediaId()); - } else { - JsonArray articleJsonArray = new JsonArray(); - for (MpnewsArticle article : this.getMpnewsArticles()) { - JsonObject articleJson = new JsonObject(); - articleJson.addProperty("title", article.getTitle()); - articleJson.addProperty("thumb_media_id", article.getThumbMediaId()); - articleJson.addProperty("author", article.getAuthor()); - articleJson.addProperty("content_source_url", article.getContentSourceUrl()); - articleJson.addProperty("content", article.getContent()); - articleJson.addProperty("digest", article.getDigest()); - articleJson.addProperty("show_cover_pic", article.getShowCoverPic()); - articleJsonArray.add(articleJson); - } - - newsJsonObject.add("articles", articleJsonArray); - } - messageJson.add("mpnews", newsJsonObject); - break; - } - case TASKCARD: { - JsonObject text = new JsonObject(); - text.addProperty("title", this.getTitle()); - text.addProperty("description", this.getDescription()); - - if (StringUtils.isNotBlank(this.getUrl())) { - text.addProperty("url", this.getUrl()); - } - - text.addProperty("task_id", this.getTaskId()); - - JsonArray buttonJsonArray = new JsonArray(); - for (TaskCardButton button : this.getTaskButtons()) { - JsonObject buttonJson = new JsonObject(); - buttonJson.addProperty("key", button.getKey()); - buttonJson.addProperty("name", button.getName()); - - if (StringUtils.isNotBlank(button.getReplaceName())) { - buttonJson.addProperty("replace_name", button.getReplaceName()); - } - - if (StringUtils.isNotBlank(button.getColor())) { - buttonJson.addProperty("color", button.getColor()); - } - - if (button.getBold() != null) { - buttonJson.addProperty("is_bold", button.getBold()); - } - - buttonJsonArray.add(buttonJson); - } - text.add("btn", buttonJsonArray); - - messageJson.add("taskcard", text); - break; - } - case MINIPROGRAM_NOTICE: { - JsonObject notice = new JsonObject(); - notice.addProperty("appid", this.getAppId()); - notice.addProperty("page", this.getPage()); - notice.addProperty("description", this.getDescription()); - notice.addProperty("title", this.getTitle()); - notice.addProperty("emphasis_first_item", this.getEmphasisFirstItem()); - JsonArray content = new JsonArray(); - for (Map.Entry item : this.getContentItems().entrySet()) { - JsonObject articleJson = new JsonObject(); - articleJson.addProperty("key", item.getKey()); - articleJson.addProperty("value", item.getValue()); - content.add(articleJson); - } - notice.add("content_item", content); - - messageJson.add("miniprogram_notice", notice); - break; - } - default: { - // do nothing - } - } - } - -} diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpMessageSendResult.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpMessageSendResult.java deleted file mode 100644 index d850adebcc..0000000000 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpMessageSendResult.java +++ /dev/null @@ -1,68 +0,0 @@ -package me.chanjar.weixin.cp.bean; - -import java.io.Serializable; -import java.util.Collections; -import java.util.List; - -import org.apache.commons.lang3.StringUtils; - -import com.google.common.base.Splitter; -import com.google.gson.annotations.SerializedName; -import lombok.Data; -import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder; - -/** - * 消息发送结果对象类. - * Created by Binary Wang on 2017-6-22. - * - * @author Binary Wang - */ -@Data -public class WxCpMessageSendResult implements Serializable { - private static final long serialVersionUID = 916455987193190004L; - - @Override - public String toString() { - return WxCpGsonBuilder.create().toJson(this); - } - - public static WxCpMessageSendResult fromJson(String json) { - return WxCpGsonBuilder.create().fromJson(json, WxCpMessageSendResult.class); - } - - @SerializedName("errcode") - private Integer errCode; - - @SerializedName("errmsg") - private String errMsg; - - @SerializedName("invaliduser") - private String invalidUser; - - @SerializedName("invalidparty") - private String invalidParty; - - @SerializedName("invalidtag") - private String invalidTag; - - - public List getInvalidUserList() { - return this.content2List(this.invalidUser); - } - - private List content2List(String content) { - if (StringUtils.isBlank(content)) { - return Collections.emptyList(); - } - - return Splitter.on("|").splitToList(content); - } - - public List getInvalidPartyList() { - return this.content2List(this.invalidParty); - } - - public List getInvalidTagList() { - return this.content2List(this.invalidTag); - } -} diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpOauth2UserInfo.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpOauth2UserInfo.java index 9122f18d3a..433e54a680 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpOauth2UserInfo.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpOauth2UserInfo.java @@ -6,11 +6,15 @@ import lombok.NoArgsConstructor; import lombok.experimental.Accessors; +import java.io.Serializable; + /** *
  *  用oauth2获取用户信息的结果类
  *  Created by BinaryWang on 2019/5/26.
  * 
+ *

+ * 文档1:https://developer.work.weixin.qq.com/document/path/91707 * * @author Binary Wang */ @@ -19,10 +23,16 @@ @NoArgsConstructor @AllArgsConstructor @Builder -public class WxCpOauth2UserInfo { +public class WxCpOauth2UserInfo implements Serializable { + private static final long serialVersionUID = -4301684507150486556L; + private String openId; private String deviceId; private String userId; private String userTicket; private String expiresIn; + private String externalUserId; + private String parentUserId; + private String studentUserId; + } diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpOpenUseridToUserid.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpOpenUseridToUserid.java new file mode 100644 index 0000000000..ec4d276e0a --- /dev/null +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpOpenUseridToUserid.java @@ -0,0 +1,40 @@ +package me.chanjar.weixin.cp.bean; + +import com.google.gson.annotations.SerializedName; +import lombok.Data; +import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder; + +import java.io.Serializable; + +/** + * userid转换 + * 将代开发应用或第三方应用获取的密文open_userid转换为明文userid + * 中间对象 + * @author yiyingcanfeng + */ +@Data +public class WxCpOpenUseridToUserid implements Serializable { + private static final long serialVersionUID = 1714909184316350423L; + + @Override + public String toString() { + return WxCpGsonBuilder.create().toJson(this); + } + + /** + * From json wx cp open userid to userid result. + * + * @param json the json + * @return the wx cp open userid to userid result. + */ + public static WxCpOpenUseridToUserid fromJson(String json) { + return WxCpGsonBuilder.create().fromJson(json, WxCpOpenUseridToUserid.class); + } + + @SerializedName("userid") + private String userid; + + @SerializedName("open_userid") + private String openUserid; + +} diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpOpenUseridToUseridResult.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpOpenUseridToUseridResult.java new file mode 100644 index 0000000000..122c3a0dc6 --- /dev/null +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpOpenUseridToUseridResult.java @@ -0,0 +1,47 @@ +package me.chanjar.weixin.cp.bean; + +import com.google.gson.annotations.SerializedName; +import lombok.Data; +import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder; + +import java.io.Serializable; +import java.util.List; + +/** + * userid转换 + * 将代开发应用或第三方应用获取的密文open_userid转换为明文userid + * @author yiyingcanfeng + */ +@Data +public class WxCpOpenUseridToUseridResult implements Serializable { + private static final long serialVersionUID = 5179329535139861515L; + + @Override + public String toString() { + return WxCpGsonBuilder.create().toJson(this); + } + + /** + * From json wx cp open userid to userid result. + * + * @param json the json + * @return the wx cp open userid to userid result + */ + public static WxCpOpenUseridToUseridResult fromJson(String json) { + return WxCpGsonBuilder.create().fromJson(json, WxCpOpenUseridToUseridResult.class); + } + + @SerializedName("errcode") + private Integer errCode; + + @SerializedName("errmsg") + private String errMsg; + + @SerializedName("userid_list") + private List useridList; + + @SerializedName("invalid_open_userid_list") + private List invalidOpenUseridList; + + +} diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpProviderToken.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpProviderToken.java index 2c98f8e3fd..872b96d93f 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpProviderToken.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpProviderToken.java @@ -4,14 +4,17 @@ import lombok.Data; import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder; +import java.io.Serializable; + /** * 服务商凭证. * - * @author Binary Wang - * @date 2019-11-02 + * @author Binary Wang created on 2019-11-02 */ @Data -public class WxCpProviderToken { +public class WxCpProviderToken implements Serializable { + private static final long serialVersionUID = -4301684507150486556L; + /** * 服务商的access_token,最长为512字节。 */ @@ -24,6 +27,12 @@ public class WxCpProviderToken { @SerializedName("expires_in") private Integer expiresIn; + /** + * From json wx cp provider token. + * + * @param json the json + * @return the wx cp provider token + */ public static WxCpProviderToken fromJson(String json) { return WxCpGsonBuilder.create().fromJson(json, WxCpProviderToken.class); } diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTag.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTag.java index f6b9fa0276..33d3d07b29 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTag.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTag.java @@ -1,14 +1,15 @@ package me.chanjar.weixin.cp.bean; -import java.io.Serializable; - import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder; +import java.io.Serializable; + /** * Created by Daniel Qian. + * * @author Daniel Qian */ @Data @@ -22,10 +23,21 @@ public class WxCpTag implements Serializable { private String name; + /** + * From json wx cp tag. + * + * @param json the json + * @return the wx cp tag + */ public static WxCpTag fromJson(String json) { return WxCpGsonBuilder.create().fromJson(json, WxCpTag.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/WxCpTagAddOrRemoveUsersResult.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTagAddOrRemoveUsersResult.java index 037740ca96..c590c6c0e8 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTagAddOrRemoveUsersResult.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTagAddOrRemoveUsersResult.java @@ -1,15 +1,14 @@ package me.chanjar.weixin.cp.bean; -import java.io.Serializable; -import java.util.Collections; -import java.util.List; - -import org.apache.commons.lang3.StringUtils; - import com.google.common.base.Splitter; import com.google.gson.annotations.SerializedName; import lombok.Data; 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; /** * 为标签添加或移除用户结果对象类. @@ -26,6 +25,12 @@ public String toString() { return WxCpGsonBuilder.create().toJson(this); } + /** + * From json wx cp tag add or remove users result. + * + * @param json the json + * @return the wx cp tag add or remove users result + */ public static WxCpTagAddOrRemoveUsersResult fromJson(String json) { return WxCpGsonBuilder.create().fromJson(json, WxCpTagAddOrRemoveUsersResult.class); } @@ -42,6 +47,11 @@ public static WxCpTagAddOrRemoveUsersResult fromJson(String json) { @SerializedName("invalidparty") private String[] invalidParty; + /** + * Gets invalid user list. + * + * @return the invalid user list + */ public List getInvalidUserList() { return this.content2List(this.invalidUsers); } diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTagGetResult.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTagGetResult.java index 244419b062..3dc34ab654 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTagGetResult.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTagGetResult.java @@ -46,10 +46,21 @@ public class WxCpTagGetResult implements Serializable { @SerializedName("tagname") private String tagname; + /** + * From json wx cp tag get result. + * + * @param json the json + * @return the wx cp tag get result + */ public static WxCpTagGetResult fromJson(String json) { return WxCpGsonBuilder.create().fromJson(json, WxCpTagGetResult.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/WxCpTaskCardUpdateResult.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTaskCardUpdateResult.java index c86b255b44..d4cee5549c 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTaskCardUpdateResult.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTaskCardUpdateResult.java @@ -16,8 +16,7 @@ * Created by Jeff on 2019-05-16. *

* - * @author Jeff - * @date 2019-05-16 + * @author Jeff created on 2019-05-16 */ @Data @AllArgsConstructor @@ -36,6 +35,12 @@ public class WxCpTaskCardUpdateResult implements Serializable { @SerializedName("invaliduser") private List invalidUsers; + /** + * From json wx cp task card update result. + * + * @param json the json + * @return the wx cp task card update result + */ public static WxCpTaskCardUpdateResult fromJson(String json) { return WxCpGsonBuilder.create().fromJson(json, WxCpTaskCardUpdateResult.class); } diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpAdmin.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpAdmin.java new file mode 100644 index 0000000000..776726de80 --- /dev/null +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpAdmin.java @@ -0,0 +1,62 @@ +package me.chanjar.weixin.cp.bean; + +import com.google.gson.annotations.SerializedName; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; +import me.chanjar.weixin.common.util.json.WxGsonBuilder; +import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder; + +import java.util.List; + +/** + * 应用的管理员 + * + * @author huangxiaoming + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class WxCpTpAdmin extends WxCpBaseResp { + private static final long serialVersionUID = -5028321625140879571L; + + @SerializedName("admin") + private List admin; + + /** + * The type Admin. + */ + @Getter + @Setter + public static class Admin extends WxCpBaseResp { + private static final long serialVersionUID = -5028321625140879571L; + + @SerializedName("userid") + private String userId; + + @SerializedName("open_userid") + private String openUserId; + + @SerializedName("auth_type") + private Integer authType; + + public String toJson() { + return WxGsonBuilder.create().toJson(this); + } + } + + /** + * From json wx cp tp admin. + * + * @param json the json + * @return the wx cp tp admin + */ + public static WxCpTpAdmin fromJson(String json) { + return WxCpGsonBuilder.create().fromJson(json, WxCpTpAdmin.class); + } + + public String toJson() { + return WxCpGsonBuilder.create().toJson(this); + } + +} diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpAppQrcode.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpAppQrcode.java new file mode 100644 index 0000000000..ada85c760c --- /dev/null +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpAppQrcode.java @@ -0,0 +1,35 @@ +package me.chanjar.weixin.cp.bean; + +import com.google.gson.annotations.SerializedName; +import lombok.Data; +import lombok.EqualsAndHashCode; +import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder; + +/** + * 应用的管理员 + * + * @author huangxiaoming + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class WxCpTpAppQrcode extends WxCpBaseResp { + private static final long serialVersionUID = -5028321625140879571L; + + @SerializedName("qrcode") + private String qrcode; + + /** + * From json wx cp tp admin. + * + * @param json the json + * @return the wx cp tp admin + */ + public static WxCpTpAppQrcode fromJson(String json) { + return WxCpGsonBuilder.create().fromJson(json, WxCpTpAppQrcode.class); + } + + public String toJson() { + return WxCpGsonBuilder.create().toJson(this); + } + +} diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpAuthInfo.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpAuthInfo.java new file mode 100644 index 0000000000..9919fd72b8 --- /dev/null +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpAuthInfo.java @@ -0,0 +1,324 @@ +package me.chanjar.weixin.cp.bean; + +import com.google.gson.annotations.SerializedName; +import lombok.Getter; +import lombok.Setter; +import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder; + +import java.io.Serializable; +import java.util.List; + +/** + * 服务商模式获取授权信息 + * + * @author yuanqixun + */ +@Getter +@Setter +public class WxCpTpAuthInfo extends WxCpBaseResp { + private static final long serialVersionUID = -5028321625140879571L; + + /** + * 服务商信息 + */ + @SerializedName("dealer_corp_info") + private DealerCorpInfo dealerCorpInfo; + + /** + * 授权企业信息 + */ + @SerializedName("auth_corp_info") + private AuthCorpInfo authCorpInfo; + + /** + * 授权信息。如果是通讯录应用,且没开启实体应用,是没有该项的。通讯录应用拥有企业通讯录的全部信息读写权限 + */ + @SerializedName("auth_info") + private AuthInfo authInfo; + + + /** + * 企业当前生效的版本信息 + */ + @SerializedName("edition_info") + private EditionInfo editionInfo; + + /** + * The type Dealer corp info. + */ + @Getter + @Setter + public static class DealerCorpInfo extends WxCpBaseResp { + private static final long serialVersionUID = -5028321625140879571L; + + @SerializedName("corpid") + private String corpId; + + @SerializedName("corp_name") + private String corpName; + } + + /** + * The type Auth corp info. + */ + @Getter + @Setter + public static class AuthCorpInfo implements Serializable { + private static final long serialVersionUID = -5028321625140879571L; + + @SerializedName("corpid") + private String corpId; + + @SerializedName("corp_name") + private String corpName; + + @SerializedName("corp_type") + private String corpType; + + @SerializedName("corp_square_logo_url") + private String corpSquareLogoUrl; + + @SerializedName("corp_round_logo_url") + private String corpRoundLogoUrl; + + @SerializedName("corp_user_max") + private String corpUserMax; + + @SerializedName("corp_agent_max") + private String corpAgentMax; + + /** + * 所绑定的企业微信主体名称(仅认证过的企业有) + */ + @SerializedName("corp_full_name") + private String corpFullName; + + /** + * 认证到期时间 + */ + @SerializedName("verified_end_time") + private Long verifiedEndTime; + + /** + * 企业类型,1. 企业; 2. 政府以及事业单位; 3. 其他组织, 4.团队号 + */ + @SerializedName("subject_type") + private Integer subjectType; + + /** + * 授权企业在微工作台(原企业号)的二维码,可用于关注微工作台 + */ + @SerializedName("corp_wxqrcode") + private String corpWxQrcode; + + @SerializedName("corp_scale") + private String corpScale; + + @SerializedName("corp_industry") + private String corpIndustry; + + @SerializedName("corp_sub_industry") + private String corpSubIndustry; + + @SerializedName("location") + private String location; + + } + + /** + * 授权信息 + */ + @Getter + @Setter + public static class AuthInfo implements Serializable { + private static final long serialVersionUID = -5028321625140879571L; + + /** + * 授权的应用信息,注意是一个数组,但仅旧的多应用套件授权时会返回多个agent,对新的单应用授权,永远只返回一个agent + */ + @SerializedName("agent") + private List agents; + + } + + /** + * 企业当前生效的版本信息 + */ + @Getter + @Setter + public static class EditionInfo implements Serializable { + private static final long serialVersionUID = -5028321625140879571L; + + /** + * 授权的应用信息,注意是一个数组,但仅旧的多应用套件授权时会返回多个agent,对新的单应用授权,永远只返回一个agent + */ + @SerializedName("agent") + private List agents; + + } + + /** + * The type Agent. + */ + @Getter + @Setter + public static class Agent implements Serializable { + private static final long serialVersionUID = -5028321625140879571L; + + @SerializedName("agentid") + private Integer agentId; + + @SerializedName("name") + private String name; + + @SerializedName("round_logo_url") + private String roundLogoUrl; + + @SerializedName("square_logo_url") + private String squareLogoUrl; + + /** + * 旧的多应用套件中的对应应用id,新开发者请忽略 + */ + @SerializedName("appid") + @Deprecated + private String appid; + + /** + * 授权模式,0为管理员授权;1为成员授权 + */ + @SerializedName("auth_mode") + private Integer authMode; + + /** + * 是否为代开发自建应用 + */ + @SerializedName("is_customized_app") + private Boolean isCustomizedApp; + + /** + * 应用权限 + */ + @SerializedName("privilege") + private Privilege privilege; + + /** + * 版本id + */ + @SerializedName("edition_id") + private String editionId; + + /** + * 版本名称 + */ + @SerializedName("edition_name") + private String editionName; + + /** + * 付费状态 + *
+ *
    + *
  • 0-没有付费;
  • + *
  • 1-限时试用;
  • + *
  • 2-试用过期;
  • + *
  • 3-购买期内;
  • + *
  • 4-购买过期;
  • + *
  • 5-不限时试用;
  • + *
  • 6-购买期内,但是人数超标, 注意,超标后还可以用7天;
  • + *
  • 7-购买期内,但是人数超标, 且已经超标试用7天
  • + *
+ */ + @SerializedName("app_status") + private Integer appStatus; + + /** + * 用户上限。 + *

特别注意, 以下情况该字段无意义,可以忽略:

+ *
    + *
  • 1. 固定总价购买
  • + *
  • 2. app_status = 限时试用/试用过期/不限时试用
  • + *
  • 3. 在第2条“app_status=不限时试用”的情况下,如果该应用的配置为“小企业无使用限制”,user_limit有效,且为限制的人数
  • + *
+ */ + @SerializedName("user_limit") + private Long userLimit; + + /** + * 版本到期时间, 秒级时间戳, 根据需要自行乘以1000(根据购买版本,可能是试用到期时间或付费使用到期时间)。 + *

特别注意,以下情况该字段无意义,可以忽略:

+ *
    + *
  • 1. app_status = 不限时试用
  • + *
+ */ + @SerializedName("expired_time") + private Long expiredTime; + + /** + * 是否虚拟版本 + */ + @SerializedName("is_virtual_version") + private Boolean isVirtualVersion; + + /** + * 是否由互联企业分享安装。详见 企业互联 + */ + @SerializedName("is_shared_from_other_corp") + private Boolean isSharedFromOtherCorp; + } + + /** + * 应用对应的权限 + */ + @Getter + @Setter + public static class Privilege implements Serializable { + private static final long serialVersionUID = -5028321625140879571L; + + /** + * 权限等级。 + * 1:通讯录基本信息只读 + * 2:通讯录全部信息只读 + * 3:通讯录全部信息读写 + * 4:单个基本信息只读 + * 5:通讯录全部信息只写 + */ + @SerializedName("level") + private Integer level; + + @SerializedName("allow_party") + private List allowParties; + + @SerializedName("allow_user") + private List allowUsers; + + @SerializedName("allow_tag") + private List allowTags; + + @SerializedName("extra_party") + private List extraParties; + + @SerializedName("extra_user") + private List extraUsers; + + @SerializedName("extra_tag") + private List extraTags; + + } + + + /** + * From json wx cp tp auth info. + * + * @param json the json + * @return the wx cp tp auth info + */ + public static WxCpTpAuthInfo fromJson(String json) { + return WxCpGsonBuilder.create().fromJson(json, WxCpTpAuthInfo.class); + } + + @Override + public String toJson() { + return WxCpGsonBuilder.create().toJson(this); + } + +} diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpContactSearch.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpContactSearch.java new file mode 100644 index 0000000000..11c653a433 --- /dev/null +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpContactSearch.java @@ -0,0 +1,71 @@ +package me.chanjar.weixin.cp.bean; + +import com.google.gson.annotations.SerializedName; +import lombok.Data; +import lombok.experimental.Accessors; +import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder; + +import java.io.Serializable; + +/** + * The type Wx cp tp contact search. + * + * @author uianz + * @since 2020 /12/23 下午 02:43 + */ +@Data +@Accessors(chain = true) +public class WxCpTpContactSearch implements Serializable { + private static final long serialVersionUID = -4301684507150486556L; + + /** + * 查询的企业corpid + */ + @SerializedName("auth_corpid") + private String authCorpId; + + /** + * 搜索关键词。当查询用户时应为用户名称、名称拼音或者英文名;当查询部门时应为部门名称或者部门名称拼音 + */ + @SerializedName("query_word") + private String queryWord; + + /** + * 查询类型 1:查询用户,返回用户userid列表 2:查询部门,返回部门id列表。 不填该字段或者填0代表同时查询部门跟用户 + */ + @SerializedName("query_type") + private Integer type; + + /** + * 应用id,若非0则只返回应用可见范围内的用户或者部门信息 + */ + @SerializedName("agentid") + private Integer agentId; + + /** + * 查询返回的最大数量,默认为50,最多为200,查询返回的数量可能小于limit指定的值 + */ + @SerializedName("limit") + private Integer limit; + + /** + * 如果需要精确匹配用户名称或者部门名称或者英文名,不填则默认为模糊匹配;1:匹配用户名称或者部门名称 2:匹配用户英文名 + */ + @SerializedName("full_match_field") + private Integer fullMatchField; + + /** + * 用于分页查询的游标,字符串类型,由上一次调用返回,首次调用可不填 + */ + @SerializedName("cursor") + private String cursor; + + /** + * 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/WxCpTpContactSearchResp.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpContactSearchResp.java new file mode 100644 index 0000000000..074b30bc0e --- /dev/null +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpContactSearchResp.java @@ -0,0 +1,77 @@ +package me.chanjar.weixin.cp.bean; + +import com.google.gson.annotations.SerializedName; +import lombok.Data; +import lombok.EqualsAndHashCode; +import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder; + +import java.io.Serializable; +import java.util.List; + +/** + * The type Wx cp tp contact search resp. + * + * @author uianz + * @since 2020 /12/23 下午 02:55 + */ +@EqualsAndHashCode(callSuper = true) +@Data +public class WxCpTpContactSearchResp extends WxCpBaseResp { + + @SerializedName("is_last") + private Boolean isLast; + + @SerializedName("query_result") + private QueryResult queryResult; + + @SerializedName("next_cursor") + private String nextCursor; + + /** + * The type Query result. + */ + @Data + public static class QueryResult implements Serializable { + private static final long serialVersionUID = -4301684507150486556L; + + @SerializedName("user") + private User user; + @SerializedName("party") + private Party party; + + /** + * The type User. + */ + @Data + public static class User implements Serializable { + private static final long serialVersionUID = -4301684507150486556L; + @SerializedName("userid") + private List userid; + @SerializedName("open_userid") + private List openUserId; + } + + /** + * The type Party. + */ + @Data + public static class Party implements Serializable { + private static final long serialVersionUID = -4301684507150486556L; + + @SerializedName("department_id") + private List departmentId; + } + + } + + /** + * From json wx cp tp contact search resp. + * + * @param json the json + * @return the wx cp tp contact search resp + */ + public static WxCpTpContactSearchResp fromJson(String json) { + return WxCpGsonBuilder.create().fromJson(json, WxCpTpContactSearchResp.class); + } + +} diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpConvertTmpExternalUserIdResult.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpConvertTmpExternalUserIdResult.java new file mode 100644 index 0000000000..9bca31c2d0 --- /dev/null +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpConvertTmpExternalUserIdResult.java @@ -0,0 +1,41 @@ +package me.chanjar.weixin.cp.bean; + +import com.google.gson.annotations.SerializedName; +import lombok.Getter; +import lombok.Setter; +import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder; + +import java.util.List; + +@Setter +@Getter +public class WxCpTpConvertTmpExternalUserIdResult extends WxCpBaseResp { + + + @SerializedName("invalid_tmp_external_userid_list") + private List results; + + @Getter + @Setter + public static class Results { + + @SerializedName("tmp_external_userid") + private String tmpExternalUserId; + + @SerializedName("external_userid") + private String externalUserId; + + @SerializedName("corpid") + private String corpId; + + @SerializedName("userid") + private String userId; + } + + @SerializedName("invalid_tmp_external_userid_list") + private List invalidTmpExternalUserIdList; + + public static WxCpTpConvertTmpExternalUserIdResult fromJson(String json) { + return WxCpGsonBuilder.create().fromJson(json, WxCpTpConvertTmpExternalUserIdResult.class); + } +} diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpCorp.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpCorp.java index 9ca59b843d..939a4eddf6 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpCorp.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpCorp.java @@ -1,42 +1,54 @@ -package me.chanjar.weixin.cp.bean; - -import java.io.Serializable; - -import com.google.gson.annotations.SerializedName; - -import lombok.Data; -import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder; - -/** - * 微信部门. - * - * @author Daniel Qian - */ -@Data -public class WxCpTpCorp implements Serializable { - - private static final long serialVersionUID = -5028321625140879571L; - @SerializedName("corpid") - private String corpId; - @SerializedName("corp_name") - private String corpName; - @SerializedName("corp_full_name") - private String corpFullName; - @SerializedName("corp_type") - private String corpType; - @SerializedName("corp_square_logo_url") - private String corpSquareLogoUrl; - @SerializedName("corp_user_max") - private String corpUserMax; - @SerializedName("permanent_code") - private String permanentCode; - - public static WxCpTpCorp fromJson(String json) { - return WxCpGsonBuilder.create().fromJson(json, WxCpTpCorp.class); - } - - public String toJson() { - return WxCpGsonBuilder.create().toJson(this); - } - -} +package me.chanjar.weixin.cp.bean; + +import com.google.gson.annotations.SerializedName; +import lombok.Data; +import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder; + +import java.io.Serializable; + +/** + * 微信部门. + * + * @author Daniel Qian + */ +@Data +public class WxCpTpCorp implements Serializable { + + private static final long serialVersionUID = -5028321625140879571L; + @SerializedName("corpid") + private String corpId; + @SerializedName("corp_name") + private String corpName; + @SerializedName("corp_full_name") + private String corpFullName; + @SerializedName("corp_type") + private String corpType; + @SerializedName("corp_square_logo_url") + private String corpSquareLogoUrl; + @SerializedName("corp_user_max") + private String corpUserMax; + @SerializedName("permanent_code") + private String permanentCode; + @SerializedName("auth_info") + private String authInfo; + + /** + * From json wx cp tp corp. + * + * @param json the json + * @return the wx cp tp corp + */ + public static WxCpTpCorp fromJson(String json) { + return WxCpGsonBuilder.create().fromJson(json, WxCpTpCorp.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/WxCpTpCorpId2OpenCorpId.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpCorpId2OpenCorpId.java new file mode 100644 index 0000000000..73dfd49064 --- /dev/null +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpCorpId2OpenCorpId.java @@ -0,0 +1,35 @@ +package me.chanjar.weixin.cp.bean; + +import com.google.gson.annotations.SerializedName; +import lombok.Data; +import lombok.EqualsAndHashCode; +import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder; + +/** + * 应用的管理员 + * + * @author huangxiaoming + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class WxCpTpCorpId2OpenCorpId extends WxCpBaseResp { + private static final long serialVersionUID = -5028321625140879571L; + + @SerializedName("open_corpid") + private String openCorpId; + + /** + * From json wx cp tp admin. + * + * @param json the json + * @return the wx cp tp admin + */ + public static WxCpTpCorpId2OpenCorpId fromJson(String json) { + return WxCpGsonBuilder.create().fromJson(json, WxCpTpCorpId2OpenCorpId.class); + } + + public String toJson() { + return WxCpGsonBuilder.create().toJson(this); + } + +} diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpCustomizedAppDetail.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpCustomizedAppDetail.java new file mode 100644 index 0000000000..f9ca645b82 --- /dev/null +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpCustomizedAppDetail.java @@ -0,0 +1,221 @@ +package me.chanjar.weixin.cp.bean; + +import com.google.gson.annotations.SerializedName; +import lombok.Data; +import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder; + +import java.io.Serializable; +import java.util.List; + +/** + * 代开发应用详情. + * + * @author Binary Wang + * created on 2026-01-14 + */ +@Data +public class WxCpTpCustomizedAppDetail extends WxCpBaseResp { + + /** + * 授权方企业id + */ + @SerializedName("auth_corpid") + private String authCorpId; + + /** + * 授权方企业名称 + */ + @SerializedName("auth_corp_name") + private String authCorpName; + + /** + * 授权方企业方形头像 + */ + @SerializedName("auth_corp_square_logo_url") + private String authCorpSquareLogoUrl; + + /** + * 授权方企业圆形头像 + */ + @SerializedName("auth_corp_round_logo_url") + private String authCorpRoundLogoUrl; + + /** + * 授权方企业类型,1. 企业; 2. 政府以及事业单位; 3. 其他组织, 4.团队小型企业(原企业微信认证版用户) + */ + @SerializedName("auth_corp_type") + private Integer authCorpType; + + /** + * 授权方企业在微工作台(原企业号)的二维码,可用于关注微工作台 + */ + @SerializedName("auth_corp_qrcode_url") + private String authCorpQrcodeUrl; + + /** + * 授权方企业用户规模 + */ + @SerializedName("auth_corp_user_limit") + private Integer authCorpUserLimit; + + /** + * 授权方企业的主体名称(仅认证或验证过的企业有),即企业全称 + */ + @SerializedName("auth_corp_full_name") + private String authCorpFullName; + + /** + * 企业类型,1. 已验证企业;2. 已认证企业 + */ + @SerializedName("auth_corp_verified_type") + private Integer authCorpVerifiedType; + + /** + * 授权方企业所属行业 + */ + @SerializedName("auth_corp_industry") + private String authCorpIndustry; + + /** + * 授权方企业所属子行业 + */ + @SerializedName("auth_corp_sub_industry") + private String authCorpSubIndustry; + + /** + * 授权方企业所在地址 + */ + @SerializedName("auth_corp_location") + private String authCorpLocation; + + /** + * 代开发自建应用详情 + */ + @SerializedName("customized_app_list") + private List customizedAppList; + + /** + * From json wx cp tp customized app detail. + * + * @param json the json + * @return the wx cp tp customized app detail + */ + public static WxCpTpCustomizedAppDetail fromJson(String json) { + return WxCpGsonBuilder.create().fromJson(json, WxCpTpCustomizedAppDetail.class); + } + + @Override + public String toJson() { + return WxCpGsonBuilder.create().toJson(this); + } + + /** + * 代开发自建应用信息 + */ + @Data + public static class CustomizedApp implements Serializable { + private static final long serialVersionUID = 1L; + + /** + * 代开发自建应用的agentid + */ + @SerializedName("agentid") + private Integer agentId; + + /** + * 代开发自建应用对应的模板id + */ + @SerializedName("template_id") + private String templateId; + + /** + * 代开发自建应用的name + */ + @SerializedName("name") + private String name; + + /** + * 代开发自建应用的description + */ + @SerializedName("description") + private String description; + + /** + * 代开发自建应用的logo url + */ + @SerializedName("logo_url") + private String logoUrl; + + /** + * 代开发自建应用的可见范围 + */ + @SerializedName("allow_userinfos") + private AllowUserInfos allowUserInfos; + + /** + * 代开发自建应用是否被禁用 + */ + @SerializedName("close") + private Integer close; + + /** + * 代开发自建应用主页url + */ + @SerializedName("home_url") + private String homeUrl; + + /** + * 代开发自建应用的模式,0 = 代开发自建应用;1 = 第三方应用代开发 + */ + @SerializedName("app_type") + private Integer appType; + } + + /** + * 应用可见范围 + */ + @Data + public static class AllowUserInfos implements Serializable { + private static final long serialVersionUID = 1L; + + /** + * 应用可见范围(成员) + */ + @SerializedName("user") + private List users; + + /** + * 应用可见范围(部门) + */ + @SerializedName("department") + private List departments; + } + + /** + * 成员信息 + */ + @Data + public static class User implements Serializable { + private static final long serialVersionUID = 1L; + + /** + * 成员userid + */ + @SerializedName("userid") + private String userId; + } + + /** + * 部门信息 + */ + @Data + public static class Department implements Serializable { + private static final long serialVersionUID = 1L; + + /** + * 部门id + */ + @SerializedName("id") + private Integer id; + } +} diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpDepart.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpDepart.java new file mode 100644 index 0000000000..39d3601a2f --- /dev/null +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpDepart.java @@ -0,0 +1,42 @@ +package me.chanjar.weixin.cp.bean; + +import lombok.Data; +import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder; + +import java.io.Serializable; + +/** + * 企业微信的部门. + * + * @author Daniel Qian + */ +@Data +public class WxCpTpDepart implements Serializable { + private static final long serialVersionUID = -5028321625140879571L; + + private Integer id; + private String name; + private String enName; + private Integer parentid; + private Integer order; + + /** + * From json wx cp tp depart. + * + * @param json the json + * @return the wx cp tp depart + */ + public static WxCpTpDepart fromJson(String json) { + return WxCpGsonBuilder.create().fromJson(json, WxCpTpDepart.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/WxCpTpOpenKfIdConvertResult.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpOpenKfIdConvertResult.java new file mode 100644 index 0000000000..b6b0e9ef82 --- /dev/null +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpOpenKfIdConvertResult.java @@ -0,0 +1,46 @@ +package me.chanjar.weixin.cp.bean; + +import com.google.gson.annotations.SerializedName; +import lombok.Getter; +import lombok.Setter; +import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder; + +import java.util.List; + +@Setter +@Getter +public class WxCpTpOpenKfIdConvertResult extends WxCpBaseResp { + + /** + * 微信客服ID转换结果 + */ + @SerializedName("items") + private List items; + + /** + * 无法转换的微信客服ID列表 + */ + @SerializedName("invalid_open_kfid_list") + private List invalidOpenKfIdList; + + @Getter + @Setter + public static class Item { + + /*** + * 企业主体下的微信客服ID + */ + @SerializedName("open_kfid") + private String openKfId; + + /** + * 服务商主体下的微信客服ID,如果传入的open_kfid已经是服务商主体下的ID,则new_open_kfid与open_kfid相同。 + */ + @SerializedName("new_open_kfid") + private String newOpenKfId; + } + + public static WxCpTpOpenKfIdConvertResult fromJson(String json) { + return WxCpGsonBuilder.create().fromJson(json, WxCpTpOpenKfIdConvertResult.class); + } +} diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpPermanentCodeInfo.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpPermanentCodeInfo.java new file mode 100644 index 0000000000..5330194abe --- /dev/null +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpPermanentCodeInfo.java @@ -0,0 +1,386 @@ +package me.chanjar.weixin.cp.bean; + +import com.google.gson.annotations.SerializedName; +import lombok.Getter; +import lombok.Setter; +import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder; + +import java.io.Serializable; +import java.util.List; + +/** + * 服务商模式获取永久授权码信息 + * + * @author yunaqixun + */ +@Getter +@Setter +public class WxCpTpPermanentCodeInfo extends WxCpBaseResp { + + private static final long serialVersionUID = -5028321625140879571L; + + @SerializedName("access_token") + private String accessToken; + + @SerializedName("expires_in") + private Long expiresIn; + + @SerializedName("permanent_code") + private String permanentCode; + + /** + * 授权企业信息 + */ + @SerializedName("auth_corp_info") + private AuthCorpInfo authCorpInfo; + + /** + * 授权信息。如果是通讯录应用,且没开启实体应用,是没有该项的。通讯录应用拥有企业通讯录的全部信息读写权限 + */ + @SerializedName("auth_info") + private AuthInfo authInfo; + + /** + * 授权用户信息 + */ + @SerializedName("auth_user_info") + private AuthUserInfo authUserInfo; + + /** + * 推广二维码安装相关信息 + */ + @SerializedName("register_code_info") + private RegisterCodeInfo registerCodeInfo; + + /** + * 企业当前生效的版本信息 + */ + @SerializedName("edition_info") + private EditionInfo editionInfo; + + /** + * 安装应用时,扫码或者授权链接中带的state值。详见state说明 + * state说明: + * 目前会返回state包含以下几个场景。 + * (1)扫带参二维码授权代开发模版。 + */ + @SerializedName("state") + private String state; + + /** + * The type Auth corp info. + */ + @Getter + @Setter + public static class AuthCorpInfo implements Serializable { + private static final long serialVersionUID = -5028321625140879571L; + + @SerializedName("corpid") + private String corpId; + + @SerializedName("corp_name") + private String corpName; + + @SerializedName("corp_type") + private String corpType; + + @SerializedName("corp_square_logo_url") + private String corpSquareLogoUrl; + + @SerializedName("corp_round_logo_url") + private String corpRoundLogoUrl; + + @SerializedName("corp_user_max") + private String corpUserMax; + + @SerializedName("corp_agent_max") + private String corpAgentMax; + + /** + * 所绑定的企业微信主体名称(仅认证过的企业有) + */ + @SerializedName("corp_full_name") + private String corpFullName; + + /** + * 认证到期时间 + */ + @SerializedName("verified_end_time") + private Long verifiedEndTime; + + /** + * 企业类型,1. 企业; 2. 政府以及事业单位; 3. 其他组织, 4.团队号 + */ + @SerializedName("subject_type") + private Integer subjectType; + + /** + * 授权企业在微工作台(原企业号)的二维码,可用于关注微工作台 + */ + @SerializedName("corp_wxqrcode") + private String corpWxQrcode; + + @SerializedName("corp_scale") + private String corpScale; + + @SerializedName("corp_industry") + private String corpIndustry; + + @SerializedName("corp_sub_industry") + private String corpSubIndustry; + + @SerializedName("location") + private String location; + + } + + /** + * 授权信息 + */ + @Getter + @Setter + public static class AuthInfo implements Serializable { + private static final long serialVersionUID = -5028321625140879571L; + + /** + * 授权的应用信息,注意是一个数组,但仅旧的多应用套件授权时会返回多个agent,对新的单应用授权,永远只返回一个agent + */ + @SerializedName("agent") + private List agents; + + } + + /** + * 企业当前生效的版本信息 + */ + @Getter + @Setter + public static class EditionInfo implements Serializable { + private static final long serialVersionUID = -5028321625140879571L; + + /** + * 授权的应用信息,注意是一个数组,但仅旧的多应用套件授权时会返回多个agent,对新的单应用授权,永远只返回一个agent + */ + @SerializedName("agent") + private List agents; + + } + + /** + * The type Agent. + */ + @Getter + @Setter + public static class Agent implements Serializable { + private static final long serialVersionUID = -5028321625140879571L; + + @SerializedName("agentid") + private Integer agentId; + + @SerializedName("name") + private String name; + + @SerializedName("round_logo_url") + private String roundLogoUrl; + + @SerializedName("square_logo_url") + private String squareLogoUrl; + + /** + * 旧的多应用套件中的对应应用id,新开发者请忽略 + */ + @SerializedName("appid") + @Deprecated + private String appid; + + /** + * 授权模式,0为管理员授权;1为成员授权 + */ + @SerializedName("auth_mode") + private Integer authMode; + + /** + * 是否为代开发自建应用 + */ + @SerializedName("is_customized_app") + private Boolean isCustomizedApp; + + /** + * 应用权限 + */ + @SerializedName("privilege") + private Privilege privilege; + + /** + * 版本id + */ + @SerializedName("edition_id") + private String editionId; + + /** + * 版本名称 + */ + @SerializedName("edition_name") + private String editionName; + + /** + * 付费状态 + *
+ *
    + *
  • 0-没有付费;
  • + *
  • 1-限时试用;
  • + *
  • 2-试用过期;
  • + *
  • 3-购买期内;
  • + *
  • 4-购买过期;
  • + *
  • 5-不限时试用;
  • + *
  • 6-购买期内,但是人数超标, 注意,超标后还可以用7天;
  • + *
  • 7-购买期内,但是人数超标, 且已经超标试用7天
  • + *
+ */ + @SerializedName("app_status") + private Integer appStatus; + + /** + * 用户上限。 + *

特别注意, 以下情况该字段无意义,可以忽略:

+ *
    + *
  • 1. 固定总价购买
  • + *
  • 2. app_status = 限时试用/试用过期/不限时试用
  • + *
  • 3. 在第2条“app_status=不限时试用”的情况下,如果该应用的配置为“小企业无使用限制”,user_limit有效,且为限制的人数
  • + *
+ */ + @SerializedName("user_limit") + private Long userLimit; + + /** + * 版本到期时间, 秒级时间戳, 根据需要自行乘以1000(根据购买版本,可能是试用到期时间或付费使用到期时间)。 + *

特别注意,以下情况该字段无意义,可以忽略:

+ *
    + *
  • 1. app_status = 不限时试用
  • + *
+ */ + @SerializedName("expired_time") + private Long expiredTime; + + /** + * 是否虚拟版本 + */ + @SerializedName("is_virtual_version") + private Boolean isVirtualVersion; + + /** + * 是否由互联企业分享安装。详见 企业互联 + */ + @SerializedName("is_shared_from_other_corp") + private Boolean isSharedFromOtherCorp; + + } + + /** + * 授权人员信息 + */ + @Getter + @Setter + public static class AuthUserInfo implements Serializable { + private static final long serialVersionUID = -5028321625140879571L; + + @SerializedName("userid") + private String userId; + + @SerializedName("name") + private String name; + + @SerializedName("avatar") + private String avatar; + + /** + * 授权管理员的open_userid,可能为空 + */ + @SerializedName("open_userid") + private String openUserid; + } + + /** + * 推广二维码安装相关信息 + */ + @Getter + @Setter + public static class RegisterCodeInfo implements Serializable { + private static final long serialVersionUID = -5028321625140879571L; + + /** + * 注册码 + */ + @SerializedName("register_code") + private String registerCode; + + /** + * 推广包ID + */ + @SerializedName("template_id") + private String templateId; + + /** + * 仅当获取注册码指定该字段时才返回 + */ + @SerializedName("state") + private String state; + + } + + /** + * 应用对应的权限 + */ + @Getter + @Setter + public static class Privilege implements Serializable { + private static final long serialVersionUID = -5028321625140879571L; + + /** + * 权限等级。 + * 1:通讯录基本信息只读 + * 2:通讯录全部信息只读 + * 3:通讯录全部信息读写 + * 4:单个基本信息只读 + * 5:通讯录全部信息只写 + */ + @SerializedName("level") + private Integer level; + + @SerializedName("allow_party") + private List allowParties; + + @SerializedName("allow_user") + private List allowUsers; + + @SerializedName("allow_tag") + private List allowTags; + + @SerializedName("extra_party") + private List extraParties; + + @SerializedName("extra_user") + private List extraUsers; + + @SerializedName("extra_tag") + private List extraTags; + + + } + + /** + * From json wx cp tp permanent code info. + * + * @param json the json + * @return the wx cp tp permanent code info + */ + public static WxCpTpPermanentCodeInfo fromJson(String json) { + return WxCpGsonBuilder.create().fromJson(json, WxCpTpPermanentCodeInfo.class); + } + + @Override + public String toJson() { + return WxCpGsonBuilder.create().toJson(this); + } + +} diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpPreauthCode.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpPreauthCode.java new file mode 100644 index 0000000000..31c61b3a2b --- /dev/null +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpPreauthCode.java @@ -0,0 +1,38 @@ +package me.chanjar.weixin.cp.bean; + +import com.google.gson.annotations.SerializedName; +import lombok.Getter; +import lombok.Setter; +import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder; + +/** + * 预授权码返回 + * + * @author yqx created on 2020/3/19 + */ +@Getter +@Setter +public class WxCpTpPreauthCode extends WxCpBaseResp { + + /** + * The Pre auth code. + */ + @SerializedName("pre_auth_code") + String preAuthCode; + + /** + * The Expires in. + */ + @SerializedName("expires_in") + Long expiresIn; + + /** + * From json wx cp tp preauth code. + * + * @param json the json + * @return the wx cp tp preauth code + */ + public static WxCpTpPreauthCode fromJson(String json) { + return WxCpGsonBuilder.create().fromJson(json, WxCpTpPreauthCode.class); + } +} diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpProlongTryResult.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpProlongTryResult.java new file mode 100644 index 0000000000..427e020a2f --- /dev/null +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpProlongTryResult.java @@ -0,0 +1,49 @@ +package me.chanjar.weixin.cp.bean; + +import com.google.gson.annotations.SerializedName; +import lombok.Getter; +import lombok.Setter; +import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder; + +/** + * 应用市场延长试用期结果 + * + * @author leiguoqing created on 2022年4月24日 + */ +@Getter +@Setter +public class WxCpTpProlongTryResult extends WxCpBaseResp { + + /** + * The constant serialVersionUID. + */ + private static final long serialVersionUID = -5028321625140879571L; + + /** + * 延长后的试用到期时间(秒级时间戳) + */ + @SerializedName("try_end_time") + private Long tryEndTime; + + + /** + * From json wx cp tp order list get result. + * + * @param json the json + * @return the wx cp tp order list get result + */ + public static WxCpTpProlongTryResult fromJson(String json) { + return WxCpGsonBuilder.create().fromJson(json, WxCpTpProlongTryResult.class); + } + + /** + * To json string. + * + * @return the string + */ + @Override + public String toJson() { + return WxCpGsonBuilder.create().toJson(this); + } + +} diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpTag.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpTag.java new file mode 100644 index 0000000000..a73ec171b4 --- /dev/null +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpTag.java @@ -0,0 +1,34 @@ +package me.chanjar.weixin.cp.bean; + +import com.google.gson.annotations.SerializedName; +import lombok.Data; +import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder; + +import java.io.Serializable; + +/** + * The type Wx cp tp tag. + * + * @author zhangq + * @since 2021-02-14 16:15 + */ +@Data +public class WxCpTpTag implements Serializable { + private static final long serialVersionUID = 581740383760234134L; + + @SerializedName("tagid") + private String tagId; + + @SerializedName("tagname") + private String tagName; + + /** + * Deserialize wx cp tp tag. + * + * @param json the json + * @return the wx cp tp tag + */ + public static WxCpTpTag deserialize(String json) { + return WxCpGsonBuilder.create().fromJson(json, WxCpTpTag.class); + } +} diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpTagAddOrRemoveUsersResult.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpTagAddOrRemoveUsersResult.java new file mode 100644 index 0000000000..565cbb408c --- /dev/null +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpTagAddOrRemoveUsersResult.java @@ -0,0 +1,23 @@ +package me.chanjar.weixin.cp.bean; + +import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder; + +/** + * 企业微信第三方开发-增加标签成员成员api响应体 + * + * @author zhangq + * @since 2021/2/14 16:44 + */ +public class WxCpTpTagAddOrRemoveUsersResult extends WxCpTagAddOrRemoveUsersResult { + private static final long serialVersionUID = 3490401800490702052L; + + /** + * Deserialize wx cp tp tag add or remove users result. + * + * @param json the json + * @return the wx cp tp tag add or remove users result + */ + public static WxCpTpTagAddOrRemoveUsersResult deserialize(String json) { + return WxCpGsonBuilder.create().fromJson(json, WxCpTpTagAddOrRemoveUsersResult.class); + } +} diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpTagGetResult.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpTagGetResult.java new file mode 100644 index 0000000000..134656e438 --- /dev/null +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpTagGetResult.java @@ -0,0 +1,24 @@ +package me.chanjar.weixin.cp.bean; + +import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder; + +/** + * 获取标签成员接口响应体 + * + * @author zhangq + * @since 2021/2/14 16:28 + */ +public class WxCpTpTagGetResult extends WxCpTagGetResult { + private static final long serialVersionUID = 9051748686315562400L; + + /** + * Deserialize wx cp tp tag get result. + * + * @param json the json + * @return the wx cp tp tag get result + */ + public static WxCpTpTagGetResult deserialize(String json) { + return WxCpGsonBuilder.create().fromJson(json, WxCpTpTagGetResult.class); + } + +} diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpTagIdListConvertResult.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpTagIdListConvertResult.java new file mode 100644 index 0000000000..e24d36d4d0 --- /dev/null +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpTagIdListConvertResult.java @@ -0,0 +1,51 @@ +package me.chanjar.weixin.cp.bean; + + +import com.google.gson.annotations.SerializedName; +import lombok.Getter; +import lombok.Setter; +import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder; + +import java.util.List; + +@Getter +@Setter +public class WxCpTpTagIdListConvertResult extends WxCpBaseResp { + + private static final long serialVersionUID = -6153589164415497369L; + + + /** + * 客户标签转换结果 + */ + @SerializedName("items") + private List items; + + /** + * 无法转换的客户标签ID列表 + */ + @SerializedName("invalid_external_tagid_list") + private List invalidExternalTagIdList; + + + @Getter + @Setter + public static class Item { + + /** + * 企业主体下的客户标签ID + */ + @SerializedName("external_tagid") + private String externalTagId; + + /** + * 服务商主体下的客户标签ID,如果传入的external_tagid已经是服务商主体下的ID,则open_external_tagid与external_tagid相同。 + */ + @SerializedName("open_external_tagid") + private String openExternalTagId; + } + + public static WxCpTpTagIdListConvertResult fromJson(String json) { + return WxCpGsonBuilder.create().fromJson(json, WxCpTpTagIdListConvertResult.class); + } +} diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpTemplateList.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpTemplateList.java new file mode 100644 index 0000000000..83abbb6e7d --- /dev/null +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpTemplateList.java @@ -0,0 +1,83 @@ +package me.chanjar.weixin.cp.bean; + +import com.google.gson.annotations.SerializedName; +import lombok.Data; +import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder; + +import java.io.Serializable; +import java.util.List; + +/** + * 应用模板列表. + * + * @author Binary Wang + * created on 2026-01-14 + */ +@Data +public class WxCpTpTemplateList extends WxCpBaseResp { + + /** + * 应用模板列表 + */ + @SerializedName("template_list") + private List